Learning Laravel - Observations, part 1: The service container

Posted on by Matthias Noback

With excerpts from the documentation

I have worked with symfony 1, Symfony 2+, Zend Framework 1, Zend Expressive, but never with Laravel. Until now. My partner was looking for a way to learn PHP and building web applications with it. Most of my own framework knowledge is related to Symfony, so my initial plan was to find a Symfony course for her. However, Jeffrey Way's Laracasts also came to mind, and I thought it would be an interesting learning experience for us both if Laravel would be the framework of choice in this matter. It turned out to be a good idea. My partner is making good progress, and I get to see what Laravel is all about. We have a lot of fun together, finding out how it works, or what you're supposed to be doing with it.

As a side-project, I've been reading the official Laravel documentation. Being a human with framework habits, I couldn't help but compare the Laravel approach to the Symfony approach. I've also compared some of the suggestions from the documentation with what I think are best practices for any web application, regardless the framework that you choose. Something to keep in mind when reading this article is that my approach to web application architecture is to keep the framework and other infrastructural concerns far from the code that represent my application's use cases and the domain models it contains. See also the series I wrote about this approach earlier (part 1, part 2 and part 3). I'm usually not looking for a way to develop something as quick as possible, or make development as convenient as possible. I try to find ways to protect its future against external influences, like changes in the language, the framework, a desire to switch to a different database, queueing system, etc. This will certainly color some of my observations in this and following articles, including the advice I give here on which feature to use, and which ones to ignore.

I also want to make clear that I'm absolutely not here to bash Laravel or anything. Quite the contrary actually, I think its builders have made some great decisions. They have also added things that I guess might nudge people in the wrong direction in terms of object design, but keep in mind: this is all my opinion. Of course, I try to give proper arguments, but in the end I'm not here to say: use Symfony, ditch Laravel. If anything, my message when it comes to frameworks would be: use them to your advantage, but only in the parts of your code base where it makes sense. Don't let them determine your overall design (or architecture), make sure you can swap parts of them out when you want or need to. If Laravel/Symfony/Zend/etc. suits your needs today, use it today, but prepare for the day when you don't want to use it anymore.

With all disclaimers out of the way, let's go!

The service container

Let's start with a discussion about the service container. After I wrote the article on Hand-written service containers someone pointed out to me that my solution wasn't the only one that has the advantage of being easy to refactor; the classes, interfaces, and methods that you use in Laravel service definitions are also indexable by your IDE and are therefore easy to rename, move, etc. That's totally correct. This is what a service definition looks like:

// Defining a service inside a closure:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

// Using it somewhere else:

$this->app->make('Helpspot\Api');

// Note: you can use `Api::class` instead of spelling out the full class name

However, most services don't even need all this work, because you can give the container instructions. E.g. instead of binding an interface with a closure, you can also bind it with a class name, so that, whenever an object requires an instance of the interface, it will get an instance of that class injected:

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);

Shared versus singleton services

I think it's very interesting that by default the services that you define using bind() are not shared, that is, the next time we'd call $this->app->make(), you will get a fresh instance. For Symfony, the default setting is the opposite: when you define a service, it's going to be shared. I definitely prefer the Laravel approach. For two reasons: first, services aren't objects that need reference equality; you shouldn't be worried about the exact instance of a service that you retrieve. You should only care about the fact that it is an instance of the requested class or interface. Second, creating a fresh copy for a service will prevent you from making the service stateful. Keeping state inside the service doesn't make sense, since you can't rely on that state to be there the next time you request that service. (Actually, I'm not sure if I'm projecting my ideals onto the Laravel service container now; maybe not having "singleton" services by default doesn't keep people from writing stateful services, I don't know.)

By the way, it's really cool how the first example of dependency injection in this documentation section is a UserController which needs a UserRepository. This is quite surprising to me, since for a very long time, Symfony developers have been debating whether or not to use constructor injection for controllers. I've always liked the idea, because I've always followed the rule that services should have all their dependencies injected as constructor arguments. However, with Symfony this has been problematic and the established practice has just been to grab services from the service container directly, whenever needed (but only in the controller; more about that later).

Anyway, what I think is a good rule for services is that only the ones that represent a shared resource (like a database connection) should be "singleton" services, and that all the others should be created whenever they are needed, without reusing existing instances. By the way, "singleton" service is a peculiar name here. I like it because it conveys the spirit that there is supposed to be only one instance, like with the Singleton design pattern, but since it does not require the class to implement that pattern, it's a bit confusing too. I just hope that you don't use language constructs to make a class by definition a singleton, but that you'll rely on the service container to make sure only one instance gets created.

Something else to mention in the context of "binding": if a service needs a configuration value to be provided as a string (or some other primitive-type value), you can bind this value too:

$this->app->when('HelpSpot\API')
          ->needs('$apiKey')
          ->give('secret');

This way, the service container will be able to construct the object, providing 'secret' as the constructor argument for a parameter called $apiKey. Symfony has this same option for helping the service container find the right value for a primitive-type parameter. I don't like it. One problem for me is the fact that something that was previously internal to the class - the name of a constructor parameter - is now also used in some other place. This means that where it was previously completely safe to rename the parameter, now you have to also update the service bindings. A solution for this would be to define dedicated value objects for each configuration value and make them available as "services" too:

namespace HelpSpot;

/*
 * This class can be used to create an object which represents
 * the API key previously passed to the API services as a string:
 */
final class HelpSpotApiKey
{
    private $apiKey;

    private function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }

    public static function fromString(string $apiKey): self
    {
         return new self($apiKey);
    }

    public function asString(): string
    {
        return $this->apiKey;
    }
}

final class API
{
    /*
     * We no longer rely on a string, but on a `HelpSpotApiKey` 
     * value object:
     */
    public function __construct(HelpSpotApiKey $apiKey)
    {
        // ...

        // to use the API key:
        $apiKey->asString();
    }
}

/*
 * As soon as we bind the `HelpSpotApiKey` value object, Laravel will
 * be able to instantiate the `API` service all by itself:
 */ 
$this->app->bind(HelpSpotApiKey::class, function ($app) {
    return HelpSpotApiKey::fromString('secret');
});

Note: if you feel like writing classes like HelpSpotApiKey is a lot of work; I completely agree. If you want, create a little trait that saves you from writing all that code by hand. The added benefit to me is definitely worth the exta classes.

There are some other service container configuration options that seem quite useful, e.g. the ability to bind an interface to different classes, based on which service requires it (Contextual binding), and another one that is very powerful - the ability to tag services and retrieve all services with a given tag. Symfony has this too, and I've always found it an amazing feature. The major difference here is that, instead of dealing with services during the container compilation phase (as is done in Symfony), with Laravel you can just grab the tagged services. I remember dealing with tagged services in Symfony for the first time was a real mind-bender. With Laravel it is more intuitive, for sure:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

Instantiating services

The problems for me start with the ability to use the bootstrapped application to resolve services using its make() method:

$api = $this->app->make('HelpSpot\API');

This is a power that belongs not to the user, but to the framework. The only place where make() should have to be called is inside the framework kernel, after it has found out which controller it should call, based on the available HTTP request information (e.g. the route, its parameters, the host name, etc.). At that point, the kernel will instantiate the controller. The controller will have all its dependencies injected as controller arguments, and any of those services will have its dependencies injected as constructor arguments too. That way, calling the controller will indirectly produce a number of calls to the service's dependencies, but along the way, no service will have to resolve more dependencies on-the-spot.

Dynamically resolving dependencies has always been considered a design problem anyway, for many reasons. It hides the actual dependencies, making it difficult to get a clear picture of what those dependencies are, and consequently, of what the job of a service is. Being able to look at a constructor and see what its dependencies are is a great way of getting to know a service. I wouldn't want to read the code in detail to find out what a service does. The public API of a service (the methods, including the constructor, their parameter types, and their return values) should tell me all I need to know.

For this reason, I'm not particularly happy about the suggestion in the documentation that "If you are in a location of your code that does not have access to the $app variable, you may use the global resolve helper":

$api = resolve('HelpSpot\API');

Static dependency instantiation

There's another problem with using static functions for resolving services (and some framework use them for resolving configuration too). They introduce framework coupling. It's very likely that in just one or two years, things will be completely different, and we no longer should (or can't) use this resolve() function anymore. Maybe we want to switch to a different framework, maybe the team agrees that they don't want to resolve dependencies on the spot. Then you're stuck with them.

I've recently had this experience with Zend Framework 1's Zend_Registry which is used to share services and configuration across a code base. Although Zend_Registry if more like a globally available map of things, and by far not an auto-wiring service container, it's still the same pattern. Application code will have lots of calls like Zend_Registry::get('Zend_Translate') and the likes, to retrieve globally available services. When using resolve(), or even $this->app->make(), you'll end up with the problem that you're not only depending on a service, you're now also depending on the service you need for retrieving that service. And this is a really painful thing. As an example of how this becomes very impractical: if your application uses Zend_Registry::get('Zend_Translate') to retrieve the translator service, and you now want to use the Symfony service container in your project, you'll either have to do something sneaky with Zend_Registry to make that work (but then you'll still depend on Zend_Registry, which you wanted to get rid of), or you'll have to rewrite all the code to get the translator injected, which will be a lot of work. In legacy migration projects, the first solution will be chosen, but this is far from ideal, and eventually you'll have to rewrite everything to use proper constructor dependency injection anyway.

Rewriting to constructor injection is mainly a problem if the instantiation of a class is not completely under your control. E.g. when the framework instantiates your controller (like Symfony does, if it isn't defined as a service). It will just be new $controllerClass(), no arguments provided. In such cases, instead of giving up the possibility to inject dependencies, try to get control over the instantiation of your classes again, so you can also use any of the dependency injection benefits that your framework has to offer.

So, this is why my advice is: don't use any of these seemingly convenient shortcuts. Just use constructor injection always. Anyway, why is constructor injection considered to be so very painful that Laravel provides us shortcuts for it? It already has something very cool that makes manualy make()-ing services obsolete: Automatic Injection.

Automatic dependency injection

As you may know, the difference between a framework and a library is: you call a library, but the framework calls you. At the crucial moment that it does this, e.g. when it calls your controller, the Laravel service container will set up your controller by automatically injecting all the dependencies that it needs. As I mentioned earlier, this is how I think it should be. It also means that, really, there is no need for you to call the service container yourself, to resolve dependencies for you. You only have to make sure that they are already injected as constructor arguments.

Something I find weird is the possibility to injection dependencies as regular method arguments. The documentation mentions the handle() method of queued jobs. It looks like a job is a class which combines both the payload and the logic for processing that payload in a single class. This is where the need to inject dependencies as method arguments comes from. This need would completely disappear if you would simply separate these two things, i.e. have a class for the payload, and another class to process that payload. The first class will be a simple data transfer object, with no behavior at all, because it will just serve as a means for passing the data from the backend application to the queue worker. The second class will be a proper service, with its dependencies injected as constructor arguments. In my book (literally ;)), you can't combine a data holder and a service object in one class. But it's a matter of style after all.

Manipulating services after they have been instantiated

Using something called container events the service container makes it possible for you to manipulate a service after it has been instantiated:

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

The documentation mentions: "As you can see, the object being resolved will be passed to the callback, allowing you to set any additional properties on the object before it is given to its consumer."

Implicitly, this means that services can be designed to be mutable. A service, once instantiated, can be reconfigured and to start behaving differently. This is a design style I don't recommend, since the service that gets injected into other services, is no longer a predictable thing. Any client that has access to it can change its behavior. Of course, you could say: that won't happen. But it happens, and leads to very confusing problems. I think it's a great idea to make services immutable; provide every dependency they need as a constructor argument, and it should be possible for it to run like machine, forever.

I find that usually the need to inject things into a service after construction time is caused be two issues that can easily be solved.

  1. It may seem more convenient to call setters multiple times, than to prepare a data structure for the constructor. In that case: just take that tiny bit of extra time.
  2. Data ends up being set on the service that is not strictly a dependency or a configuration value, but is in fact contextual data, and therefore can't be resolved by the container all by itself. For example, maybe you want to inject into the service the current user's IP address, or the request URI. Or maybe even the entire session object. This kind of data should not be injected, but passed to it as a regular method argument. I've described this in more detail in another article about Context passing.

Conclusion

Laravel's service container looks great. I like the idea that it can figure things out mostly by itself. I like that it's PHP-based, and that its syntax is quite compact. I think that most of the convenience functions (e.g. resolve()) and exotic options (like $this->app->resolving()) should be ignored. The best thing you can do for your application in the long term is to let all your services use dependency injection, and to inject only constructor arguments. This keeps things simple, but also portable to other frameworks with other dependency injection containers, or other architectural styles, when the time is there.

PHP Laravel Comments

Newcrafts 2019 Day 1

Posted on by Matthias Noback

This week I attended and spoke at the Newcrafts conference in Paris. The following is a mix of notes and personal observations I wanted to share, centered around some of the talks I saw there.

Romeu Romera: Bourdieu's Social theory and our work in tech

I had never attended a talk by Romeu before. I really enjoyed this one. Somehow I already knew that he uses a mindmap to support his talk. I thought he would use an existing mind map to navigate through the talk, but it turned out he was creating one during the talk. For me personally, a slide deck helps to keep track of the story, and it helps me remember all the different topics I need to talk about. Not so much for Romeu, who knew exactly what he was going to talk about, and didn't seem to forget to mention any important part, or make important connections.

The topic is one that seems close to his heart. Still, he called himself "not an expert", saying that this talk was an experiment. It turned out that he was hinting at the fact that the subject matter is vast, and he could only cover some parts of it during the talk. Still, the things he covered, maybe simplified a lot, were very impactful, and very interesting. I'd definitely recommend watching this talk once it becomes available online.

More than with any other talk, I think you can't help it but apply the ideas mentioned to your own situation when you listen to Romeu. He covered three parts of Bourdieu's social theory. The first part is about icons of power. The way you look and behave shows how much power you have. This modified appearance is called Symbolic Violence; an act of violance people in positions of power put onto themselves. I think in the context of conferences, being a public speaker is a great example of violence the speaker puts onto themselves. Personally, I often find it a painful experience (although I'll keep doing it as long as there's a way to help people do a better job in any way).

The second part of the theory has to do with Cultural Capital. Everyone has their own amount of cultural capital. Take for example the people in your team. Some will have more experience than others, a deeper understanding of design, architecture, etc. People with less cultural capital will be seen as lesser people. Having more cultural capital can also be an issue with speakers at a conference, where they will be automatically taken to be experts, to be better humans (or at least, better designers, programmers, etc.). They will be perceived to be more powerful, and more right. This isn't fair to either party; speakers, and attendees alike, but it's how the game gets played.

Differences in the amounts of cultural capital between people will result in Dissociation. The first thing that might happen is that you see a person with less cultural capital as someone you can ignore, not take seriously, etc. The other thing that could happen is that you'll feel that a person with more cultural capital than you is unreachable, and that they wouldn't be interested in even talking to you. Personally I can relate to this problem a lot. When I'm at a conference, it totally depends how I feel: if I feel like I have a sufficient amount of cultural capital, I'll be perfectly fine, and can speak freely with anyone in the room. If I feel that I lack cultural capital, I'm very shy, and generally tend to avoid other speakers, as I will quickly feel like an imposter, noticing a mismatch between the expected and the actual amount of cultural capital.

