简体   繁体   English

使用ArrayObject在PHP中可强制执行的数组结构?

[英]Enforceable array structure in PHP using ArrayObject?

I've a set of data (~30 properties, all having their own array of values) that I want to pass around to various classes in PHP and I want to also enforce the data's array structure. 我有一组数据(〜30个属性,都有各自的值数组),我想传递给PHP中的各个类,并且我也想强制执行数据的数组结构。 Multiple classes will be expecting this structure to be consistent. 多个类将期望此结构是一致的。

Because of these facts I can't really rely on a standard array and so I decided to pass an object around. 由于这些事实,我不能真正依赖于标准数组,因此我决定传递一个对象。 I looked into ArrayObject and while it allows me to set/get as if the class were an array I didn't see anything about enforcing the structure. 我研究了ArrayObject,虽然它使我可以像设置类一样是一个数组来进行设置/获取,但是我对强制执行结构一无所知。

Is there an existing standard class that can handle enforcement of it's array-like structure while still being treated as an array, eg, basically ArrayObject + enforcement? 是否有一个现有的标准类可以处理其类似数组的结构,同时仍被视为数组,例如基本上是ArrayObject +强制?

An example of the array structure: 数组结构的示例:

$item_type_props = array(
    'phone'     => array('speed' => 1000, 'self_label' => false, 'support_poe' => true, 'bluetooth' => false),
    'pbx'       => array('acd_support' => true, 'max_conn' => 300, max_phones => 600),
    'adapter'   => array('fxo' => 4, 'fxs' => 0, 't1_e1_pri' => 0),
    etc...
);

I know each property in the array could be it's own class and enforce it's own fields through the constructor and set/get but then I suddenly have ~30 classes that are nothing but a bunch of attributes and that seems somewhat excessive for just holding data. 我知道数组中的每个属性都可能是它自己的类,并通过构造函数和set / get强制执行了它自己的字段,但是随后我突然有了约30个类,这些类不过是一堆属性,对于仅保存数据而言似乎有点多余。


Alternatively, if I'm just approaching this from the wrong mindset and missing something really, really obvious then please do point it out. 或者,如果我只是以错误的思维方式来处理此问题,并且确实缺少某些非常明显的东西,那么请务必指出。 I get the feeling that I am but my brain might be having a vacation. 我感到自己在,但我的大脑可能正在休假。

While you could roll your own, I encourage you to use an existing validation implementation. 尽管可以自己动手,但我建议您使用现有的验证实现。 For example, Symfony\\Validator allows you to define nested structures and the requirements on each level: 例如, Symfony\\Validator允许您在每个级别上定义嵌套结构和要求:

use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;

$validator = Validation::createValidator();

$constraint = new Assert\Collection(array(
    // the keys correspond to the keys in the input array
    'name' => new Assert\Collection(array(
      'first_name' => new Assert\Length(array('min' => 101)),
      'last_name' => new Assert\Length(array('min' => 1)),
    )),
    'email' => new Assert\Email(),
    'simple' => new Assert\Length(array('min' => 102)),
    'gender' => new Assert\Choice(array(3, 4)),
    'file' => new Assert\File(),
    'password' => new Assert\Length(array('min' => 60)),
));

$violations = $validator->validate($input, $constraint);

This lets you push the details of how to validate down to another (already tested) level, while letting your code focus on why it needs this data. 这使您可以将如何进行验证的详细信息下推到另一个(已测试)级别,同时使您的代码专注于为什么需要此数据。 For the Symfony case, you can use an array as the storage mechanism, and use a design that firewalls unvalidated from validated data. 对于Symfony案例,您可以使用array作为存储机制,并使用对未经验证的数据进行未验证的防火墙的设计。


One way we might do this is notationally. 我们可以这样做的一种方式是符号表示法。 Pretend we have implemented a method, perhaps using Symfony's validator, to return a validated array. 假设我们已经实现了一种方法,也许使用了Symfony的验证器,以返回经过验证的数组。 We can use Hungarian notation to indicate our structure has passed through validation and is "safe": 我们可以使用匈牙利表示法来表示我们的结构已通过验证并且是“安全的”:

<?php
$vInput = validate($_GET); // Hungarian notation: any variable beginning with "v" is "validated" and safe to use

function foobar(array $vInput) { ... }

While this is performant, it's not great for maintenance in the long term. 虽然这是高性能的,但从长远来看,对于维护不太好。 So, you might consider an object wrapper that allows you to leverage the type system: 因此,您可以考虑使用对象包装器,该包装器可以利用类型系统:

use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;

class ValidatedArray extends \ArrayObject {
    public function construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator') {
        $violations = Validation::createValidator()->validate($array, $this->constraints());
        // throw exception if any violations
        parent::__construct($input, $flags, $iterator_class);
    }

    public function __offsetSet($index, $value) {
        $constraints = $this->constraints()[$index]; // specific constraints to this index
        $violations = Validation::createValidator()->validate($array, $constraints);
        // throw exception on violations
        parent::__offsetSet($index, $value);
    }

    public function constraints() {
        return new Assert\Collection(...);
    }
}

$input = new ValidatedArray($_REQUEST); // first time validation
$input['foo'] = 'bar'; // also subject to validation

You might want to make this implementation an abstract base class, with concrete descendants implementing the constraints method to provide the specific limitations on the array object itself. 您可能希望使此实现成为abstract基类,并由具体的后代实现constraints方法,以提供对数组对象本身的特定限制。 This provides a flexible way to get dedicated Data Transfer Objects. 这提供了获取专用数据传输对象的灵活方法。

