So how do you deal with littering your model with these methods? I like my models to be concise and have a clear overview of their relationships. Do you just stuff these methods into traits instead of having them in the services you have before?
Personally, I solved this problem this way. I leave only relationships in the model, only what is related to the database. I wrap the model itself in my own class, and use the methods already on it. For example, I hide the `User` model behind the `UserEntity` class, and call methods like `save` or `authorize` already on `UserEntity`
Just thinking about this, how would you work printing or emailing a list? Say you have a statement that could be emailed or printed off. Email::($statement, $recipients); Print::($statement); or $statement->email($recipients); $statement->print(); So often I get caught running this sort of logic in the controller because of the view data needing passed through. $statement = $statement->getDetails($dateRange, ....); if ($format == 'print') { // build print code } elseif ($format == 'email') { // build email code }
i wonder how you would turn a service into this.. for example when I create a product, i might be adding prices (separate table), images, product variants.. and so putting it all into MODEL product doesnt feel great?
I often create "Form Objects" for this sort of thing, as typically all of that data is coming as a single form submission from the user and needs to be split out to multiple models on the backend. So maybe something like (new NewProductForm($request))->save().
Thank you very much. I like your talk very much and it makes me think about my own code. Definitely going to think more about my code in this way. What I'd do differently: I would directly use the LicenenseCode::generate() method instead of calling an invokable class in the LicenseCode class... this is where I would simplify it one more step. Besides that, brilliant talk!
Thanks! The only issue with just using that method directly is that you couldn't bind a different implementation to the container for tests, so you are back to the exact same scenario as if you had just used `str_random(32)` directly.
The fundamental problem, in my opinion, is conflating real-world objects with software "objects." This is why I moved to a more functional approach, where data and data flows are more central to the architecture than objects sending each other messages. Also, stateful objects are a nightmare and OOP encourages state mutation. If you don't think state is hard to manage, think about why frameworks like React came about and also the easiest way to fix systems is to restart them.
As an example, look at how Elixir's core modules are organized. You have domains with functions basically, so you don't need to worry about OOP gymnastics. For the split example in the beginning of the talk, Elixir has `String.split("foo bar")` which just means, for the domain of Strings there's a function split which operates a string data. This is inline with affordances, but expressed in a much simpler way, IMO. For plants, Plant.water(%PlantStruct{})` .
I think why Adam is taking heat is when he's making jokes about the "Service" classes. I think the take away from this talk was to promote encapsulation. "Service classes" are still necessary to perform many of the decisions about what your app should be doing when there is no active Actor. Personally, I don't call those classes "Service" classes any longer, but rather Tasks. Where now a service dictates external activity.
In my opinion there is a better way to design the Gardener + Plant relationship ( gist.github.com/esnunes/f0e16b83511355b4cc7a89b4ed9f286a ). IHMO the methods of a model should be responsible only to change or retrieve the model state. Another class should be created to implement the irrigation feature. I also disagree with the idea of changing model state + dispatching a job in a model's method, it compromises the Single Responsibility Principle. I totally discourage people to directly access Laravel's container as well as using stateful Facades (Queue is going to push the job to a Redis/SQS/etc, it has a side-effect), they end up hiding dependencies. You should be able to write a test to a method without knowing its internals, the knowledge about the input, output and business logic should be enough, that's clean code. Last but not least, if you are going to create one method for each action that is related to the model, you are going to end up with god objects for every model that plays in important / central role in your application.
I knew I'd regret that comment. But fair enough - here's the justification of my point of view. 1. I agree that creating a gardener object for the purpose of watering a plant seems unfortunate but the solution proposed by Adam makes the design rather worse. A plant should have no notion of performing the process of watering. A plant needs watering but it cannot water itself. 2. Talking about promoting encapsulation in one second, and in the next acting on a class through public properties is hypocrisy (acting itself isn't a violation but having them public in the first place is.) 3. Talking about violating basic design principles with a funny voice doesn't make things any better - it's pathetic. 4. Again, I agree that a PurchaseFullfillerService class with a purchase method is ugly but the way Adam reverses the dependencies there makes things worse. Ironically, the next talk on this playlist is from Bob Martin (also from Laracon!,) the author of Clean Architecture (and few others,) where he explains in detail practices, patterns, and principles, witch Adam so lightly violates in his talk. 5. If you need a function, why not just create a function instead of a class with an invoke method? Adam's argument for easier namespacing is incorrect - you can namespace functions exactly like you would classes. 6. Another hypocrisy in this talk is how Adam talks about conquering god objects while having explicit calls to a service locator in his code - the biggest god object of them all. Not to mention that incorrectly reversing dependencies is exactly how you would end up with god objects in the first place. I'm not trying to convince anyone here, just felt responsible for justifying my criticism. However, I do suggest to everyone to try looking beyond laravel (and maybe even php,) and be more critical about the things you are told from a stage.
Should an array have the notion of reversing? A string the notion of splitting? A date the notion of formatting? This is OO 101 man, behavior belongs with the data it acts on. An object having a method doesn't mean we are saying that "a plant can water itself", we're saying "a plant can be watered". Here's another article for you to read: verraes.net/2014/09/objects-as-contracts-for-behaviour/
If I may, I see this comment a lot: "…it cannot X itself” This is a misinterpretation of OOP. $object->action() does *not* mean that the object is performing the action. It means that an action is being performed where the object is available to it as one of the parameters (in this case, implicitly $this). This is why Adam says methods are affordances and not abilities. Also, this statement violates the message-passing intention of OOP. It implies that methods on an object can only access or modify internal state OR act on state of another object. In addition to breaking encapsulation, this notion requires the creation of do-er objects to perform any action in an application. Do-er objects should only be created when either your domain requires them or when it can actively produce cleaner, more manageable code. And in such cases methods should be added to objects that delegate to the do-er objects. They're meant to hide complexity by grouping related code. Not introduce it.
There were couple things done badly in this talk: - Author is trying to squeeze too many topics in really short time just to show off - Author is encouraging developers to depend on globally available state of framework comparing it with globally available libraries in PHP language. Why this is bad comparison? Mostly because frameworks code is changing more often than build-in php libraries which are proven to be more stable, additionally those libraries are stateless and facades in laravel are not. Couple more things as well but I am not getting paid here to fix the world...
One of the points of the author was that built-in php libraries are more stateful than you think: 12:10 But in any case I myself would want to avoid using facades in random classes or models when possible, and I don't agree with the style of which classes to put the methods on to accept which arguments; so I don't really agree in the things presented in the talk.
6 лет назад+10
This explains why Laravel users always make terrible code. You get taught it.
Adam Wathan , You are a Genius!!!
One of the best talks I have ever watched about OOP. Thank you a lot Adam!
Great talk Adam. You are generating fantastic content. You are a real gift to this world 🔥
Thank you Adam. This was very insightful
Adam, I loved this talk. It's entertaining and applicable no matter what language you use.
What a brilliant video. Thanks
So how do you deal with littering your model with these methods? I like my models to be concise and have a clear overview of their relationships. Do you just stuff these methods into traits instead of having them in the services you have before?
Personally, I solved this problem this way. I leave only relationships in the model, only what is related to the database. I wrap the model itself in my own class, and use the methods already on it. For example, I hide the `User` model behind the `UserEntity` class, and call methods like `save` or `authorize` already on `UserEntity`
Thanks for uploading your talk Adam. I've waited some time to watch it again!
nice talk!. I believe it is basically related to "Tell don't ask" and to the "anemic" models problem
Thanks for sharing.
Ok, looks like I've got some refactoring to do. WELL, THANK YOU, ADAM.
Watching this while coding and ending a class name with Strategy.
thank you adam for the idea.
Wow. Definetely gonna use this technique. Thanks, Adam
Absolutely loved your talk.. that artisan command use... I really do it with controllers command though. 😅
Fantastic job Adam!
Just thinking about this, how would you work printing or emailing a list?
Say you have a statement that could be emailed or printed off.
Email::($statement, $recipients);
Print::($statement);
or
$statement->email($recipients);
$statement->print();
So often I get caught running this sort of logic in the controller because of the view data needing passed through.
$statement = $statement->getDetails($dateRange, ....);
if ($format == 'print') {
// build print code
} elseif ($format == 'email') {
// build email code
}
🖤🖤🖤
i wonder how you would turn a service into this.. for example when I create a product, i might be adding prices (separate table), images, product variants.. and so putting it all into MODEL product doesnt feel great?
I often create "Form Objects" for this sort of thing, as typically all of that data is coming as a single form submission from the user and needs to be split out to multiple models on the backend. So maybe something like (new NewProductForm($request))->save().
thanks for the quick reply :) that does make sense..
Your talks make me smile and absorb info, wannabe model!
Thank you very much. I like your talk very much and it makes me think about my own code. Definitely going to think more about my code in this way. What I'd do differently: I would directly use the LicenenseCode::generate() method instead of calling an invokable class in the LicenseCode class... this is where I would simplify it one more step. Besides that, brilliant talk!
Thanks! The only issue with just using that method directly is that you couldn't bind a different implementation to the container for tests, so you are back to the exact same scenario as if you had just used `str_random(32)` directly.
The fundamental problem, in my opinion, is conflating real-world objects with software "objects." This is why I moved to a more functional approach, where data and data flows are more central to the architecture than objects sending each other messages. Also, stateful objects are a nightmare and OOP encourages state mutation. If you don't think state is hard to manage, think about why frameworks like React came about and also the easiest way to fix systems is to restart them.
As an example, look at how Elixir's core modules are organized. You have domains with functions basically, so you don't need to worry about OOP gymnastics. For the split example in the beginning of the talk, Elixir has `String.split("foo bar")` which just means, for the domain of Strings there's a function split which operates a string data. This is inline with affordances, but expressed in a much simpler way, IMO. For plants, Plant.water(%PlantStruct{})` .
Great work adam.:+1 For me I prefer to dispatch events using the event helper function event(new EventName)
Thanks for share!!
Nice presentation, What do you use to prepare your deck?
Just keynote!
is this code example in github?
good insights!
why dhh --> cause its a great gif LOL
I think why Adam is taking heat is when he's making jokes about the "Service" classes. I think the take away from this talk was to promote encapsulation. "Service classes" are still necessary to perform many of the decisions about what your app should be doing when there is no active Actor.
Personally, I don't call those classes "Service" classes any longer, but rather Tasks. Where now a service dictates external activity.
Can you give an example of this?
Sweet!
Wonderful talk!
In my opinion there is a better way to design the Gardener + Plant relationship ( gist.github.com/esnunes/f0e16b83511355b4cc7a89b4ed9f286a ). IHMO the methods of a model should be responsible only to change or retrieve the model state. Another class should be created to implement the irrigation feature.
I also disagree with the idea of changing model state + dispatching a job in a model's method, it compromises the Single Responsibility Principle.
I totally discourage people to directly access Laravel's container as well as using stateful Facades (Queue is going to push the job to a Redis/SQS/etc, it has a side-effect), they end up hiding dependencies. You should be able to write a test to a method without knowing its internals, the knowledge about the input, output and business logic should be enough, that's clean code.
Last but not least, if you are going to create one method for each action that is related to the model, you are going to end up with god objects for every model that plays in important / central role in your application.
I dunno. I feel like you're un-factoring. Why do people not like objects?
I thought you were better than this, Adam. These are some terrible ideas delivered in a nice way - truly terrifying.
How is this terrible? Adam spent the time to explain his POV, least you can do is justify yours.
I knew I'd regret that comment. But fair enough - here's the justification of my point of view.
1. I agree that creating a gardener object for the purpose of watering a plant seems unfortunate but the solution proposed by Adam makes the design rather worse. A plant should have no notion of performing the process of watering. A plant needs watering but it cannot water itself.
2. Talking about promoting encapsulation in one second, and in the next acting on a class through public properties is hypocrisy (acting itself isn't a violation but having them public in the first place is.)
3. Talking about violating basic design principles with a funny voice doesn't make things any better - it's pathetic.
4. Again, I agree that a PurchaseFullfillerService class with a purchase method is ugly but the way Adam reverses the dependencies there makes things worse. Ironically, the next talk on this playlist is from Bob Martin (also from Laracon!,) the author of Clean Architecture (and few others,) where he explains in detail practices, patterns, and principles, witch Adam so lightly violates in his talk.
5. If you need a function, why not just create a function instead of a class with an invoke method? Adam's argument for easier namespacing is incorrect - you can namespace functions exactly like you would classes.
6. Another hypocrisy in this talk is how Adam talks about conquering god objects while having explicit calls to a service locator in his code - the biggest god object of them all. Not to mention that incorrectly reversing dependencies is exactly how you would end up with god objects in the first place.
I'm not trying to convince anyone here, just felt responsible for justifying my criticism. However, I do suggest to everyone to try looking beyond laravel (and maybe even php,) and be more critical about the things you are told from a stage.
In case you're seeing a different playlist than I am, I meant this in point 4: ruclips.net/video/NeXQEJNWO5w/видео.html
Should an array have the notion of reversing? A string the notion of splitting? A date the notion of formatting? This is OO 101 man, behavior belongs with the data it acts on. An object having a method doesn't mean we are saying that "a plant can water itself", we're saying "a plant can be watered".
Here's another article for you to read: verraes.net/2014/09/objects-as-contracts-for-behaviour/
If I may, I see this comment a lot: "…it cannot X itself”
This is a misinterpretation of OOP. $object->action() does *not* mean that the object is performing the action. It means that an action is being performed where the object is available to it as one of the parameters (in this case, implicitly $this). This is why Adam says methods are affordances and not abilities.
Also, this statement violates the message-passing intention of OOP. It implies that methods on an object can only access or modify internal state OR act on state of another object. In addition to breaking encapsulation, this notion requires the creation of do-er objects to perform any action in an application. Do-er objects should only be created when either your domain requires them or when it can actively produce cleaner, more manageable code. And in such cases methods should be added to objects that delegate to the do-er objects. They're meant to hide complexity by grouping related code. Not introduce it.
There were couple things done badly in this talk:
- Author is trying to squeeze too many topics in really short time just to show off
- Author is encouraging developers to depend on globally available state of framework comparing it with globally available libraries in PHP language. Why this is bad comparison? Mostly because frameworks code is changing more often than build-in php libraries which are proven to be more stable, additionally those libraries are stateless and facades in laravel are not.
Couple more things as well but I am not getting paid here to fix the world...
One of the points of the author was that built-in php libraries are more stateful than you think: 12:10
But in any case I myself would want to avoid using facades in random classes or models when possible, and I don't agree with the style of which classes to put the methods on to accept which arguments; so I don't really agree in the things presented in the talk.
This explains why Laravel users always make terrible code. You get taught it.
How is it terrible? Explain
Yes, please explain. I would be truly interested in downsides of the approach when it is used as discussed by Adam.
How? Please enlighen us.
Good to see it’s not just Stack Overflow you’re conceited and have a holier-than-thou attitude.
@Mārtiņš Tereško - please provide your counter-arguments then instead of saying it's all wrong.