The third part of the theory is about Hexis, which means something like to what level you feel like you belong somewhere. Hexis could be considered "high" if you never doubt that you should be where you are now. It's low if you have doubts about your presence. Being self-condident is much appreciated, showing doubt is a signal of fragility, and it will look punishable. The immediate association I had, was how code reviews show a difference in seniority (which comes with self-confidence, never a doubt that you're in the right place). The senior developer is likely to provide a lot of nitpicking comments to the one who is more junior. The junior developer will likely have a hard time providing feedback to the senior. The situation gets worse if the senior is considered to be the boss/manager/team lead as well.

And this is where Romeu brings the discussion back to software development. The problem with some agile practices is that they assume equality in the workplace. Pair programming is easy if none of the programmers are the (perceived) boss. Retrospectives are easy if the (perceived) boss isn't there.

If you have enough cultural capital, and symbolic violence, you can ignore the problem. But if you have not, you can't. The problem is real. And of course, it's better if nobody would ignore the problem. It's also a good idea if the one with all the symbolic violence, in this case the speaker himself, doesn't provide a solution, because that solution would be made from a power position. Instead, we should all work together, so we can all feel like we belong here, like we're all equally important, like everybody can bring in their ideas.

Alberto Brandolini - The Gordian Knot

Alberto is always a joy to listen to. I found this talk very well-structured. He started out with some words about the book "Accellerate", which gives scientific support for certain practices we already considered to be best practices in our industry. One of the things so-called high-performing teams have is a short time between a commit and the release of that commit to production.

Being able to release code quickly to production requires a good culture, and that will be one of DevOps, one where there's a focus on quality, testing, and autonomy. What is culture? Alberto defines it as the accumulation of winning behavior in a group. And what is the winning behavior has a lot to do with the design of the system with which this group works. Alberto thinks it's very annoying how people ascribe a lack of quality, discipline, etc. to a mentality issue, or a culture issue that simply can't be fixed. It does seem like there are bigger issues, that can be fixed.

Daniel Pink's ideas about what motivates lead Alberto to introduce the "Pink Test" for software development teams. To be motivated, developers will need:

  1. Autonomy
  2. Mastery
  3. Purpose

After showing several examples of teams that score different results on the Pink Test, Alberto concludes that a Bounded Context (a Domain-Driven Design concept), can help you score maximally on the test, because it will be an autonomy context; one where you and your team can work mostly on your own. You can show and practice mastery within that context, and you'll be fully responsible for only this part. You can't move responsibility to another team, or other people. So, you'll be motivated to make it work well.

In order to feel purpose in your work, you still need an extra ingredient: feedback. You need to know how to get feedback, when to expect feedback. Will you hear positive feedback, or only negative feedback? Do you hear it from users, or from managers? Seeing how your software makes its users happy, is very important for a sense of purpose.

Not receiving positive feedback, can easily lead to fear of changing things, because if you break something, you'll definitely get that negative feedback. If you fix it, it will be assumed to be normal. For me personally, many, many experiences come to mind where this was the case. In fact, even if teams have celebrate-with-cake moments, they aren't really satisfying, because they usually celebrate a milestone, in terms of finished work, but not in terms of appreciated work. In my experience, celebrations don't involve users, nor focus on how the users enjoy the work that was just finished. The people in the celebration meeting may even be quietly angry about how much money it has cost them, how much still doesn't work, or how their managers are already pushing for the next thing that needs to be done.

Fear of negative feedback, but also fear of not being allowed to do something that you and your team think is necessary for your project, leads to lack of motivation. If you take a look at the Pink Test again, having to ask permission for testing means you're not very autonomous. Being dependent means that you're likely not able to perform your work in a masterful way. It robs you of purpose as well, since it's not your own boat you're sailing, it's someone else's.

Alberto gets back once more to the idea of a bounded context, and besides its function in domain modelling, points out how it's going to be really helpful in establishing a place where people can feel responsible, and experience pride in their work.

PHP Newcrafts conference Comments

Style Guide for Object Design: Release of the PHP edition

