-
-
Notifications
You must be signed in to change notification settings - Fork 417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DTO] Add new make:dto command #303
base: main
Are you sure you want to change the base?
Conversation
From my side, this would be ready for merging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @ckrack!
Awesome start - a lot of complex stuff here. But, doing cool things like this is part of what makes this bundle fun. I've added a first round of changes.
Cheers!
I had a chat with @Pierstoval about his proposal for DTOs in EasyAdmin today and he raised the following issues:
To achieve this, he uses mutators on the entity ( I agree to this and we should try to enforce best practices as much as possible - while making generated DTOs usable without modifications. An approach could be: Simple example, given an Entity // src/Entity/Foobar.php
class Foobar {
private $id;
private $acme;
// ... getters
} Running the maker: php bin/console make:dto Foobar Foobar
Add helper extract/fill methods? (yes/no) [yes]:
> yes
Generate getters/setters? (yes/no) [no]:
> no Would generate the following Dto: class FoobarData {
public $acme;
public function __construct(?Foobar $foobar = null)
{
if (null !== $foobar) {
$this->acme = $foobar->getAcme();
}
}
} And modify the Entity with this // src/Entity/Foobar.php
class Foobar {
// ...
public function updateFromFoobar(Foobar $foobar)
{
$this->acme = $foobar->acme;
}
} WDYT? |
I'm totally ok with this proposal 👍 Just as a side-note, this example: class FoobarData {
public $acme;
public function __construct(?Foobar $foobar = null)
{
if (null !== $foobar) {
$this->acme = $foobar->getAcme();
}
}
} Should be replaced IMO with this: class FoobarData {
public $acme;
public function __construct(Foobar $foobar = null)
{
if ($foobar) {
$this->acme = $foobar->getAcme();
}
}
} I'd even go further and only allow static constructors such as this: class FoobarData {
public $acme;
public static function createEmpty(): self
{
return new self();
}
public static function fromFoobar(Foobar $foobar): self
{
$dto = new self();
$dto->acme = $foobar->getAcme();
return $dto;
}
} |
Yeah named static constructors like you suggest and an scalar conctructor like this:
Maybe Entity Class metadata could be used to derive the correct typehints, too |
Technically, a constructor can be called twice with |
Just my 2 cents: I usually add the most critical properties to the DTO's constructor. It reduces the number of lines, e.g, using
It is especial convenient in tests where a lot of DTOs instantiating is needed and they are not connected to the entities
|
@karser Why public properties? |
Indeed @mablae, getters/setters are more flexible. Probably, it'd be sensible for them to be configurable in make:dto - either use getters/setters or public properties.
|
Injecting values in the constructor is a nice idea @karser but I see here a big drawback for the Form component: it forces you to use a DataMapper. The Form component highly uses getters and setters, and in the end our DTOs are just here to transport data from a layer to another, so having getters and setters is mostly here to guarantee the type & provide some autocompletion features. And by the way, with a proper IDE, creating a constructor for a property is done in a few clicks/keyboard shortcuts, as well as getters and setters, so not having a constructor by default does not sound very harmful to me. |
@Pierstoval It's only for some properties, not for all of them, because having more than 4-5 arguments in constructor makes it useless if you want to specify only last argument, e.g rather than btw Serializer supports denormalization from the constructor |
Didn't know that, thanks 👍! For the rest, I still think public properties are kinda useless, especially because we don't have strict type yet (PHP 7.4 only, so not for now). Do as you wish, after all people can do whatever they want with their DTOs, especially because the Form component can use them, but I don't think making this available as a direct feature is a good idea. The Maker sounds "magic" to newcomers, and they rely on the generated code as if it was a pure Symfony recommendation, and public properties has never been a really nice recommendation. |
The blocker with this PR is that there's not just one way of doing a DTO. I see 3 main options - this is not a full list of possibilities, but trying to simplify into a few styles:?
(1) is so easy, and not something we should probably promote too widely (it's certainly fine in many cases - and devs that understand why vs when not will have no problem creating this very simple class). For the other 2, I don't like having many ways to do things, but this seems like a special case to me. Could we add this question?
That would add 1 question that should create DTO's that would cover most situations. Btw @ckrack this PR has been delayed by this complication and my delayed review. Are you still interested and available to work on it? Cheers! |
The 3 solutions could be interesting, and solution 1 is almost identical to solution 3 with PHP 7.4 coming along, so we could instead propose the 3 ways of doing it, like:
|
That makes sense to me - allow the user to choose from these 3 different "styles" of DTO's. I always like less options, but these all seem like legitimate strategies, where the "best" is subjective and dependent on the person/situation. Now, who wants to help finish this? :) |
@ckrack what do you need to finish this PR exactly? 😄 |
@weaverryan What do you think about this option?
|
Hmm, maybe. It's really an extension to 3 (Immutable, with getters only) - it could be added now, later, or never :p. We should get at least a few options done initially - i.e. if there are other variants, those might be nice to add, but let's not make them blockers. |
Thanks for your input. |
Is it possible to have a fork of just the MakerBundle (https://github.com/symfony/maker-bundle/)? I'd be happy to test and give feedback, I need a DTO for a complex entity, would love to give approach this a spin. I have no opinion on where the files should go or how ->fill() should be called or what should be in the constructor. Whatever you implement, I'll try. |
@tacman, you can use the fork of this PR and configure it on your |
Is this PR still in Review? What needs to be done? |
This PR needs a champion to carry it to the finish. @ckrack has done a great job getting it started, but he may or may not (he can correct me) have as much time as he'd like to finish it :). Anyone else can take this work and continue on a new PR if you have some time and willingness! |
Check for existance of methods. Print warning and comment code out, adding @todo comments.
The validator is used to get all validations for a property. This is counted and compared against the validations from annotations in the entity class.
Refactor Maker to allow independency of validator. Remove default use statement from template. Make validator optional in constructor. Add tests without validator.
Keep the default (false). Reverse question to make it more natural language wise. Fix conditions to keep code simple to read.
The requirement is no longer needed, the package requires 7.1.3 itself.
Change MakeDto to allow a choice between three styles of a DTO: - Mutable with getters & setters - Mutable with public properties - Immutable with getters Use individual templates for the styles. Add mutator method to Entity when confirmed by the user. Add constructor creation to DTOClassSourceManipulator. Refactor tests to adhere to the changes. Rename tests in fixtures.
This should be ready for another review @weaverryan. I've added the three style options.
When using 1 and 2, you will get a constructor that accepts the Entity and uses the getters, setters or public properties to setup the Dto with values. When using the immutable style, you'll get a constructor with all properties (ordered by mandatory -> optional with defaults -> optional) and a named constructor The mutator was moved to the Entity
This will create |
Little up/ping for this 2 years old PR just waiting to be reviewed @weaverryan 😄 |
PR interesting :) |
Is there anything happening with this? Seems pretty cool, but seems to have gotten stuck right before the finish line. |
This command is added as a supplement to make:form.
To prevent problems with invalid state of entities when used in forms,
data transfer objects are a solution.
This command generates these from existing entities.
It can generate getters/setters and helper methods to fill and extract data from/to entities.
Closes #162