Skip to content

Instantly share code, notes, and snippets.

@Xymanek
Last active March 15, 2018 19:00
Show Gist options
  • Save Xymanek/23feec43b9faf5e0b8f70c2604fb8eda to your computer and use it in GitHub Desktop.
Save Xymanek/23feec43b9faf5e0b8f70c2604fb8eda to your computer and use it in GitHub Desktop.
Sonata admin custom form action RFC
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use Sonata\AdminBundle\Admin\AdminInterface;
abstract class AbstractFormAction implements FormActionInterface, AdminAwareInterface
{
/**
* @var AdminInterface
*/
protected $admin;
public function setAdmin (AdminInterface $admin = null)
{
$this->admin = $admin;
}
}
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use Sonata\AdminBundle\Admin\AdminInterface;
interface AdminAwareInterface
{
public function setAdmin (AdminInterface $admin = null);
}
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use Sonata\AdminBundle\Form\FormMapper;
interface FormActionInterface
{
/**
* Transforms the model object (eg. entity) to form DTO
*
* @param null|object $object The model object or null if no id specified in request (eg. create action)
* @return object The DTO that will be passed to form as $data argument
*/
public function transformToForm ($object);
/**
* Transforms the DTO back to model. This method MUST return the model object even if the updates were done in-place
* This method will not be called unless \Symfony\Component\Form\FormInterface::isValid() returns true
*
* @param object $data The DTO that you returned in transformToForm()
* @return object The model that needs to be persisted to database
*/
public function transformToModel ($data);
/**
* Same as \Sonata\AdminBundle\Admin\AbstractAdmin::configureFormFields()
*
* @param FormMapper $form
* @return void
*/
public function configureFormFields (FormMapper $form);
/**
* Returns the name that will be used of the route that will be used to display this action
*
* @return string
*/
public function getRouteName ();
}
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use App\Form\Data\NewUserFormData;
use Sonata\AdminBundle\Form\FormMapper;
class NewUserAction extends AbstractFormAction
{
public function transformToForm ($object): object
{
if ($object !== null) {
throw new \InvalidArgumentException('This action is intended only for creation of new entities');
}
return new NewUserFormData();
}
public function transformToModel ($data): object
{
if (!$data instanceof NewUserFormData) {
throw new \TypeError();
}
return $data->toUser();
}
public function configureFormFields (FormMapper $form): void
{
$form
->add('username')
->add('password');
}
public function getRouteName (): string
{
return 'custom_create';
}
}
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use App\Form\Data\NewUserFormData;
use AutoMapperPlus\AutoMapperInterface;
use Sonata\AdminBundle\Form\FormMapper;
class NewUserActionWithDependency extends AbstractFormAction
{
/**
* @var AutoMapperInterface
*/
private $autoMapper;
public function __construct (AutoMapperInterface $autoMapper)
{
$this->autoMapper = $autoMapper;
}
public function transformToForm ($object): object
{
if ($object !== null) {
throw new \InvalidArgumentException('This action is intended only for creation of new entities');
}
return new NewUserFormData();
}
public function transformToModel ($data): object
{
if (!$data instanceof NewUserFormData) {
throw new \TypeError();
}
return $this->autoMapper->mapToObject($data, $this->admin->getSubject());
}
public function configureFormFields (FormMapper $form): void
{
$form
->add('username')
->add('password');
}
public function getRouteName (): string
{
return 'custom_create';
}
}
<?php
declare(strict_types=1);
namespace App\Form\Data;
use App\Entity\User;
use Symfony\Component\Validator\Constraints as Assert;
class NewUserFormData
{
/**
* @var string|null
*
* @Assert\NotNull()
* @Assert\NotBlank()
* @Assert\Length(min=3)
*/
private $username;
/**
* @var string|null
*
* @Assert\NotBlank()
*/
private $password;
public function toUser () : User
{
if ($this->username === null) {
throw new \DomainException('Cannot transform to user when no username set');
}
$user = new User($this->username);
if ($this->password !== null) {
// This is EXTREMELY STUPID from security perspective and is done only for demonstration purposes
// Do not try at home
$user->changePasswordHash(md5($this->password));
}
return $user;
}
public function getUsername (): ?string
{
return $this->username;
}
public function setUsername (?string $username): void
{
$this->username = $username;
}
public function getPassword (): ?string
{
return $this->password;
}
public function setPassword (?string $password): void
{
$this->password = $password;
}
}
services:
App\Admin\FormAction\NewUserAction:
autowire: true
autoconfigure: true
tags:
- { name: sonata.admin.form_action, admin: App\Admin\UserAdmin } # admin's service, can be multiple tags/admins
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class User
{
/**
* @var int|null
*
* @ORM\Id()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(unique=true)
*/
private $username;
/**
* @var string|null
*
* @ORM\Column(name="password", nullable=true)
*/
private $passwordHash;
public function __construct (string $username)
{
$this->username = $username;
}
public function getId (): ?int
{
return $this->id;
}
public function getUsername (): string
{
return $this->username;
}
public function getPasswordHash (): ?string
{
return $this->passwordHash;
}
public function changePasswordHash (?string $passwordHash): void
{
$this->passwordHash = $passwordHash;
}
}
<?php
declare(strict_types=1);
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Route\RouteCollection;
class UserAdmin extends AbstractAdmin
{
protected function configureRoutes (RouteCollection $collection)
{
$collection
->remove('create')
->add('custom_create', '/create');
$collection->add('password_change', $this->getRouterIdParameter() . '/password-change');
}
}
<?php
declare(strict_types=1);
namespace App\Admin\FormAction;
use App\Entity\User;
use App\Form\Data\UserPasswordChangeFormData;
use Sonata\AdminBundle\Form\FormMapper;
class UserPasswordChangeAction extends AbstractFormAction
{
public function transformToForm ($object): object
{
if (!$object instanceof User) {
throw new \TypeError();
}
return new UserPasswordChangeFormData($object);
}
public function transformToModel ($data): object
{
if (!$data instanceof UserPasswordChangeFormData) {
throw new \TypeError();
}
$data->applyChanges();
return $data->getUser();
}
public function configureFormFields (FormMapper $form): void
{
$form
->add('password');
}
public function getRouteName (): string
{
return 'password_change';
}
}
<?php
declare(strict_types=1);
namespace App\Form\Data;
use App\Entity\User;
class UserPasswordChangeFormData
{
/**
* @var User
*/
private $user;
/**
* @var string|null
*
* @Assert\NotBlank()
*/
private $password;
public function __construct (User $user)
{
$this->user = $user;
}
public function applyChanges (): void
{
if ($this->password !== null) {
// This is EXTREMELY STUPID from security perspective and is done only for demonstration purposes
// Do not try at home
$this->user->changePasswordHash(md5($this->password));
} else {
$this->user->changePasswordHash(null);
}
}
public function getUser (): User
{
return $this->user;
}
public function getPassword (): ?string
{
return $this->password;
}
public function setPassword (?string $password): void
{
$this->password = $password;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment