Skip to content

Latest commit

 

History

History
331 lines (259 loc) · 11.5 KB

README.RU.md

File metadata and controls

331 lines (259 loc) · 11.5 KB

ClassTransformer

Packagist Version GitHub Workflow Status Coverage License Packagist Downloads Packagist Downloads

Эта библиотека позволит вам легко преобразовать любой набор данных в нужный вам объект. От вас не требуется менять структуру классов, наследовать их от внешних модулей и т.д. Никаких танцев с бубнами - только данные и нужный класс.

Хорошей практикой считается написание кода независимого от сторонних пакетов и фреймворков. Код разбивается на сервисы, доменные зоны, различные слои и т.д. Для передачи данных между слоями, как правило, используется шаблон DataTransfer Object (DTO). DTO - это объект, который необходим для инкапсуляции данных и отправки их из одной подсистемы приложения в другую.

Таким образом, сервисы/методы работают с конкретным объектом и данными необходимым для него. При этом неважно, откуда эти данные были получены - это может быть http запрос, БД, файл и т.д.

Соответственно, при каждом вызове сервиса нам необходимо инициализировать данное DTO. Но сопоставлять каждый раз данные вручную - неэффективно, и сказывается на читабельности кода, особенно если объект сложный.

Здесь на помощь приходит данный пакет, который берет на себя всю работу с мапингом и инициализацией необходимой DTO.

📜 Установка

Пакет может быть установлен с помощью composer:

композитору требуется yzen.dev/plain-to-class

Примечание: Текущая версия пакета поддерживает только PHP 8.1 +.

Для PHP версии 7.4 вы можете ознакомиться с документацией в версия v0.*.

Для PHP версии 8.0 вы можете ознакомиться с документацией в версия v1.*.

📜 Использование

Общий вариант использования:

📜 Base

namespace DTO;

class CreateUserDTO
{
    public string $email;
    public float $balance;
}
$data = [
    'email' => '[email protected]',
    'balance' => 128.41,
];
$dto = ClassTransformer::transform(CreateUserDTO::class, $data);
var_dump($dto);

Result:

object(\LoginDTO)
  'email' => string(13) "[email protected]"
  'balance' => float(128.41) 

Также с версии php 8 вы можете передавать именованные аргументы:

$dto = ClassTransformer::transform(CreateUserDTO::class,
        email: '[email protected]',
        balance: 128.41
      );

Если свойство не является скалярным типом, и у него явно указан класс, оно будет автоматически рекурсивно к нему приведено.

class ProductDTO
{
    public int $id;
    public string $name;
}

class PurchaseDTO
{
    public ProductDTO $product;
    public float $cost;
}

$data = [
    'product' => ['id' => 1, 'name' => 'phone'],
    'cost' => 10012.23,
];

$purchaseDTO = ClassTransformer::transform(PurchaseDTO::class, $data);
var_dump($purchaseDTO);

Результат:

object(PurchaseDTO)
  public ProductDTO 'product' => 
    object(ProductDTO)
      public int 'id' => int 1
      public string 'name' => string 'phone' (length=5)
  public float 'cost' => float 10012.23

📜 Коллекция

Если у вас есть массив объектов определенного класса, то вы должны указать для него атрибут ConvertArray, передав ему в какой класс вам нужно привести элементы.

Также можно указать класс в PHP DOC, но тогда вам нужно написать полный путь к этому классу array <\DTO\ProductDTO>. Это делается для того, чтобы точно знать, какой экземпляр нужно создать. Поскольку Reflection не предоставляет готовых функций для получения файла use. Помимо use, вы можете указать псевдоним и его будет сложнее отследить. Пример:

class ProductDTO
{
    public int $id;
    public string $name;
}

class PurchaseDTO
{
    #[ConvertArray(ProductDTO::class)]
    public array $products;
}

$data = [
    'products' => [
        ['id' => 1, 'name' => 'phone',],
        ['id' => 2, 'name' => 'bread',],
    ],
];
$purchaseDTO = ClassTransformer::transform(PurchaseDTO::class, $data);

📜 Анонимная коллекция

