miniyus/mapper

v2.7.0 2022-03-22 07:04 UTC

README

Version

Documentation Maintenance License: MIT

目录

目的

虽然Laravel框架非常有用,但基本上推荐使用关联数组。这可以在很大程度上提高生产力,但可能会增加协作和维护管理的难度。为了在尽可能减少对生产力的干扰的同时使维护管理变得简单,我们尝试将视图的DTO(数据传输对象)和用于DB数据管理的Entity对象进行分离。但是,由于DTO是用于层次间数据交换的对象,而Entity是具有DB表架构的对象,因此它们之间可能存在属性上的差异,且相对于Entity来说,DTO可能会有更多的更改。因此,我们认为需要一个可以在两个类之间进行转换和控制的函数,这就是我们构思此包的原因。

结构和用途

  • DTO

    • 作为DataTransferObject执行数据传递的任务。用于在不同层之间传递从Request接收到的数据。

  • Entity

    • 具有DB表属性的经典类。

  • Mapper

    • 根据Dto或Entity类型,使用config/mapper.php文件通过Dto、Entity、Map类连接。
    • 虽然Mapper可以单独调用,但可以通过Dto、Entity内嵌的toDto()、toEntity()方法调用。

  • Entities

    • 在从Laravel模型获取多个记录时,观察到了继承自Laravel集合的Eloquent Collection对象的使用。我认为使用集合对象来控制Entity对象会更好,因此实现了继承自Laravel集合的Entities类。与基本集合不同的是,Entities::toDtos()提供了将Entities转换为具有相同目的的Dto集合的方法。

  • Dtos

    • 与Entities具有相同目的的设计,可以更轻松地控制Dto对象。

  • Map

    • 可以映射Dto与Entity之间各属性的类。

    • 要实现的method有两个,即toEntity()和toDto()。

    • 当Dto与Entity的属性名和类型不匹配,需要相互匹配不同属性时使用。

    • 如果不实现Map类,将使用Dto、Entity的map()方法。(但请注意,属性必须匹配才能获得预期结果。)

    • 在实现toEntity()和toDto()时,建议使用getter、setter,并使用PHP docblock检查getter和setter的有效性。如果不使用的话,也可以使用' instanceof'。这是因为mapper2.0的一个实现目标是利用IDE预先防止开发过程中可能出现的错误。

  • MapperConfig

    • config/mapper.php中的类,它使得获取所需的值变得更容易。

  • DataMapper

    • Dto、Entity类内部的map()、mapList()方法中调用的类。
    • 使用JsonMapper库。

  • Dynamic

    • 动态属性分配类。使用$fillable属性定义字符串数组,以指定要使用的属性。
    • 模仿Laravel Model类创建。利用魔术方法控制属性。(适用于测试或属性经常变化的情况)

  • CustomCollection 通过继承该类,整合Dtos、Entities的公共功能。

  • Traits:尽可能利用Trait应用公共功能。

    • ReadOnlyDto:只读DTO
    • ToDto:实现toDto()方法
    • ToDtos:实现toDtos()方法
    • ToEntities:实现toEntities()方法
    • ToEntity:实现toEntity()方法
    • Transformation:实现toArray()、toJson()、makeHidden()、makeVisible()、jsonSerialize()

  • Mapable

    • 在添加具有map()、mapList()、toArray()(继承Arrayable)、toJson()(继承Jsonable)方法的interface Dynamic类后,认为需要添加一个interface来聚集Data对象公共功能,因此添加了该interface。

安装

composer require miniyus/mapper

php artisan vendor:publish --provider="Miniyus\Mapper\Provider\MapperServiceProvider"

用法

DTO
<?php
// Dto 만들기
use Miniyus\Mapper\Data\Dto;

class DemoDto extends Dto
{
    private int $id;
    private string $name;
    
    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }
    
    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id): DemoDto
    {
        $this->id = $id;
        return $this;
    }
    
    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }
    
     /**
     * @param string $name
     * @return $this
     */
    public function setName($name): DemoDto
    {
        $this->name = $name;
        return $this;
    }
}

// 기능 예제

// 1. 인스턴스 생성
// 생성 시 파라미터는 Arrayable, array, object(public 속성만 할당 됨) 유형만 허용
/** @var DemoDto $dto */
$dto = new DemoDto(['id'=>1,'name'=>'abc']);
$dto = DemoDto::newInstance(['id'=>1,'name'=>'abc']);

// 2. map(), mapList() 메서드
// 생성 시 파라미터와 동일하다.
// 두번째 파라미터로 Closure, callable 활용이 가능하다.
$dto->map(['id'=>2,'name'=>'Laravel']);

// 콜백 파라미터 사용법
$dto->map($someArray,function($somArray, DemoDto $dto){
    return $dto->setId($somArray['id']);
});

