Skip to content

Instantly share code, notes, and snippets.

@fesor
Last active August 2, 2022 11:32
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fesor/8c8632099530fa730181 to your computer and use it in GitHub Desktop.
Save fesor/8c8632099530fa730181 to your computer and use it in GitHub Desktop.
DTO example
<?php
namespace SomeApp\Application\DTO;
use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class AbstractRequest
{
public function __construct(array $data)
{
$options = new OptionsResolver();
$this->configureOptions($options);
$this->map($options);
}
abstract protected function configureOptions(OptionsResolver $options);
protected function map(OptionsResolver $options)
{
// just plain mapping, all other things
// like snake case to camel case convertion or keys aliases resolving
// should be done in controller
// values normalization could be done via option resolver's normalizers
$options->resolve($data);
foreach ($options->getDefinedOptions() as $prop) {
$this->$prop = $options[$prop];
}
}
}
<?php
namespace SomeApp\Application\DTO\Something;
use Symfony\Component\OptionsResolver\OptionsResolver;
use SomeApp\Infrastructure\Security\UserSession;
class DoSomethingRequest extends AbstractRequest
{
protected $userSession;
protected $foo;
protected $fooBar;
/**
* @param UserSession $userSession - id of user
* @param array $data - array of options to be resolved
*/
public function __construct(UserSession $userSession, array $data)
{
// this is only for example,
// I usually have UserSession object wich implements UserInterface
// UserSession isn't doctrine entity, it just data container which hydrates from JWT token
// It contains user Id, some other information (isAdmin flag for example) and so on.
$this->userSession = $userSession;
parent::__contruct($data);
}
protected function configureOptions(OptionsResolver $options)
{
$options
->setRequired(['foo', 'bar'])
->setAllowedTypes([
'foo' => 'string',
'fooBar' => ['int', 'string']
])
;
}
public function getUserID()
{
return $this->userSession->getUserID();
}
public function getFoo()
{
return $this->foo;
}
public function getFooBar()
{
return $this->fooBar;
}
}
<?php
class SomethingController extends Controller
{
public function createSomething(Request $request) {
// handled via symfony/serializer in middleware
// or you can map it with jms serializer or just by yourself here in controller
$requestData = $request->request->all();
// create request DTO
$requestDTO = new DoSomethingRequest($this->getUser(), $requestData);
// pass it to application layer, which will return response dto
$responseDTO = $this->get('something_maker')->doStuff($requestDTO);
return $responseDTO; // middleware will serialize it, or we can use JsonResponse and map data to array...
}
}
@sndpl
Copy link

sndpl commented Jul 12, 2016

nice solution, shouldn't the $this->map($options); in the constructor of the AbstractRequest class also pass the $data in to the map function?

@vaso123
Copy link

vaso123 commented Mar 8, 2017

I've did not check the whole code, but you can improve your abstract request class.

First, what kind of request is it? A http request? I know, this is for demonstration purpose, but in that case the AbstractSomerRequest could be more luckily.

Based on this: (http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/) the constructor of this class should be:

public function __construct(array $data, OptionResolver $options)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment