Skip to content

Instantly share code, notes, and snippets.

@Pierstoval
Created May 1, 2019 20:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pierstoval/6675bf766873834e6ec0f530d5cbbc33 to your computer and use it in GitHub Desktop.
Save Pierstoval/6675bf766873834e6ec0f530d5cbbc33 to your computer and use it in GitHub Desktop.

Base DTO Controller with EasyAdmin

In order to use DTOs in EasyAdmin, you have to override a few things.

Either you override everything by hand like in this example project from @yceruto, or you can also use these classes to simplify the workflow.

Let's consider:

  • A Post entity
  • An EasyAdmin-based backend with preconfigured Post entity (see the provided easyadmin_post.yaml file)

If you want to use DTOs in your workflow, here is what you need to do:

  • Create the PostAdminDTO class and make it implement the provided EasyAdminDTOInterface, you can addsome assertions using Symfony Validator component since it's the object that EasyAdmin will send to the form (and that will be sent to the Validator)
  • Create the AdminPostController and make it extend EasyAdminController and use the provided BaseDTOControllerTrait trait
  • Implement the necessary abstract methods, and do what you want to do with your DTO where it's needed (common needs are provided already with the existing methods)
<?php
declare(strict_types=1);
/*
* (c) Alexandre Rock Ancelet <pierstoval@gmail.com> and Studio Agate.
*
* Licensed with MIT
*/
namespace App\Admin\Controller;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
use App\Admin\Controller\BaseDTOControllerTrait;
use Admin\DTO\EasyAdminDTOInterface;
use App\DTO\PostAdminDTO;
use App\Entity\Post;
class AdminPostController extends EasyAdminController
{
use BaseDTOControllerTrait;
protected function getDTOClass(): string
{
return PostAdminDTO::class;
}
protected function createEntityFromDTO(EasyAdminDTOInterface $dto): object
{
return Post::fromAdmin($dto);
}
protected function updateEntityWithDTO(object $entity, EasyAdminDTOInterface $dto): object
{
// Should be an instance of "Post" at that time
$entity->updateFromAdmin($dto);
return $entity;
}
}
<?php
declare(strict_types=1);
/*
* (c) Alexandre Rock Ancelet <pierstoval@gmail.com> and Studio Agate.
*
* Licensed with MIT
*/
namespace App\Admin\Controller;
use Admin\DTO\EasyAdminDTOInterface;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
use Symfony\Component\Form\FormInterface;
trait BaseDTOControllerTrait
{
abstract protected function getDTOClass(): string;
/**
* @return object an instance of the configured Entity
*/
abstract protected function createEntityFromDTO(EasyAdminDTOInterface $dto): object;
/**
* You can return a new instance if you want to override any existing entity instead of updating one (i.e when you have immutable objects).
*
* @return object an instance of the configured Entity, or the same object that is passed as argument
*/
abstract protected function updateEntityWithDTO(object $entity, EasyAdminDTOInterface $dto): object;
protected function createNewEntity()
{
$dtoClass = $this->doGetDTOClass();
return $dtoClass::createEmpty();
}
protected function persistEntity($object): void
{
$dtoClass = $this->doGetDTOClass();
if (!($object instanceof $dtoClass)) {
throw new \InvalidArgumentException('DTO is not of valid type.');
}
parent::persistEntity($this->createEntityFromDTO($object));
}
protected function getEntityFormOptions($entity, $view)
{
$dtoClass = $this->doGetDTOClass();
$options = parent::getEntityFormOptions($entity, $view);
$options['data_class'] = $dtoClass;
return $options;
}
protected function createEntityFormBuilder($entity, $view)
{
$dtoClass = $this->doGetDTOClass();
if (!($entity instanceof $dtoClass)) {
$entity = $dtoClass::createFromEntity($entity);
}
return parent::createEntityFormBuilder($entity, $view);
}
protected function updateEntity($entity, FormInterface $editForm = null): void
{
if (!$editForm) {
throw new \InvalidArgumentException('Form is mandatory to update entity.');
}
$dtoClass = $this->doGetDTOClass();
$entityClass = $this->entity['class'];
$dto = $editForm->getData();
if (!($dto instanceof $dtoClass)) {
throw new \InvalidArgumentException(\sprintf('DTO is not of valid type, expected %s.', $dtoClass));
}
if (!($entity instanceof $entityClass)) {
throw new \InvalidArgumentException(\sprintf('Only %s is supported in this controller.', $entityClass));
}
parent::updateEntity($this->updateEntityWithDTO($entity, $dto));
}
private function doGetDTOClass(): string
{
$this->validateDTOClass();
return $this->getDTOClass();
}
private function validateDTOClass(): void
{
if (!($this instanceof EasyAdminController)) {
throw new \RuntimeException(\sprintf('The DTO-based controller %s must extend %s.', \get_class($this), EasyAdminController::class));
}
$dtoClass = $this->getDTOClass();
if (!\is_subclass_of($dtoClass, EasyAdminDTOInterface::class, true)) {
throw new \RuntimeException(\sprintf('DTO class %s must implement %s.', $dtoClass, EasyAdminDTOInterface::class));
}
}
}
easy_admin:
entities:
Post:
class: App\Entity\Post
controller: App\Admin\Controller\AdminPostController
list:
fields:
- { property: id }
- { property: title }
- { property: content }
form:
fields:
- { property: title }
- { property: content }
<?php
declare(strict_types=1);
/*
* (c) Alexandre Rock Ancelet <pierstoval@gmail.com> and Studio Agate.
*
* Licensed with MIT
*/
namespace Admin\DTO;
interface EasyAdminDTOInterface
{
/**
* @return static
*/
public static function createFromEntity(object $entity);
/**
* @return static
*/
public static function createEmpty();
}
<?php
declare(strict_types=1);
/*
* (c) Alexandre Rock Ancelet <pierstoval@gmail.com> and Studio Agate.
*
* Licensed with MIT
*/
namespace App\Entity;
use App\DTO\PostAdminDTO;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
/**
* @ORM\Table(name="posts")
* @ORM\Entity()
*/
class Post
{
/**
* @var int
*
* @ORM\Column(name="id", type="string", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="UUID")
*/
protected $id;
/**
* @var string
*
* @ORM\Column()
*/
protected $title;
/**
* @var string
*
* @ORM\Column()
*/
protected $content;
public static function fromAdmin(PostAdminDTO $dto): self
{
$self = new self();
$self->title = $dto->getTitle();
$self->content = $dto->getContent();
\assert('' !== $self->title);
return $self;
}
public function updateFromAdmin(PostAdminDTO $dto): void
{
$this->title = $dto->getTitle();
$this->content = $dto->getContent();
\assert('' !== $this->title);
}
public function __toString()
{
return $this->title;
}
private function __construct()
{
$this->id = (string) Uuid::uuid4();
}
public function getId(): string
{
return $this->id;
}
public function getTitle(): string
{
return $this->title;
}
public function getContent(): string
{
return (string) $this->content;
}
}
<?php
declare(strict_types=1);
/*
* (c) Alexandre Rock Ancelet <pierstoval@gmail.com> and Studio Agate.
*
* Licensed with MIT
*/
namespace App\DTO;
use Admin\DTO\EasyAdminDTOInterface;
use App\Entity\Post;
use Symfony\Component\Validator\Constraints as Assert;
class PostAdminDTO implements EasyAdminDTOInterface
{
/**
* @var string
*
* @Assert\NotBlank
*/
private $title = '';
/**
* @var string
*/
private $content = '';
public static function createFromEntity(object $entity): self
{
return self::fromPost($entity);
}
public static function createEmpty(): self
{
return new self();
}
private static function fromPost(Post $Post): self
{
$self = new self();
$self->title = $Post->getTitle();
$self->content = $Post->getContent();
return $self;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(?string $title): void
{
$this->title = (string) $title;
}
public function getContent(): string
{
return $this->content;
}
public function setContent(?string $content): void
{
$this->content = (string) $content;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment