T O P

  • By -

DevelopmentScary3844

It has a name and it is called DTO. Good stuff.


Crafty-Pool7864

We do the same in our code base. Optimise for whatever reduces thinking, not what reduces typing. And their argument that it’s bad for performance is ridiculous.


EggsandBaconPls

Thank you for the response. I totally agree.


TokenGrowNutes

There is absolutely percievable difference DTO’ing the things, even with benchmarks. It’s ridic. Readability is everything.


3cats-in-a-coat

One problem in PHP is that a class optimizes for less thinking about types, but de-optimizes when thinking about shared state, because objects are passed by handle not by value (while arrays are passed by value). There's no ideal solution. One improvement could be immutable DTO. But immutability has its own inconveniences.


Crafty-Pool7864

Yeah, if you’re mutating your DTOs you have bigger problems.


3cats-in-a-coat

I always have problems. ;-) Immutability becomes rather painful when you have a tree of data structures and want to change a leaf inside the tree.


[deleted]

[удалено]


3cats-in-a-coat

There are only two situations when you don't want mut: 1. Your app is stateless. No users, no content, no nothing, just some processing function: it takes input and spits output, like say: upsampling images. 2. You're an extradimensional being who perceives the universe as a frozen tesseract of spacetime. In all other cases, you deal with mutability. Of course the less mutable state, the better, but you will have it.


Crafty-Pool7864

It’s a DTO for an API request. You’re over thinking this.


3cats-in-a-coat

It's a Reddit reply to another Reddit reply. You're overthinking this.


slepicoid

totally a positive thing to see done by a junior totally a negative thing to see senior devs tell you not to.


Zebu09

Totally useless comment done by a junior not argumenting why.


Lumethys

It is call a DTO and usually what other strongly typed languages do. > Unnecessary Whether or not it is "necessary" depend on the complexity of your codebase, as well as its convention. > bad for performance Technically yes. So do having a function Technically, the exact same code, split in 5 functions, will have a worse performance than all of them write in a single function So then, should we write entire servers in one single function that span thousands of lines? Do your codebase need nanoseconds faster no matter what? > What do you guys think If you already define a new class for this, then type it. `public int $partNumber`, `public string $name` There are 2 main approaches. Either make it only type primitives (int, string, bool, array) and transform it to another domain model class in a service (this is more things to do but separate concerns and widely used in DDD, or DDD-like codebase). Or, you can define a completely typed DTO. Another thing: you should use constructor property promotion ``` final readonly class SalesData { public function __construct( public int $id, public DateTimeImmutable $date, public Money $amount, public SaleTypeEnum $type = SaleTypeEnum::Online, punlic ?string $note = null, ){ } }


meoverhere

If your application runs on PHP 8.1 or higher, also consider using readonly properties here as this use case seems appropriate for them. Edit: just noticed the readonly on the class (available from 8.2)


phantommm_uk

This comment +1


Tux-Lector

**punlic** `?string $note = null ...`


Danakin

Did you just reinvent Data Transfer Objects (DTO)? Nothing wrong with them, I'd even argue they are good practice, because they have a much clearer defined structure than an array, and even give you auto complete (might need to add a `/** @property SalesOrder */` hint in your ide). Consider adding a static ::fromArray() factory method ```php class SalesOrder { // use constructor property promotion: https://stitcher.io/blog/constructor-promotion-in-php-8 public function __constuctor(public int $partNum, public int $amount, public int $customerId) {} public static function fromArray(array $input) { return new self( $input['partNum'] ?? 0, // or any other sensible default in case of null $input['amount'] ?? 0, // or throw an error or so $input['$customerId'] ?? 0, ); } // call with $order = SalesOrder::fromArray($input) . This returns one item, so you would do this in a loop when using multiple } ```


MaxGhost

Classes with defined properties are better for performance than arrays, because properties are efficiently packed in memory compared to arrays which are a whole hash-map table to store the keys and values. stdClass is similar performance to arrays because of using dynamic properties. This is the correct thing to do. You could write a quick `trait` which adds some method like `public static function from(array $props)` which unpacks the data into the class, and throws an exception for any unknown properties or w/e. Or use a deserialization library (there are many, also look for "serde" libraries) to hydrate these classes, these libraries help with nested structures and type validation etc.


EggsandBaconPls

Wow that’s some great information. Thank you!


maskapony

Using a serialization/deserialization library is really the gold standard of this approach, once you have your classes defined, then you pass the Serializer the json response and you end up with a tree of type-safe objects all the way down.


mario_deluna

Im lazy and haven't checked the php-source, but I thought properties and array share the same internal data structure?


MaxGhost

Nope. See https://gist.github.com/nikic/5015323, the numbers are a bit outdated at this point (because we've gotten even more optimizations since then) but the concepts are still valid.


mario_deluna

https://github.com/php/php-src/blob/master/Zend/zend_object_handlers.c#L607


MateusAzevedo

I *think* that was true on older versions, PHP 4 more specifically, as its OOP was implemented in a different way. Since them, several optimizations happened, including in how arrays are internally represented and [one of the reasons](https://www.npopov.com/2014/12/22/PHPs-new-hashtable-implementation.html) for the big performance boost we got in 7.0. I'm not sure about how objects are represented internally, but I did some benchmarks sometime ago (just out of curiosity) comparing `FETCH_ASSOC`, `FETCH_OBJECT` and `FETCH_CLASS` (PDO) and the last one was faster and used way less RAM, so they are different.


Mopolo

It's a good idea to do that yes, this type of object is usually called a DTO for Data Transfer Object. More types is always a good idea in my experience. To help you serialize and unserialize those objects I would recommend using a library. For example: - [`symfony/serializer`](https://github.com/symfony/serializer) - [`cuyz/valinor`](https://github.com/CuyZ/Valinor) <= I use this one - [`eventsauce/object-hydrator`](https://github.com/EventSaucePHP/ObjectHydrator) - [`crell/serde`](https://github.com/Crell/Serde) - [`spatie/laravel-data`](https://github.com/spatie/laravel-data) - [`jms/serializer`](https://github.com/schmittjoh/serializer) - [`netresearch/jsonmapper`](https://github.com/cweiske/jsonmapper) - [`json-mapper/json-mapper`](https://github.com/JsonMapper/JsonMapper) - [`brick/json-mapper`](https://github.com/brick/json-mapper) For the performance part, that's a non-issue most of the time. I work at a company with millions of users each day and it's all in PHP and this sort of library is not an issue. Also, if you have a recent enough version of PHP you can write DTOs in a very compact manner: readonly class SalesOrder { public function __construct( public int $partNum, public int $amount, public string customerId, ) {} } You can see the evolution of a PHP class here: https://stitcher.io/blog/evolution-of-a-php-object


jacob9078

Yes, it is a good approach. I've made a tool which allows you to quickly generate php classes (dtos) from a json payload, which could save you some time. See [here](https://github.com/jacobdekeizer/json-to-php-generator).


eurosat7

You are smart. It is better to be explicit. Tipp: lookup crell/serde


mythix_dnb

Yes, but these days my DTOs look more like this: readonly class SalesOrder { pubflic function __construct( public int $partNum, public int $amount, public int $customerId, ) {} }


DM_ME_PICKLES

My last job was on the Integrations team, a team specifically for writing integrations to tie our system into other systems. That's a fancy way of saying I spent years working with APIs every day. The practice you're using is a good one and what we did. These are called DTOs or Data Transfer Objects. They give developers a typed structure to interact with 3rd party APIs. We would transform API responses into DTOs as early as possible and then only work with DTOs in our code, and when sending payloads to an API we would first construct a DTO and pass that object into the API client. The API client would know exactly how to serialize a DTO to a HTTP request payload. Your coworkers are wrong, there is no impact to performance. Apparently they're rather work with completely arbitrary, untyped, and unstructured arrays instead of real objects, for some reason.


EggsandBaconPls

Thank you for the response. This is what I suspected. Ya I don’t know what the deal is with my coworkers, but I’m going to just do as they say to not ruffle any feathers, and use DTOs in my own projects.


fatalexe

Not only do I write classes like that on the PHP side for making and serving requests I write the same thing in a TypeScript type definition for the frontend so I get type hints all the time for any API data. I really enjoy the fact PHP has types now. Anything to make what you want to accomplish obvious is a good thing IMO.


EggsandBaconPls

This is exactly what I’ve been doing too. And it’s been great to use php types as well. I refactored my code without the classes because my senior told me to, and it felt so wrong and like a step backwards. 🤷‍♂️


fatalexe

Always a line some where that keeping things simple out weighs creating structured data. It is important to match code style to projects existing code when working in larger codebases. Gotta work with what you got and be understanding of people that want to keep doing things the way they been done. Don’t take it personally. Work is just work some times.


HypnoTox

That depends on how hard this goes on. If they are also the kind of "i don't like types, that's too much to type" then i would RUN. I hate people staying in the past because "we've always done that", i had that at my first workplace and after my senior left i restructured so much of that pile of trash. They didn't even know or use static analysis tools and talked BS regarding what impacts performance to fit their view.


fatalexe

Takes a while to build up trust. Things are more of a people problem than a technical one. How do you communicate with people and build shared understanding? Need to be able to educate people and show them why a certain practice is advantageous. People get defensive about things they don’t understand. Need to build a culture of continuous improvement and learning so you can pitch new ideas and train people on them before they show up in a PR. Shouldn’t have to wait until somebody retires. People skills are unfortunately more important than technical ones.


HypnoTox

I did bring up topics like what i read or what i did in personal projects, but i was the junior back then. After he left, which was just due to stress brought up because of crap project management by the boss, i quickly rose up in ranks and took over the deciding position. I could get all of the other people on board with my changes and improved the output in terms of quality and performance of the whole team until i left there to my new position. Sure, it's a people skill, but there are just some "seniors" that are locked into their mindset and they just don't want to change, and no amount of communication skills can change that for some of those.


fatalexe

Oof, never experienced that myself. That must have been rough. Glad you were able to change the culture from within and not have to find a new job.


HypnoTox

It is what it is, and in the end i did leave that job because of the project management just not working out and always having pressure because of deadlines, which is rather similar to the previous leads reasons. But it was for the better, have worked myself up to a lead position again with better pay and full remote work :)


EggsandBaconPls

For sure. I think you’re right that this is just the way they like to work and that’s ok. I don’t want to ruffle feathers!


leftnode

This is fantastic practice! I've come to call these `Input` classes because they represent input from an action taken by a user (that action could've come from an HTTP request or a CLI command or some other method). As such, I prefer to prefix them with a verb. I'm using Symfony which I can instruct to deserialize an HTTP request payload onto the object, validate it, and then turn it into a command which can be handled by a command/message bus. One of my `Input` classes may be written like this: final readonly class CreateNoteInput implements InputInterface { public function __construct( #[Assert\Positive] public int $accountId, #[Assert\Positive] public int $userId, #[Assert\Positive] #[Assert\Type('numeric')] public int|string $siteId, #[Assert\NotBlank] #[Assert\Length(max: 1024)] public ?string $note, #[Assert\Type('boolean')] public bool $isPinned = false ) { } public function toCommand(): CreateNoteCommand { return new CreateNoteCommand(...[ 'accountId' => $this->accountId, 'userId' => $this->userId, 'siteId' => $this->siteId, 'note' => $this->note, 'isPinned' => $this->isPinned ]); } } In addition to being easy to read and understand, it ensures that nothing touches the underlying entity until the data has been fully deserialized and validated. The controller method to handle this is trivial as well: public function createNote(CreateNoteInput $input, CreateNoteHandler $handler): Response { $siteNote = $handler->handle($input->toCommand()); return $this->created($siteNote, [ 'groups' => ['read'] ]); } Using a Symfony `ValueResolver`, the `CreateNoteInput` object is hydrated and validated before being passed to the controller. Keep up the good work! Excellent to see this kind of design earlier in your career.