// Dtos는 라라벨의 Collection 클래스를 상속받은 클래스
// 기본적인 사용법은 라라벨 Collection과 동일하다.
// 두번째 파라미터로 Closure, callable 활용이 가능하다.
/** @var \Miniyus\Mapper\Data\Dtos $dtos */
$dtos = $dto->mapList([DemoDto::newInstance(),DemoDto::newInstance()]);

// 3. Entity 변환
// 파라미터 없으면, config/mapper.php 파일에 매칭해둔 Map 클래스를 통하여 매핑한 Entity 객체를 반환
$dto->toEntity();

// 연결된 Map 클래스가 있으면 Map클래스의 toEntity() 메서드를 통해 매핑
// 연결된 Map 클래스가 없으면 (new DemoEntity())->map($dto) 와 같음
$dto->toEntity(DemoEntity::class);

// 2번째 파라미터 활용
// Map 클래스 지정 매핑
// Map 클래스를 config/mapper.php에 명시하지 않았더라도 매개변수로 넘겨주면 해당 Map클래스를 통해 매핑한다.
$dto->toEntity(DemoEntity::class, DemoMap::class);

// Closure 활용 매핑
// Closure 혹은 기타 callable을 활용할 경우 해당 로직으로 완전히 대제됩니다.
$dto->toEntity(DemoEntity::class, function(DemoDto $dto, DemoEntity $entity){
    // getter, setter 매핑 로직
    return $entity;
});

// Closure 활용 매핑 2
$dto->toEntity(DemoEntity::class, function(DemoDto $dto){
    // Mapper는  배열로 반환된 경우도 매핑이 가능하다.
    return [
        'id'=>$dto->getId(),
        'name'=>$dto->getName()
    ];
});

// callable 활용
function exampleCallable($dto, $entity){
// getter, setter 등등... 매핑 로직
    return $entity;
}
$dto->toEntity(DemoEntity::class, 'exampleCallable');

// 4. 기타 변환

// 배열, 파라미터에서 null 허용 여부를 선택할 수 있다.
// true면 null인 속성도 출력, false면 null인 속성 제외 
$dto->toArray();

// json
// toJson 파라미터는 기존 toJson과 동일하게 option 파라미터가 들어간다.
$dto->toJson();

// 속성 숨김, laravel model의 hideAttributes의 makeHidden() 메서드와 사용법은 동일
$dto->makeHidden('name');

// 속성 보이기, laravel model의 hideAttributes의 makeVisible() 메서드와 사용법은 동일
$dto->makeVisible('name');

// 할당 되지 않은 속성 기본 값으로 초기화
$dto->initialize();
Entity
<?php

use Miniyus\Mapper\Data\Entity;

/**
 * Class DemoEntity
 * 
 * @author Yoo Seongmin <miniyu97@iokcom.com>
 */
class DemoEntity extends Entity{
    // 기타 내장 메서드들은 toDto()를 제외하고 크게 다르지 않음
    // 구현하려는 설계 방식에 맞춰 작성
    
    /**
     * Model과 연결을 위해   
     * @return string
     */
    protected function getIdentifier() : string{
        return DemoModel::class;
    }
}

// 대부분의 기능들은 Dto와 동일
$entity = DemoEntity::newInstance();

// 사용법은 toEntity(),toEntities()와 동일하나, 첫번째 파라미터는 Dto를 상속받은 객체만 들어갈 수 있다.
$entity->toDto(DemoDto::class);
$entity->toDtos(DemoDto::class);

// 모델로 변환
$entity->toModel();
// 내부 적으로는 getIdentifier() 메서드의 명시한 모델을 생성하여 모델의 fill() 메서드를 활용한다.
(new DemoModel())->fill($entity->toArray(true));
Collections(Dtos,Entities)
<?php
// 생성시, 파라미터는 array|Collection|Arrayable
$dtos = \Miniyus\Mapper\Data\Dtos::newInstance();
$entities = \Miniyus\Mapper\Data\Entities::newInstance();

// toDtos & toEntities()
// 입력받은 파라미터 클래스로 기존 데이터를 변환하고 Dtos,Entities 객체로 반환
// 사용법은 toDto(), toEntity() 와 같으나, 반환 결과는 Entities, Dtos
// 내부적으로 Mapper 클래스를 사용하기 때문에 두번째 파라미터의 사용법도 동일하다.
$entities->toDtos(DemoDto::class);
$dtos->toEntities(DemoEntity::class);
Map
<?php

use Miniyus\Mapper\Maps\Map;

class DemoMap extends Map
{
    protected function toDto(\Miniyus\Mapper\Data\Entity $entity,\Miniyus\Mapper\Data\Dto $dto)
    {
        // TODO: Implement toDto() method.
        // case 1
        // getter, setter 활용
        if($entity instanceof DemoEntity && $dto instanceof DemoDto){
            // getter & setter
            return $dto;
        }
        
        // case 2
        // Map 클래스 또한 배열 리턴이 가능하다.
        return [
            'id' => $entity->getId();
        ];
    }
    
