Принудительная структура массива в PHP с использованием ArrayObject?

У меня есть набор данных (~30 свойств, каждый из которых имеет свой собственный массив значений), которые я хочу передать различным классам в PHP, и я также хочу усилить структуру массива данных. Несколько классов будут ожидать, что эта структура будет последовательной.

Из-за этих фактов я не могу полагаться на стандартный массив, поэтому я решил передать объект. Я посмотрел в ArrayObject, и хотя он позволяет мне устанавливать / получать, как если бы класс был массивом, я ничего не видел в обеспечении структуры.

Существует ли существующий стандартный класс, который может обрабатывать принудительное применение своей структуры, подобной массиву, и при этом обрабатываться как массив, например, в основном ArrayObject + принудительное применение?

Пример структуры массива:

$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...
);

Я знаю, что каждое свойство в массиве может быть его собственным классом и применять свои собственные поля через конструктор и set / get, но потом у меня внезапно появляется ~ 30 классов, которые являются не чем иным, как набором атрибутов, и это кажется несколько чрезмерным для простого хранения данных.


В качестве альтернативы, если я просто подхожу к этому из-за неправильного мышления и упускаю что-то действительно, действительно очевидное, тогда, пожалуйста, укажите это. Я чувствую, что я есть, но мой мозг может быть в отпуске.

2 ответа

Решение

Несмотря на то, что вы можете использовать свою собственную, я рекомендую вам использовать существующую реализацию валидации. Например, 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);

Это позволяет вам продвигать детали того, как проверять, до другого (уже протестированного) уровня, в то же время позволяя вашему коду сосредоточиться на том, зачем ему нужны эти данные. Для случая Symfony вы можете использовать array в качестве механизма хранения и использовать конструкцию, в которой брандмауэры не проверяются на соответствие проверенным данным.


Один способ, которым мы могли бы сделать это, - это обозначение. Представьте, что мы реализовали метод, возможно, с использованием валидатора Symfony, чтобы вернуть проверенный массив. Мы можем использовать венгерскую нотацию, чтобы указать, что наша структура прошла проверку и является "безопасной":

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

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

В то время как это эффективно, это не очень хорошо для технического обслуживания в долгосрочной перспективе. Итак, вы можете рассмотреть объектную оболочку, которая позволяет вам использовать систему типов:

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

Возможно, вы захотите сделать эту реализацию abstract базовый класс, с конкретными потомками, реализующими constraints метод для предоставления конкретных ограничений на сам объект массива. Это обеспечивает гибкий способ получения выделенных объектов передачи данных.

Вообще говоря, я бы сказал, что - если вы не передаете данные в другой контекст, например, в javascript, - приложение PHP должно быть хорошо организовано в классы PHP. Это просто самый простой способ обеспечить соблюдение структуры. Вы правы, это может привести к довольно простым DTO с кучей геттеров и сеттеров, но это наверняка побьет проверку структур массива. В вашем случае, похоже, также есть связь в массиве, иначе было бы бессмысленно объединять их в массив.

Используя PHP7, вы можете четко определить сигнатуру метода и применить типы, например

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

То же самое с типами возврата:

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

Если у вас есть более сложные типы данных, я бы порекомендовал использовать объекты значения. Это не более чем простые классы PHP, которые представляют более сложное значение. Например, номер телефона:

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

Здесь PhoneNumber - это объект-значение, который фактически сам по себе является крошечным классом, который обеспечивает его использование:

class PhoneNumber {
    private $phoneNumber;

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

        $this->phoneNumber = $phoneNumber;
     }
}

В этом случае проверка также может быть связана с ответом @bishop, поскольку вы можете использовать существующий Валидатор, чтобы помочь вам. Вы можете найти пример объекта-значения телефонного номера здесь (просто погуглил его): Пример объекта-номера телефонного значения

У меня есть ощущение, что вы можете конвертировать ваши данные PHP в массив по другой причине? Например, взаимодействовать с базой данных или передавать ее в другой контекст, такой как Javascript?

В этом случае, как только вы точно определили свои DTO и VO, вы можете рассмотреть их сериализацию, например, в / из JSON. Для этого вы можете использовать библиотеки Symfony, как описано здесь: Symfony Serializer

Если вам действительно нужны массивы, вы также можете подумать о том, чтобы их гидрировать в / из сущностей, используя библиотеку Marco Pivetta (ocramius), которая в значительной степени является авторитетом в области гидратации - подход, широко используемый в Doctrine: Ocramius Hydrator.

Множество вариантов, а!?

Честно говоря, хотя, IMHO, как правило, должен быть довольно хороший аргумент для передачи этих сложных массивов, так как массивы очень мало поддерживают в функциональности. Кроме того, ваш код с большой вероятностью и очень быстро станет трудным для чтения и обслуживания, так как в каждой точке, где произойдет какая-либо модификация массива или использование его элементов данных, вам потребуется реализовать все виды проверок. или выполните проверку, как описано в @bishop. Я бы не позволил чему-то подобному существовать в кодах наших приложений...

Итак, подытожим: если у вас есть строго определенный набор объектов PHP с хорошо разработанным конструктором, свойствами, геттерами, сеттерами, конструктором, отношениями, методами действий и, кроме того, каким-то механизмом сериализации, вы в хорошей форме. Также другие разработчики будут "вынуждены" работать с объектами и предоставленным интерфейсом (методами и ВО), что гарантирует определенную степень удобства и качества.

Кстати, вы могли бы подумать об изменении мыслей Мартина Фаулера об этих вещах: Мартин Фаулер: DTO Мартин Фаулер: VO Мартин Фаулер: DTO2

Другие вопросы по тегам