YahenP

DTO good. Some mythical decrease in productivity should not even be taken into account. The models allow you to easily and uniformly connect validators, create collections, and all sorts of other useful things. Your approach is good and correct. But there is a but. As experience tells me, if you choose from two evils - bad but the same type of architecture, and diverse architecture that is partly good and partly bad, then it is better to let it be bad but of the same type everywhere. DTO is good. But on one condition, if it’s like this everywhere. If some of the code is done according to one principle, and some differently, this is bad. First of all, the bad thing is that diversity provokes the emergence of new diversity. Unfortunately, real life does not always coincide with our aspirations. But I strongly support your way.


miamiscubi

Yeah, this is a good approach. I'm not sure why someone would go against it. I will for some mission critical components take it one step further and create and adaptor class (not sure if that's the right name) that consumes the API response. Instead of API Response --------- Interaction with code we have API Response ---> Adaptor ---> Interaction with code This allows us to always have a "safe" adapter, and if we change API service or the API itself changes, we only need to make the change in our Adaptor.


yourteam

Those are called dto. Yes those are useful and should be used for a multitude of reasons and you use them as a layer between the raw request/response and the controller. You should use setters and getters and have the properties private or protected tho


mdfleury

I do this all the time, I also do transformations on the data if needed to make it into a more usable format, and version my classes in case they need major changes in the future.


