PHP & Symfony About PHP and Symfony2 development

Unnecessary contrapositions in the new "Symfony Best Practices"

Posted on by Matthias Noback

Of course I'm going to write something about the new Symfony Best Practices book that was written by Fabien Potencier, Ryan Weaver and Javier Eguiluz. It got a lot of negative responses, at least in my Twitter feed, and I think it's a good idea to read Richard Miller's post for some suggestions on how to deal with an "early preview release" like this and how we can be a bit more positive about it.

Staying positive

One of the most important things we have to keep in mind: you don't have to agree with everything in this document. There are so many different ideas about "how to do things best", it would have been impossible to write a set of best practices everyone agrees upon.

As you can imagine (if you read my blog posts or have seen my presentations), I myself don't think many of the practices in the book could be described as "best". But then again, what is "best", and what is the type of project and/or team you have in mind when you decide that they are "best"? So, I really don't care about the document in this way. What the Symfony team thinks is best, they are allowed to label as "best".

But

But, and this is one huge "but": the way they introduced this book was very unfortunate (emphasis mine):

[...] community resources - like blog posts or presentations - have created an unofficial set of recommendations for developing Symfony applications. Unfortunately, a lot of these recommendations are in fact wrong.

To say such a thing in an official text is such a bad move, I don't understand why none of the reviewers has been able to convince the authors to remove this particular paragraph. Still, it is apparently such an important phrase, that not only is it part of the book itself, it is also being quoted in the article that first introduced the book.

You know, I have invested countless hours into working with Symfony, finding out how some parts of it work, writing about it (the Security Component documentation, Cookbook articles, 90 blog posts), talking about it in public, preaching Symfony to my team members and employers, finding new and better ways to do things with Symfony, and so on and so on.

Now I have always known that what I think is good, probably is not what Fabien thinks is good. But still, to actually read this in a public, official document - "a lot of these recommendations are in fact wrong" - well, that is just something that's very hard to process.

It is totally not necessary to write such a thing. It is a very mean thing to say to a community at large. It makes the authors look like old-fashioned managers, walking by the desks of their personnel, saying "you're wasting your time there, buddy", "you know, traditionally, Symfony developers used to do that, but we know better now" and "you are over-complicating things, get out!"

Let's not get all emotional

I totally agree with Richard Miller as he correctly states:

However, I do see no reason not to remove some of the more emotional and inflammatory language as soon as possible.

And, yes, I have to admit, I just used a bit of emotional and inflammatory language myself. But this article is not an official recommendation. It is a personal, opinionated blog post, so I feel totally entitled to do so.

Recommendations

To me the introductory paragraph and several other parts of the text communicate the message that "you're doing it wrong". It didn't have to be that way! It could have just been like, "you know, we think, this is the right way". So my first recommendation to the authors would be: change the text to reflect this community-friendly attitude. Even if the motivation to write the book was the "wrongness" of the approaches suggested by community authors like me, you should definitely hide that motivation, since it doesn't do any good to your relation with the community.

In general, there is no need for a best practices book like this to be opposed to anything really. It's not necessary to point out "mistakes" made be community people. And it only causes negative feelings in the reader's mind if they have to read about "things Symfony developers traditionally do", while they actually do those things, and don't know what would be "wrong" about that. Finally, it makes no sense to label a common approach as "overcomplicating things" or "over-kill". So just provide a list of the best practices and don't mention all the things you find a bad practice.

Good luck

I am a Symfony developer

At the end of this post, you may still call me a Symfony developer. I really hope the authors of this book will take my complaints seriously. Then I think that this book will have a long and prosper future and will serve as a good book for people to get started with the Symfony framework. When you've finished it, I recommend you to read my own book, A Year With Symfony, to get some more advanced insights into Symfony and how to work with it. Afterwards, I hope you will practice your coupling radar, learn to recognize the touching points between your code and the framework of your choice, and to actually break free from it, because:

It's not your code that needs the framework, it's you. And you need a good one. So choose Symfony.

Categories: PHP Symfony2

Comments: Comments

Composer "provide" and dependency inversion

Posted on by Matthias Noback

This is a response to Peter Petermann's article Composer and virtual packages. First, let's make this totally clear: I don't want to start an Internet war about this, I'm just pointing out some design issues that may arise from using Composer's provide option in your package's composer.json file. This means it's also nothing personal. To Peter: you wrote a very nice article and shed light on an underused feature of Composer. Thank you for that!

The situation Peter describes is a library that makes use of the LoggerInterface as defined in PSR-3:

namespace Example\MyLib;

use Psr\Log\LoggerInterface

class SomeClass
{
    public function __construct(LoggerInterface $logger)
    {
        ...
    }
}

This LoggerInterface is readily available as part of the psr/log package. So this library explicitly mentions this package as a dependency:

{
    "name": "example/mylib",
    ...
    "require": {
        "psr/log": "~1.0",
    }
    ...
}

Peter then says:

[...] since you are building your lib so it needs a log provider injected (and if it is a NullLog), you want your dependencies to reflect that.

This is both true and false. Yes, if a user wants to run the code in your library, they need to have some class that implements LoggerInterface. But no, this shouldn't be reflected in the dependencies of the library. Let me explain this by taking a look at the Dependency inversion principle.

Why we depend on interfaces: dependency inversion

We could have chosen to require one specific logger, for example the Zend logger (or the Monolog logger, or Drupal Watchdog if you like). Our code would have looked something like this:

namespace Example\MyLib;

use Zend\Log\Logger

class SomeClass
{
    public function __construct(Logger $logger)
    {
        ...
    }
}

We'd have to reflect this in our list of dependencies of course:

{
    "name": "example/mylib",
    ...
    "require": {
        "zendframework/zend-log": "~2.0",
    }
    ...
}

Now we can do a little bit better, by depending on the interface Zend offers, instead of the class. This would allow others to implement their own logger class, which implements the Zend logger interface (maybe it can be an adapter for their own favorite logger).

namespace Example\MyLib;

use Zend\Log\LoggerInterface

class SomeClass
{
    public function __construct(LoggerInterface $logger)
    {
        ...
    }
}

But it doesn't really make a difference, since people would still have the zendframework/zend-log package pulled into their project for (almost) nothing.

To solve this issue, we may apply the Dependency inversion principle here. We want to invert the direction of our dependencies. We can do this in two different ways.

Solution 1: define your own interface

The first way is to define our own Logger interface, inside the library:

namespace Example\MyLib;

interface LibraryLoggerInterface
{
    ...
}

class SomeClass
{
    public function __construct(LibraryLoggerInterface $logger)
    {
        ...
    }
}

One advantage of this approach is that we can define only the methods we need. A big disadvantage is that if we do this for each and every dependency we will end up with a lot of interfaces in our library, which requires you to also write a lot of adapter classes to make existing implementations conform to those interfaces.

But still, in the example above we did invert the dependency direction. We started with this:

Mylib depends on Zend logger

Then, we introduced our own LibraryLoggerInterface and this effectively removed any dependency arrow outwards. We introduced an adapter package which makes the Zend logger compatible with our logger interface. Looking at the modified dependency graph, we see that it contains a new dependency arrow, but this one points towards our library package:

Mylib is independent

Solution 2: decide on a common interface defined elsewhere

To overcome the problem of needing numerous adapter packages before we can finally start to use a library in our application, we can also try to define some common interfaces for things every library uses/needs. This is where PSR-3 steps into the picture. It tries to offer something very much like dependency inversion: a general logger interface which does not come with any concrete implementations.

Mylib is dependent again, but on a very *stable* library

This psr/log package is a highly abstract, independent, and therefore very stable package. It doesn't tell you anything about how to implement a fully functional logger. Our example/mylib package therefore only depends on abstract things, instead of concrete things, which is what the Dependency inversion principle is all about.

Back to to square one: Composer's provide

That was quite a bit of theoretical background. Now, back to where this all started: Composer has this feature which allows you to tell what kind of "virtual packages" a package provides. In the case of the psr/logger there are multiple logger packages that provide an implementation of the LoggerInterface from that package. These packages mention in their composer.json that they provide a psr/log-implementation package (you can find a list of PSR-3 compliant logger packages here):

{
    "provide": {
        "psr/log": "1.0.0"
    }
}

This is a really cool feature. Especially since you can just "define" any virtual package by mentioning it under your own provide key.

Virtual packages on Packagist

But you have to think very hard before you use this provide feature. Peter suggests that the example/mylibrary package should not only have psr/log as a dependency, but it should also depend on the virtual package psr/log-implementation:

{
    "provide": {
        "psr/log": "1.0.0",
        "psr/log-implementation": "1.0.0"
    }
}

He thereby wants to communicate that in order to use this library, you also need a package which offers an implementation of LoggerInterface from psr/log. I don't think this is a good idea (as you might have guessed ;)). These are my reasons:

  1. Strictly speaking (as in, would the code compile), the code from the library itself doesn't need a package that provides psr/log-implementation. It just needs the LoggerInterface (which happens to be in the psr/log package).
  2. Of course, in order to actually run the code from the library you will need an instance of LoggerInterface, which means you need a class that implements said interface. But that doesn't mean you actually need a package that contains such a class. That class can be located anywhere, in the current project, in a globally installed PEAR package, in a PHP extension, it may even be shipped with PHP. If you want to communicate that your library needs a working logger implementation, just using the LoggerInterface - and thus requiring just psr/log - is quite enough.
  3. By depending on an implementation package, you basically undo any effort you made to depend on abstractions and not on concretions. Since a "PSR logger" implementation is by definition a concrete implementation of the LoggerInterface from psr/log. In other words, you have pointed your previously inverted dependency arrow back to concrete packages (although you leave it undecided which concrete package that will be).
  4. psr/log-implementation, or any virtual package for that matter, is very problematic as a package. There is no definition of this virtual package. It is merely a phenomenon arising from the fact that some package has the name of the "virtual package" in its provide section. In the case of the psr/log-implementation package, this lack of a proper definition or rules for virtual packages means that there can a) be packages that contain a class that implements LoggerInterface (from psr/log), but don't have "provide": { "psr/log-implementation": ..." } in their composer.json and b) that packages might say they provide, while they don't. Which makes the concept unreliable.
  5. Some day, someone may decide to introduce another virtual package, called the-real-psr/log-implementation (they can easily do that, right?). Such packages may be completely exchangeable with existing psr/log-implementation packages, but in order to be useful, every existing PSR-3 compliant logger package needs to mention that virtual package too in their provide section. And so on, and so on. The underlying conceptual problem is: there is no such thing as a canonical virtual package.
  6. The notion of an "implementation package" is really vague. What does it mean for a package to be an implementation package. Is it sufficient for it to implement just one interface? What if the "interface package" contains multiple interfaces, which one should the "implementation package" implement? All, one?
  7. The final argument against psr/log-implementation packages is that psr/log (the interface package) itself contains a NullLogger class, which is an implementation of its own LoggerInterface, and therefore this package itself also qualifies as a psr/log-implementation package!

But then, when should I use provide?

Now after being so negative about virtual packages, I am obliged to give you some positive examples of using virtual packages.

When the package knows about existing virtual packages

One interesting legitimate example I came across was the DoctrinePHPCRBundle. The bundle offers a Symfony-specific configuration layer for using PHPCR ODM with one of the existing PHPCR implementations (don't ask me what all these words mean, since I don't know much about them ;)). The actual PHPCR implementations packages jackalope/jackalope and midgard/phpcr both provide the virtual package phpcr/phpcr-implementation.

From the perspective of the DoctrinePHPCRBundle it makes sense to use virtual packages, since it actually knows about the existing phpcr/phpcr-implementation packages and it allows users of the bundle to pick one of them. Whether you choose the midgard/phpcr or jackalope/jackalope implementation package, the bundle knows how to correctly configure services for them.

Categories: PHP

Tags: Composer package design

Comments: Comments

Announcements after a year with "A Year With Symfony"

Posted on by Matthias Noback

As a follow-up on a previous article I have some announcements to make.

Feedback

A wonderful number of 67 of you have provided very valuable feedback about "A Year With Symfony" - you sure wrote some nice things about my book! Just a small selection of highlights:

Reading "A Year With Symfony" helped to clarify and validate the ideas and structures that I had been developing with my team, along with explaining some of the internals of Symfony that the documentation did not.

There is lots of practical advice, solutions to common problems and general best-practice information contained and anyone who uses Symfony absolutely MUST read this book - a genuine godsend!

— Matthew Davis

This book is well written and has filed in many of the gaps I had in my understanding of how Symfony works at its core. I finally understand how to use the dependency injection component correctly and why configuring controllers and commands as services can be an excellent idea. The chapters on project and bundle organization have caused me to change the way I code to make it more understandable, more flexible, and more resilient. I'm going to be reading this book again because there is so much excellent information in this book that I'm certain I missed wrote a bit.

— Joshua Smith

"A Year with Symfony" was a really great and smooth read and it's the perfect book as a follow up to the official "The Symfony Book" with a great collection of best practices for building maintainable web applications with Symfony2.

— Dominik Liebler

Prizes!

Notebook

Those of you who submitted the form automatically took part in a raffle of some book-related things like free printed and digital copies of the book, as well as a special edition notebook. All participants have been notified of the outcome already. The winners are:

  • 5 printed copies: Lukasz, Nikolai Zujev, pinouf and Liviu Mirea
  • 10 digital copies: Sadok Ferjani and Heiko Krebs
  • 5 notebooks: Erik van Wingerden and Peter Nijssen

Rewriting an existing chapter

As I told you, I'm planning to rewrite the "Project structure" chapter of my book. It will be replaced entirely by something more up-to-date, corresponding to my own current practices. Some of the things you may expect the new chapter to be about: hexagonal architecture, commands and command handlers and events and event handlers.

Writing a new chapter

I also asked you: what would you like me to write about, if I were to publish a new chapter? This is the list of potential subjects I provided and the number of times you voted for them:

Subject Votes
Performance optimization 41
Testing 41
Deployment 35
Forms 29

For completeness sake, this is a list of other subjects that were mentioned: REST API, domain separation, BDD, black-box testing and white-box testing, writing APIs, Silex, Doctrine2 best practices (but each of them not more than once).

It is clear that most people find all of the potential subjects quite interesting, even though "Performance optimization" and "Testing" are absolute winners. On both subjects I might have some useful thoughts to share, but I've already written a blog post series about testing for PHP. Also, the subject of "Performance optimization" has some very Symfony-specific aspects to it. So I have decided that the second bonus chapter will be about:

Performance optimization!

However, first I'm going to finish my second book Principles of PHP Package Design, since that one has been begging for my attention the last couple of months. So expect to hear more about my plans for "A Year With Symfony" in 2015.

General discount

Let's continue the party a little longer, by applying a general discount of 20% to "A Year With Symfony". You don't need a special discount code or anything. Just buy the book via Leanpub.

Conclusion

I've got nothing more to say than: thank you again, for your overwhelming response and your continuous support of my book writing and blog posting efforts.

Categories: PHP Symfony2 Book

Tags: A Year With Symfony prizes

Comments: Comments