Posted on by Matthias Noback

With a foreword by Ross Tuck

Today I've released the revised edition of my latest book "Style Guide for Object Design". Over the past few weeks I've fixed many issues, smaller and larger ones. Ross Tuck and Laura Cody have read the manuscript and provided me with excellent feedback, which has helped me a lot to get the book in a better shape. I added many sections, asides, and even an extra chapter, because some topics deserve more detail and background than just a few paragraphs. Oh, and Ross wrote the kindest of forewords to kick off the book. It's available in the free sample of the book.

The book will be available at the initial "preview release" price of 20 dollars (with a suggested price of 29 dollars) but only until April 15th. Use this link to apply the discount: https://leanpub.com/object-design/c/PREVIEW_RELEASE.

What happens on April 15th?

Over the past few weeks I've been talking with some fine people from Manning, publisher of tech books. This resulted in a contract with them. They will republish the "Style Guide for Object Design". There will be an e-book, but also a printed version, which is great.

The book currently has code samples in PHP and contains some PHP-specific suggestions, but the Manning edition will have Java code samples, and no programming language or ecosystem-specific advice, hopefully making the book useful and appealing to a larger group of object-oriented programmers. This is an exciting thing for me as a book author, because I may be able to reach more people in this way.

I know that some readers prefer to read the PHP version, so that's why the version as it is now will be available until April 15th. From that moment on, you won't be able to buy it anymore. However, if you've bought the e-book from Leanpub, you will be granted access to the Manning Early Access Program (MEAP) for the book, meaning that you will eventually be able to read the Manning/Java edition too. Once this is possible, I'll send out an email to existing readers, and update the Leanpub page with instructions for joining the program.

Changelog

If you already read (part of) the book, here's a summary of the most important changes, so you can decide if it'll be useful to download and read the new version. The full change log is in the back of the book and it includes linkes to the chapters and sections that have been updated.

  • Foreword

    • Added the foreword by Ross Tuck.
  • The lifecycle of an object

    • Rewrote the explanation about the two types of objects. What really defines these types is how they are related to each other. The first type uses the second type (services versus materials).
  • Creating services

    • Added a subsection: "Keeping together configuration values that belong together", which introduces a way to keep together configuration values that belong together.
    • Added an aside: "What if I need the service and the service I retrieve from it?", which answers a common question that was asked by the technical reviewer.
  • Creating other objects

    • Added a new section: "Don't use custom exception classes for invalid argument exceptions", explaining why you don't usually need custom exception classes for invalid argument exceptions.
    • Added an aside: "Adding more object types also leads to more typing, is that really necessary?", explaining some of the benefits of using object types instead of primitive types, just in case people are wondering if all that extra typing is really necessary.
    • Added another example to the section "Don't inject dependencies, optionally pass them as method arguments", explaining how you could rewrite the currency conversion logic using a simple services. Added a comment about the design trade-offs you have to make in this type of situation.
    • Added an aside about PHP's class-based scoping, explaining how it's possible that a named constructor can manipulate private properties directly.
    • Added a subsection "Optionally use the private constructor to enforce constraints" with an example showing how you can use the private constructor when you have multiple named constructors.
    • Finish the chapter with a new section: "The exception to the rule: Data transfer objects" about Data transfer objects, a type of object with less strict rules, which was not yet discussed in detail.
  • Manipulating objects

    • Added a new introduction, explaining the different types of "other" objects and what their design characteristics are in the area of object manipulation, covering Entities, Value objects and DTOs.
    • Add an aside: "A third-party library has some object design issues, what do I do?"
  • Retrieving information

    • Added an aside: "How do you handle ambiguous naming?", explaining how you can deal with query method names that could be read as verbs (e.g. "name").
  • Dividing responsibilities

    • Added this new chapter, which elaborates on how you can make some objects be responsible for changing state, and others for providing information. It has a new, more detailed example, which is also more realistic than the current one about a player and its position.
PHP object design Comments