Generally speaking I would argue that - unless you are passing data to another context, eg javascript - a PHP application should be nicely organized in PHP classes. 一般来说,我认为-除非您将数据传递到另一个上下文(例如javascript),否则PHP应用程序应很好地组织在PHP类中。 This is simply the easiest way to enforce the structure. 这是执行结构的最简单方法。 Your are right, this might result in quite straightforward DTO's with a bunch of getters and setters, but it will beat checking array structures for sure. 您是对的,这可能会导致带有许多吸气剂和吸气剂的简单直接的DTO,但可以肯定地检查数组结构。 In your case there also appears to be a relationship in the array, otherwise it would not make sense combining them into an array at all. 在您的情况下,数组中似乎也存在关系,否则将它们组合成数组根本没有意义。

Using PHP7 you can clearly define the method signature and enforce the types, eg 使用PHP7,您可以清楚地定义方法签名并强制执行类型,例如

public function setSomething(string $myValue)
{
    $this->something = $myValue;
}

Same with return types: 与返回类型相同:

public function myActionMethod(array $options): ActionRespsonse
{
    // Do something
}

If you have more complex data types, I would recommend using Value Objects. 如果您有更复杂的数据类型,我建议您使用值对象。 These are nothing more but simple PHP classes that represent a more complex value. 这些不过是代表更复杂值的简单PHP类。 For example, a phone number: 例如,一个电话号码:

public function setPhoneNumber(PhoneNumber $phoneNumber)
{
    $this->phoneNumber = $phoneNumber;
}

Here the PhoneNumber is a Value Object which effectively is a tiny class by itself which enforces its usage: 这里的PhoneNumber是一个Value Object,它实际上是一个很小的类,它强制执行其用法:

class PhoneNumber {
    private $phoneNumber;

    public __construct(string $phoneNumber) {
        if (strlen($phoneNumber) != 10) {
            throw new \Exception('Not a valid phone number');
        }

        $this->phoneNumber = $phoneNumber;
     }
}

This is also where the validation can tie into the answer from @bishop as you can use an existing Validator to help you out. 这也是验证可以绑定到@bishop的答案的地方,因为您可以使用现有的Validator来帮助您。 You can find an example of a Value Object of a Phone Number here (just googled it): Example Phone Number Value Object 您可以在此处找到一个电话号码值对象的示例 (只需在Google上搜索一下): 电话号码值对象示例

I do have the feeling that you might be converting your PHP data to an array for another reason? 我确实觉得您可能出于其他原因将PHP数据转换为数组? Like interacting with a database, or passing it on to a different context, such as Javascript? 像与数据库进行交互,还是将其传递给其他上下文(例如Javascript)?

In that case, once you have your DTO's and VO's neatly defined you can then consider serializing them eg to/from JSON. 在这种情况下,一旦您对DTO和VO进行了整洁的定义,就可以考虑将其序列化,例如,从JSON到JSON的序列化。 You can use Symfony libraries for that, as described here: Symfony Serializer 您可以为此使用Symfony库,如下所述: Symfony序列化器

If you really want arrays you can also consider hydrating them to/from entities, using the library from Marco Pivetta (ocramius) who is pretty much the authority on hydration an approach extensively used in Doctrine: Ocramius Hydrator 如果您真的想要数组,也可以考虑使用Marco Pivetta(ocramius)的库为实体与实体之间进行水合,该库在水合方面几乎是权威,这是在教义中广泛使用的方法: Ocramius Hydrator

Plenty of options, eh!? 有很多选择,嗯!

To be honest, though, IMHO typically there would need to be a pretty good argument to pass these complex arrays around as arrays offer very little in supporting functionality. 不过,老实说,恕我直言,通常需要一个很好的论据来传递这些复杂的数组,因为数组在支持功能方面几乎没有。 In addition, your code will very likely and very quickly become difficult to read and maintain, as at every point where there would be some kind of modification of the array, or usage of its data elements, you would need to implement all kinds of checks or run a validation as described by @bishop. 此外,您的代码很可能且非常容易变得难以阅读和维护,因为在需要对数组进行某种修改或使用其数据元素的每个点上,您都需要实施各种检查或按照@bishop所述运行验证。 I would not allow something like that to exist in our applications code bases... 我不允许在我们的应用程序代码库中存在类似的东西。

So, concluding: If you have a tightly defined set of PHP Objects with well established constructor, properties, getters, setters, constructor, relationships, action methods and besides that some kind of serializing mechanism, you're in good shape. 因此,得出以下结论:如果您有一组严格定义的PHP对象,并且它们具有完善的构造函数,属性,getter,setter,构造函数,关系,操作方法,并且除了某种序列化机制之外,您的状态还不错。 Also other developers will be 'forced' to work with the objects and provided interface (methods and VO's) thus guaranteeing a degree of maintainability and quality. 同样,其他开发人员也将被迫使用对象和提供的接口(方法和VO),从而保证一定程度的可维护性和质量。

By the way, you might consider reasing Martin Fowler's thoughts about these things: Martin Fowler: DTO Martin Fowler: VO Martin Fowler: DTO2 顺便说一句,您可以考虑放宽马丁·福勒对这些事情的想法: 马丁·福勒:DTO 马丁·福勒:VO 马丁·福勒:DTO2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM