miniyus / mapper
v2.7.0
2022-03-22 07:04 UTC
Requires
- php: ^7.4
- ext-json: *
- illuminate/console: ^v7.30|^8.0
- illuminate/database: ^v7.30|^8.0
- illuminate/support: ^v7.30|^8.0
- netresearch/jsonmapper: ^v3.1.0|^4.0.0
Requires (Dev)
- phpunit/phpunit: ^9.5
README
目录
目的
虽然Laravel框架非常有用,但基本上推荐使用关联数组。这可以在很大程度上提高生产力,但可能会增加协作和维护管理的难度。为了在尽可能减少对生产力的干扰的同时使维护管理变得简单,我们尝试将视图的DTO(数据传输对象)和用于DB数据管理的Entity对象进行分离。但是,由于DTO是用于层次间数据交换的对象,而Entity是具有DB表架构的对象,因此它们之间可能存在属性上的差异,且相对于Entity来说,DTO可能会有更多的更改。因此,我们认为需要一个可以在两个类之间进行转换和控制的函数,这就是我们构思此包的原因。
结构和用途
-
DTO
- 作为DataTransferObject执行数据传递的任务。用于在不同层之间传递从Request接收到的数据。
- 作为DataTransferObject执行数据传递的任务。用于在不同层之间传递从Request接收到的数据。
-
Entity
- 具有DB表属性的经典类。
- 具有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集合的方法。
- 在从Laravel模型获取多个记录时,观察到了继承自Laravel集合的Eloquent Collection对象的使用。我认为使用集合对象来控制Entity对象会更好,因此实现了继承自Laravel集合的Entities类。与基本集合不同的是,Entities::toDtos()提供了将Entities转换为具有相同目的的Dto集合的方法。
-
Dtos
- 与Entities具有相同目的的设计,可以更轻松地控制Dto对象。
- 与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中的类,它使得获取所需的值变得更容易。
- 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。
- 在添加具有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);