В случае если вам нужно преобразовать массив данных в массив объектов класса, вы можете реализовать это с помощью метода transformCollection.

$data = [
  ['id' => 1, 'name' => 'phone'],
  ['id' => 2, 'name' => 'bread'],
];
$products = ClassTransformer::transformCollection(ProductDTO::class, $data);

В результате этого вы получите массив объектов ProductDTO

array(2) {
  [0]=>
      object(ProductDTO) {
        ["id"]=> int(1)
        ["name"]=> string(5) "phone"
      }
  [1]=>
      object(ProductDTO) {
        ["id"]=> int(2)
        ["name"]=> string(5) "bread"
      }
} 

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

    $userData = ['id' => 1, 'email' => '[email protected]', 'balance' => 10012.23];
    $purchaseData = [
        'products' => [
            ['id' => 1, 'name' => 'phone',],
            ['id' => 2, 'name' => 'bread',],
        ],
        'user' => ['id' => 3, 'email' => '[email protected]', 'balance' => 10012.23,],
    ];

    $result = ClassTransformer::transformMultiple([UserDTO::class, PurchaseDTO::class], [$userData, $purchaseData]);
    
    [$user, $purchase] = $result;
    var_dump($user);
    var_dump($purchase);

Result:

object(UserDTO) (3) {
  ["id"] => int(1)
  ["email"]=> string(13) "[email protected]"
  ["balance"]=> float(10012.23)
}

object(PurchaseDTO) (2) {
  ["products"]=>
  array(2) {
    [0]=>
    object(ProductDTO)#349 (3) {
      ["id"]=> int(1)
      ["name"]=> string(5) "phone"
    }
    [1]=>
    object(ProductDTO)#348 (3) {
      ["id"]=> int(2)
      ["name"]=> string(5) "bread"
    }
  }
  ["user"]=>
  object(UserDTO)#332 (3) {
    ["id"]=> int(3)
    ["email"]=> string(13) "[email protected]"
    ["balance"]=> float(10012.23)
  }
}

📜 ** Стиль написания**

Постоянная проблема со стилем написания, например, в базе данных это snake_case, а в коде camelCase. И их постоянно нужно как-то трансформировать. Пакет позаботится об этом, вам просто нужно указать атрибут WritingStyle в свойстве:

class WritingStyleSnakeCaseDTO
{
    #[WritingStyle(WritingStyle::STYLE_CAMEL_CASE, WritingStyle::STYLE_SNAKE_CASE)]
    public string $contact_fio;

    #[WritingStyle(WritingStyle::STYLE_CAMEL_CASE)]
    public string $contact_email;
}


 $data = [
  'contactFio' => 'yzen.dev',
  'contactEmail' => '[email protected]',
];
$model = ClassTransformer::transform(WritingStyleSnakeCaseDTO::class, $data);
var_dump($model);
RESULT:

object(WritingStyleSnakeCaseDTO) (2) {
  ["contact_fio"]=> string(8) "yzen.dev"
  ["contact_email"]=> string(13) "[email protected]"
}

📜 Alias

Для свойства можно задать различные возможные alias'ы, которые будут также искаться в источнике данных. Это может быть полезно если DTO формируется по разным источникам данных.

class WithAliasDTO
{
    #[FieldAlias('userFio')]
    public string $fio;

    #[FieldAlias(['email', 'phone'])]
    public string $contact;
}

📜 Кастомизация сеттеров

Если поле требует дополнительной обработки при его инициализации, вы можете мутировать его сеттер. Для это создайте в классе метод следующего формата - set{$name}Attribute. Пример:

class UserDTO
{
    public int $id;
    public string $real_address;

    public function setRealAddressAttribute(string $value)
    {
        $this->real_address = strtolower($value);
    }
}

📜 Пост обработка

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

class UserDTO
{
    public int $id;
    public float $balance;

    public function afterTransform()
    {
        $this->balance = 777;
    }
}

📜 Кастомное преобразование

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

class CustomTransformUserDTOArray
{
    public string $email;
    public string $username;
    
    public function transform($args)
    {
        $this->email = $args['login'];
        $this->username = $args['fio'];
    }
}