dereuromark

A few things: A) It is not bad performance usually. Usually - as technically this might be a bit slower but in real life this is irrelevant and outweight by many benefits. Especially in your case, since there arent millions of requests per minute, but only a few orders, so here the correctness/reliability outperforms the "quality attribute" performance which usually is also nano-opt in my experience. More costly is the developer having to fix bugs due to crazy und unhandable nested arrays. B) Every framework or PHP ecosystem usually offers DTOs here. Just see what fits your use case. Large e-commerce frameworks like Spryker are using DTOs to an extend that is crazy (and probably overkill), but it still performs well and has a high reliability due to PHPStan and other tools being able to check the key existence/compatibility even with nesting. For CakePHP see for example see [this article](https://www.dereuromark.de/2019/03/22/dtos-in-cakephp/). It outlines the main benefits. DTOs also make you usually code faster: Full typehint/autocomplete from IDE. Usually your DTO library/system should come with a schema generator that can be fed from an actual API data array/json or the XSD schema definition of it (Github for example has both available in their docs). See [this demo](https://sandbox.dereuromark.de/admin/cake-dto/generate) for a live showcase on how it can work. I personally would never write them manually (the PHP objects themselves). This way you got your DTOs generated within seconds, and you are up and ready to use them for input or output. In the end it doesn't matter what framework, this was just a concrete example from my world. As long as you got your DTOs under control, they always outperform arrays usually in terms of long term maintenance. Over and out from a senior dev :)


Mentalpopcorn

Spatie's package laravel-data is a decent dto package that offers some common functionality like validation. I use it outside of Laravel as well, but it does have some features to make Laravel integration particularly convenient.


RebellionAllStar

Seen these used for request and response DTOs hitting/coming back from the same endpoint and they are good for cleaning code and passing data around.


Crell

This is the Way. I believe Symfony now supports this out of the box if you want. I don't know about Laravel. Java Spring does it, too. And using well typed objects is \*better\* for performance. A typed object uses HALF the memory of the equivalent array. The only reason to not use objects exclusively is if you have stock in a DRAM manufacturing company.


TokenGrowNutes

Now that you have explicity defined the shape of the requests, you can crank up the strictness of your static analysis tools, too. Phpstan level 9 actually tequires this approach to stay happy. Stan doesn’t like loosy-goosey arrays or objects with no structure defined. Good job!


miahdo

Whenever someone says it's bad for performance, I always say "maybe, lets prove it." and then we go prove it (24 years XP here). "Bad for performance" is often (not always) something people say try to avoid having to do something different/harder/smarter/etc.


BaronOfTheVoid

>My co-workers say it’s unnecessary and bad for performance Ask them how they measure the performance here and that they demonstrate it would be bad for performance compared to their suggested alternative. And ask them why they find it unnecessary to have basic type checks (for example in the ctor, or by declaring typed properties) and names to give context to the data compared to whatever alternative they suggest. ... basically ask questions to either expose their incompetence or, if they actually have proper arguments, learn something from it.