    protected function toEntity(\Miniyus\Mapper\Data\Dto $dto,\Miniyus\Mapper\Data\Entity $entity)
    {
        // TODO: Implement toEntity() method.
        // case 1
        if($entity instanceof DemoEntity && $dto instanceof DemoDto){
            // getter & setter
            return $entity;
        }
        
        // Map 클래스 또한 배열 리턴이 가능하다.
        return [
            'id' => $dto->getId();
        ];
    }
}
  • generate:map命令
# map 클래스는 php artisan generate:map {name} {--json=} 명령을 통해 생성할 수 있다.
# {name}은 생성할 Map 클래스이름, --json 옵션은 매핑관련 파일이다.
# config/mapper.php에 명시된 Map인 경우, 자동으로 생성해 준다.
# 단, Dto와 Entity객체에서 서로 일치하는 속성만 getter, setter를 만들어 준다.
# --json 옵션에 미리 어떤 항목끼리 매핑할지 정할 수 있다.
# 기타 경로 설정은 config/make_class.php 참조
php artisan generate:map DemoMap --json=DemoMap
- generate:map --json={json filename} 파일 구조
{
  "dto": "매핑하고자 하는 Dto 클래스의 이름(namespace 포함)",
  "entity": "매핑하고자 하는 Entity 클래스의 이름(namespace 포함)",
  "map": {
    "entityPropertyName": "dtoPropertyName"
  }
}
Dynamic
use Miniyus\Mapper\Data\Dynamic;

class DemoDynamic extends Dynamic
{
    /**
     * 해당 속성의 배열 값이 해당 클래스에서 접근 및 제어 가능한 속성이 된다. 
     * @var array|string[] 
     */
    protected array $fillable = [
        'id',
        'name'
    ];
    
    /**
     * @param $data
     * @param callable|Closure|null $callback
     * @return \Miniyus\Mapper\Data\Contracts\Mapable
     */
    public function map($data,$callback = null) : \Miniyus\Mapper\Data\Contracts\Mapable
    {
        // TODO: Implement map() method.
    }
    
    /**
     * @param $data
     * @param callable|Closure|null $callback
     * @return Collection|array
     */
    public function mapList($data,$callback = null)
    {
        // TODO: Implement mapList() method.
    }   
}

// 1. 생성
// 생성자의 파라미터는 array 유형이다.
$demo = new Dynamic(['id'=>1,'name'=>'name']);

// Dynamic 클래스는 매직메서드를 사용하여 속성에 접근할 수 있다.
$demo->id = 1;
$demo->id;

// getter, setter처럼 사용할 수 있다.
$demo->setId(1);
$demo->getId();

// 2. 기타 변환
// Dto, Entity와 동일하게 toArray(), toJson()을 지원한다.
// 추가적으로 Dynamic클래스는 fromJson() 메서드를 사용할 수 있다.
$jsonString = "{\"id\":1,\"name\":\"name\"}";

$demo->fromJson($jsonString);

// fill(), 라라벨 Model의 fill()가 동일하다.(과정은 다르지만, 기능면으로)
$array = ['id'=>1,'name'=>'name'];
$demo->fill($array);

// 3. map(), mapList()
// Dynamic은 Mapable 인터페이스 메서드들을 구현해줘야 한다.
// 간단 예제
public function map($parameters)
{
    // 실제 fill() 메서드는 배열만 받기 때문에 예외 처리가 별도로 필요함.
    return $this->fill($parameters);
}

// mapList의 경우 명시적으로 return type이 정의되어 있지 않기 때문에 type에 주의
public function mapList($parameters)
{
    // collect 활용 예시
    return collect($parameters)->each(function($item){
        return (new static)->map($item);
    });
}
Mapper
<?php
// Mapper 클래스는 Entity, Dto에 내장되어 사용된다.
// Entity <-> Dto 변환에 특화되어 있기 때문에, 그 외의 용도로 사용할 수 없다.
// 객체 생성
$mapper = \Miniyus\Mapper\Mapper::newInstance();

// 단일 객체 매핑
$mapper->map($sourceObj, $targetClass, $callback);

// 리스트 객체 매핑 | array, Laravel Collection 객체 허용됨
$mapper->mapList($sourceList, $targetClass, $callback);

// 기타 정적 메서드 (제거: v2.6.0)
// \Miniyus\Mapper\Mapper::mappingDto($dto, $entityClassName, $callback);
// \Miniyus\Mapper\Mapper::mappingEntity($entity, $dtoClassName, $callback);


# DataMapper(JsonMapper를 Wrapping)
# JsonMapper에서 지원하지 않는 Type 지원 및 예외처리 로직을 추가했다.
# 배열 -> 객체, 객체 -> 객체 변환을 위한 클래스
// 첫번째 파라미터: 변환 전 데이터
// 두번째 파라미터: 데이터를 할당 받을 객체
// 세번째 파라미터: 콜백 함수
// 콜백 파라미터가 있으면 먼저 일치하는 항목들을 매핑하고 콜백 함수의 내용을 실행
\Miniyus\Mapper\Data\DataMapper::map($data, $object, $callback);

变更日志