Final classes by default, why?

Posted on by Matthias Noback

I recently wrote about when to add an interface to a class. After explaining good reasons for adding an interface, I claim that if none of those reasons apply in your situation, you should just use a class and declare it "final".

PHP 5 introduces the final keyword, which prevents child classes from overriding a method by prefixing the definition with final. If the class itself is being defined final then it cannot be extended.

— PHP Manual, "Final Keyword"

An illustration of the final syntax for class declarations:

final class CanNotBeExtended
{
    // ...
}

class CanStillBeExtended
{
     // ...
}

class Subclass extends CanStillBeExtended
{
    // override methods here
}

// Produces a Fatal error:

class Subclass extends CanNotBeExtended
{
    // ...
}

For a couple of years now I've been using the final keyword everywhere (thanks to Marco Pivetta for getting me on track!). When I see a class that's not final, it feels to me like it's a very vulnerable class. Its internals are out in the open; people can do with it what they want, not only what its creator has imagined.

Still, I also remember my initial resistance to adding final to every class definition, and I often have to defend myself during workshops, so I thought it would help if I explained all about it here.

The alternative: non-final classes

Omitting the final keyword is the standard for many developers, who reason that they should allow others to reuse the class and change just a part of its behavior by extending it. At first this may seem like a very considerate thing to do, but there are several downsides to it.

You are the initial designer of the class, and you had one particular use case in mind when you created it. Using the class for something else by overriding part of its behavior may jeopardize its integrity. The state of the extended object may become unpredictable or inconsistent with the state of the original object. Or the behavior may change in such a way that the subclass can no longer be considered a proper substitute for the base class.

Furthermore, the developer who creates a subclass of your class to override its behavior, probably doesn't need all of the internals of the parent class. Still it inherits those internals (data structures, private methods), and will soon arrive at the point that it has to work around them.

In terms of future development there's another issue: the class that has now been subclassed, still has a life on its own. Its clients may have different needs over time, so changes will be made to it. These changes may affect the subclasses too, since they likely rely on a particular implementation detail of the parent class. When this detail changes, the subclass will break.

From all these issues we have to conclude that by allowing subclassing, we will end up with an undesirable situation. The future of the subclass and the parent class will become intertwined. Refactoring the parent class will be quite hard, because who knows which classes are relying on a particular implementation detail. And since the subclass doesn't need all of the parent class's data and behaviors, they may act like they are the same kind of thing, but in reality, they aren't.

Replacing is better than overriding

So changing the behavior of a class shouldn't be done by subclassing that class and overriding methods. But what else can we do? In most cases, actually modifying the code of the original class isn't possible. Either because the class is used and relied on elsewhere in the project, or it's not physically an option to modify its code because it's part of a vendor package.

I'd even say that changing code shouldn't be the preferred way of changing behavior in the first place. If you can, change the behavior of an object by reconfiguring it with, that is, by swapping out constructor arguments, you should do it.

This reminds us of the Dependency inversion principle, according to which we should depend on abstractions, and the Open/closed principle, which helps us design objects to be reconfigurable, without touching its code.

What about the Template Method pattern?

There's an alternative approach for changing the behavior of a class. It's a behavioral pattern described in the classic "Design Patterns: Elements of Reusable Object-Oriented Software". The pattern is called "Template Method":

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

This solution is slightly better than allowing subclasses to override a parent class's behavior, since it limits what can be changed. It renders the parent class itself useless by marking it as abstract. This prevents it from having a life on its own. In the Symfony Security component we find an excellent example of the Template Method pattern in the abstract Voter class. It helps users implement their own authorization voter, by providing the bulk of the algorithm that is supposed to be the same for all voters. The user only needs to implement supports() and voteOnAttribute(). which are both smaller parts of the overall algorithm. Symfony wouldn't be able to guess these implementations, since they are project-specific.

abstract class Voter implements VoterInterface
{
    public function vote(TokenInterface $token, $subject, array $attributes)
    {
        $vote = self::ACCESS_ABSTAIN;

        foreach ($attributes as $attribute) {
            if (!$this->supports($attribute, $subject)) {
                continue;
            }

            $vote = self::ACCESS_DENIED;
            if ($this->voteOnAttribute($attribute, $subject, $token)) {
                return self::ACCESS_GRANTED;
            }
        }

        return $vote;
    }

    abstract protected function supports($attribute, $subject);

    abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token);
}

It's really nice, but still, there's nothing in here that couldn't have been solved with composition instead of inheritance. An equivalent solution would've been:

// The user needs to implement this interface, instead of extending from Voter:

interface AttributeVoter
{
    public function supports($attribute, $subject);

    public function voteOnAttribute($attribute, $subject, TokenInterface $token);
}

// The Security package provides this standard Voter implementation:

final class Voter implements VoterInterface
{
    private $attributeVoter;

    public function __construct(AttributeVoter $attributeVoter)
    {
        $this->attributeVoter = $attributeVoter;
    }

    public function vote(TokenInterface $token, $subject, array $attributes)
    {
        $vote = self::ACCESS_ABSTAIN;

        foreach ($attributes as $attribute) {
            // We delegate calls to the injected AttributeVoter

            if (!$this->attributeVoter->supports($attribute, $subject)) {
                continue;
            }

            $vote = self::ACCESS_DENIED;
            if ($this->attributeVoter->voteOnAttribute($attribute, $subject, $token)) {
                return self::ACCESS_GRANTED;
            }
        }

        return $vote;
    }
}

Following the reasoning from "When to add an interface to a class" we might even reconsider the presence of a VoterInterface in the example above. If all the voting is done in the same way, that is, the voting algorithm is the same in all situations, we don't actually want users to write their own implementations of VoterInterface. So we also might provide only the Voter class, not the interface (I'm not actually sure it's the case for the Symfony Security component, but it's a good design choice to consider in your own projects).

The proposed solution doesn't involve inheritance anymore. It leaves the internals of all the involved classes to themselves. That is, these classes can all be final, and can be safely refactored now. Another advantage is that we could reuse each class in a different scenario. Final classes are more like building blocks ready for use, instead of templates that still need to be made concrete.

Composition over inheritance

You may have heard the phrase "Composition over inheritance" before; this is what is meant by that. In most cases, you should prefer a solution that involves composition over a solution that involves inheritance. To be more specific, if you feel the need to reconfigure an object, to change parts of an algorithm, to rewrite part of the implementation, consider creating a new class instead of overriding an existing class. If you need to represent a hierarchy of classes, where subclasses are proper substitutes for their parent classes, this would be the classic situation where you may still consider inheritance. However, the result may still be better if you don't inherit from concrete parent classes but from abstract interfaces. Still, this is very tricky to get right (I personally regret most of my previous work involving inheritance), so if you can, you should still prefer composition.

Extension should be a distinct use case

As a class designer you create a class in such a way that it provides a number of useful things its users can do with it. For example, they can:

  • instantiate it,
  • call a method on it,
  • get some information from it,
  • or change something about it.

"Reconfiguring it to change part of its behavior" should also be on this list, as a separate item. And so should "allowing it to be extended to override part of its behavior". It should be a deliberate decision to allow the user to do that with your class. Allowing a class to be extended has (often limiting) consequences for its design, and its future. So at least you shouldn't allow it by default.

"Final" pushes everyone in the right direction

So if allowing users to subclass a class shouldn't be the standard, then not allowing it should be. In other words: adding final to a class declaration should be your default. This simple trick will lead everyone in the right direction: towards classes that are smaller, have fewer maintenance issues, are easier to refactor, and act more like building blocks that can be reused in different parts of the project.

"You can always remove final if you want to"

I've often heard one argument for using final that I wanted to address here: "You can always remove final if you want to." In a sense, this is right; you can always allow extending a class whenever you want. But the same seems to be true for adding "final" - it's always just a few keystrokes away. As discussed in this article, I don't think it's the right attitude; it's better to close down, instead of open up. Opening up after having been closed is asking for all the trouble of inheritance described above. Make sure that instead of removing "final" from the class declaration, you will always aim for a solution that replaces part of the existing solution, and uses composition to allow for reconfiguration.

"My mocking tool doesn't work with final classes"

One objection against final classes that I've often heard is: "I like what you say, but my mocking tool doesn't work with final classes". Indeed, most don't. It makes sense, because the test doubles that such a tool generates usually look something like this:

final class CanNotBeExtended
{
}

// This produces a Fatal error:

class TestDoubleForCanNotBeExtended32789473246324369823903 extends CanNotBeExtended
{
    // override implementations of parent class here...
}

It's unfortunate, but the tools are not to blame. They'd have to do some sneaky things like hooking into the class loader to make the class non-final (in fact, that's quite doable). But they don't, so the truth gets slapped in our face. All that the tool is saying is that a final class can't be extended - there's nothing problematic about that fatal error. Instead, whenever we feel the need to replace a final class with a test double, we should consider two options:

  1. Maybe we shouldn't want to replace the real thing, i.e. the class.
  2. Maybe we should introduce something we can replace, i.e. an interface.

Option 1 is often useful when you're trying to mock things like entities and value objects. Just don't do it.

Option 2 should be applied in most other cases. When the class should've had an interface, add one, and create a test double for the interface instead.

Conclusion

In conclusion: make your classes final by default. One trick to help you with that is to modify the IDE template for new classes to automatically add final for you. Also, make the "final" discussion part of your technical code review. Ask: why is this class not final? The burden of proof should be on the author of the class (and if they don't agree, talk to them about the merits of using "final").

PHP design reuse package design Comments

Reusing domain code

Posted on by Matthias Noback

Last week I wrote about when to add an interface to a class. The article finishes with the claim that classes from the application's domain don't usually need an interface. The reason is that domain code isn't going to be swapped out with something else. This code is the result of careful modelling work that's done based on the business domain that you work with. And even if you'd work on, say, two financial software projects in a row, you'll find that the models you produce for each of them will be different in many subtle (if not radical) ways. Paradoxically you'll find that in practice a domain model can sometimes be reused after all. There are some great examples out there. In this article I explain different scenarios of where and how reuse could work.

Reuse-in-the-small

In "Facts and Fallacies of Software Engineering" (2002), Robert Glass speaks about reuse-in-the-small:

Reuse-in-the-small (libraries of subroutines) began nearly 50 years ago and is a well-solved problem.

Reuse in software is quite possible, but only if the amount of shared code is relatively small. Examples of components with the right size in the PHP ecosystem would be:

  • Flysystem (filesystem abstraction)
  • ProxyManager (proxy generation)
  • JMSSerializer (object serialization)
  • Symfony Validator (validation)

And so on. The idea being: if the library is basically a utility function that "got a bit out of hand", but is flexible at the same time, supporting many different use cases, then we can speak of successful reuse. In particular because PHP package maintainers tend to set a very high standard for themselves: every quality indicator should be green, 100%, etc. And so it can happen that these packages have millions and millions of downloads.

By the way, I also count frameworks as successful reuse-in-the-small: most of them are a collection of utility-like libraries anyway, and they rarely get in the way in terms of flexibility, at least in my experience; you can build any web application on top of any of them.

Reuse-in-the-large and software diversity

If a reusable component is too large, many aspects of it will be irrelevant, or even counter-productive, for its users. This leads to objections like:

  • When I'd use this component in my project, I'll be downloading way too much stuff that I'll never actually need.
  • Considerable parts of this component don't work as I want them to, so maybe I should roll my own.
  • This component is good today, since we're in the prototype phase, but probably in about a year, it will limit us.

In Glass's terms, we're talking about reuse-in-the-large, and he poses that:

Reuse-in-the-large (components) remains a mostly unsolved problem, even though everyone agrees it is important and desirable.

Software projects in general are very diverse. Every project comes with its own requirements, its own domain experts, its own team, and everything about it is special (although some product owners would be better off not thinking that their project was so very special). Still, one might say, there should be some common ground. Some domain knowledge will be potentially reusable, like a Money class, or a DateTime class, right?

If there are enough common problems across projects and even application domains, then component-based approaches will eventually prevail. If, as many suspect, the diversity of applications and domains means that no two problems are very similar to one another, then only those common housekeeping functions and tasks are likely to be generalized, and they constitute only a small percentage of a typical program’s code.

Over the past few years we've had some excellent articles making the rounds, which prove the point that "no two problems are the same". I'd like to mention Ross Tuck's "Precision Through Imprecision: Improving Time Objects" and Mathias Verraes's "Type Safety and Money". In these articles, we learn by example how design decisions are based on domain knowledge, that different situations require different decisions, and that designs resulting from those decisions can't be useful in every other situation. Trying to use things like a Money object in situations where they just don't belong, is very much like the saying: "trying to fit a round peg in a square hole".

In particular, reusing domain code will make us feel like we have to ignore or work around certain aspects of it. Which is why, instead of reusing this code from some shared location, we might be better off copying it instead. By copying the code and using it in a different situation, we can find out if the code really has potential for reuse. And it gets even better: it'll be easier to find the right abstractions, since we'll be able to clearly see what's essential to the thing we we're trying to reuse, once we see how it may serve other use cases.

We won't have any of these advantages if we aim to reuse the code from the outset. Which is why Glass gives us one of his "rules-of-three":

[...] a reusable component should be tried out in three different applications before it will be sufficiently general to accept into a reuse library.

Reuse-in-the-even-larger: reusing entire subdomains

It's funny that, while reuse-in-the-large is deemed an unsolved problem, today we see several larger reusable software projects, which are (according to Glass, against all odds) very successful. We see large reusable components for e-commerce software, like Sylius, Spryker, Spark, etc. These are not just oversized utility functions; they provide complete solutions for running commercial internet-based businesses, and offer features like inventory management, payments and invoicing, and so on. There's bound to be a lot of domain decisions in that code. This contradicts the reuse-in-the-large problem of software diversity. Even though no two problems/projects are the same, these components still aim to solve many problems at once, and given the popularity of them, we should conclude that these reusable components are indeed examples of successful reuse-in-the-large.

How to explain this?

In part, I think, because most of us don't want to spend time building the equivalent components from the ground up. As it says on the Spark homepage:

Spark is a Laravel package that provides scaffolding for all of the stuff you don't want to code.

Another reason might be that these components are still relatively small and focused - in most cases there's always the option to write your own component to replace the third-party ones. Limiting the scope of a package to a small part of the domain therefore contributes to its success. At that point you can make a conscious decision: which part of the domain is special in your case, which part requires more careful modelling, how can my application make a difference?

Eric Evans has written extensively about how and when reusing a domain model could work, in his book "Domain-Driven Design" (2003). He explains how you can divide the overall domain of an application or software system into several subdomains. He calls this "strategic distillation", because the aim is to find out what your core domain is, separating it from the other subdomains, including so-called "generic" ones. Evans summarizes this as follows:

Identify cohesive subdomains that are not the motivation for your project. Factor out generic models of these subdomains and place them in separate MODULES. Leave no trace of your specialties in them. Once they have been separated, give their continuing development lower priority than the CORE DOMAIN, and avoid assigning your core developers to the tasks (because they will gain little domain knowledge from them). Also consider off-the-shelf solutions or published models for these GENERIC SUBDOMAINS.

For application developers this is useful advice, because it helps you focus your development effort on areas where your application can stand out amongst many others. At the same time, it will help you decide for which parts of your application you should rather use an existing library or external service, also known as an "off-the-shelf solution".

It's also useful advice for package developers: application developers looking for off-the-shelf solutions are the potential users of the packages that you publish. So whenever you consider extracting part of your application into a reusable package, consider if it can be used by others to help them get to their core domain quicker.

Once more, for application developers this is crucial advice: when using these third-party solutions in your own applications, consider once again if you've done your strategic distillation right. Can you be certain that the off-the-shelf solution won't get in your way when you're continuously improving the code for your core domain?

Conclusion

Reuse-in-the-small is definitely possible. Reuse-in-the-large is deemed to be impossible, because no two problems/projects are alike, but practice proves otherwise. There are reusable components covering entire subdomains, which are nonetheless quite successful. The chance of success is bigger if such a reusable component is used to cover for a generic subdomain. Using an off-the-shelf solution in such a case helps you save development effort which can instead be redirected to the core domain. Another requirement is that the component is flexible enough to replace parts that don't fit well with your requirements, and/or that the components are small enough to be replaced or rewritten in its entirety.

Epilogue

Here's something interesting to watch: a talk by Eric Evans called "Exploring Time". In it, he discusses date/time calculations in terms of instances and intervals. This could be considered a generic subdomain that's part of almost every larger domain. Pointing out that existing date/time handling APIs don't make a lot of sense, he comes up with better models. One big conclusion for me is that modelling efforts in generic subdomains will improve the models in many ways. Even though we don't know all the situations in which a generic model will be used, we can still do a better job by extensively thinking and researching aspects of the domain.

PHP Domain-Driven Design reuse package design Comments

When to add an interface to a class

Posted on by Matthias Noback

I'm currently revising my book "Principles of Package Design". It covers lots of design principles, like the SOLID principles and the lesser known Package (or Component) Design Principles. When discussing these principles in the book, I regularly encourage the reader to add more interfaces to their classes, to make the overall design of the package or application more flexible. However, not every class needs an interface, and not every interface makes sense. I thought it would be useful to enumerate some good reasons for adding an interface to a class. At the end of this post I'll make sure to mention a few good reasons for not adding an interface too.

If not all public methods are meant to be used by regular clients

A class always has an implicit interface, consisting of all its public methods. This is how the class will be known to other classes who use it. An implicit interface can easily be turned into an explicit one by collecting all those public methods (except for the constructor, which should not be considered a regular method), stripping the method bodies and copying the remaining method signatures into an interface file.

// The original class with only an implicit interface:

final class EntityManager
{
    public function persist($object): void
    {
        // ...
    }

    public function flush($object = null): void
    {
        // ...
    }

    public function getConnection(): Connection
    {
        // ...
    }

    public function getCache(): Cache
    {
        // ...
    }

    // and so on
}

// The extracted - explicit - interface:

interface EntityManager
{
    public function persist($object): void;

    public function flush($object = null): void;

    public function getConnection(): Connection;

    public function getCache(): Cache;

    // ...
}

However, regular clients of EntityManager won't need access to the internally used Connection or Cache object which can be retrieved by calling getConnection() or getCache() respectively. You could even say that the implicit interface of the EntityManager class unnecessarily exposes implementation details and internal data structures to clients.

By copying the signatures of these methods to the newly created EntityManager interface, we missed the opportunity to limit the size of the interface as it gets exposed to regular clients. It would be most useful if clients only needed to depend on the methods they need. So the improved EntityManager interface should only keep persist() and flush().

interface EntityManager
{
    public function persist($object);

    public function flush($object = null);
}

You may know this strategy from the Interface segregation principle, which tells you not to let clients depend on methods they don't use (or shouldn't use!).

If the class uses I/O

Whenever a class makes some call that uses I/O (the network, the filesystem, the system's source of randomness, or the system clock), you should definitely provide an interface for it. The reason being that in a test scenario you want to replace that class with a test double and you need an interface for creating that test double. An example of a class that uses I/O is the CurlHttpClient:

// A class that uses IO:

final class CurlHttpClient
{
    public function get(string $url): string
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // This call uses the network!
        $result = curl_exec($ch);

        // ...

        return $result;
    }
}

// An explicit interface for HTTP clients like CurlHttpClient

interface HttpClient
{
    public function get(string $url): string;
}

Introducing an interface for such classes is usually an excellent situation to apply the Dependency inversion principle as well: make sure the interface is more abstract than the class. A first step would be to remove the specificness from the class name and methods, as we did in the example: we went from CurlHttpClient to HttpClient, hiding the fact that Curl is used to do the actual work. The next step would be to find out how this interface will be used. For example, is it used to communicate with a remote service to load user data from, like in the AuthenticationManager class below?

final class AuthenticationManager
{
    private $client;

    public function __construct(HttpClient $client)
    {
        $this->client = $client;
    }

    public function authenticate(Request $request): void
    {
        $username = $request->request->get('username');

        $userData = json_decode($this->client->get('/user?username=' . $username), true);

        // ...
    }
}

In that case, we could take the design to the next level by acknowledging that AuthenticationManager doesn't really need an HttpClient, but rather a "user data provider". This is a more abstract concept, which can easily be modelled as an interface:

// A proper abstraction for something that "provides user data":

interface UserDataProvider
{
    public function getByUsername(string $username): array;
}

final class AuthenticationManager
{
    private $userDataProvider;

    public function __construct(UserDataProvider $userDataProvider)
    {
        $this->userDataProvider = $userDataProvider;
    }

    public function authenticate(Request $request): void
    {
        $username = $request->request->get('username');

        $userData = $this->userDataProvider->getByUsername($username);

        // ...
    }
}

Introducing the UserDataProvider abstraction makes the AuthenticationManager class much more flexible, allowing us to plug a different strategy for providing user data. It will also make it easier to provide test doubles for the dependencies of AuthenticationManager. Instead of preparing an HttpClient stub which returns a carefully crafted HTTP response object, we can now simply return an array of user data.

If you'd like to know more about using test doubles to replace I/O calls, take a look at my article series on "Mocking at architectural boundaries":

If the class depends on third-party code

If there is some third-party code (e.g. from a package you don't maintain yourself) that is used in your class, it can be wise to isolate the integration of your code with this third-party code and hide the details behind an interface. Good reasons to do so are:

  • The (implicit) interface wouldn't be how you would’ve designed it yourself.
  • You're not sure if the package is safe to rely on.

Let's say you need a diffing tool to calculate the differences between two multi-line strings. There's an open source package (nicky/funky-diff) which provides more or less what you need, but the API is a bit off. You want a string with pluses and minuses, but the class in this package returns a list of ChunkDiff objects:

class FunkyDiffer
{
    /**
     * @param array $from Lines
     * @param array $to Lines to compare to
     * @return array|ChunkDiff[]
     */
    public function diff(array $from, array $to): array
    {
        // ...
    }
}

Besides offering a strange API, the package is being "maintained" by someone you've never heard of (and it has 15 open issues and 7 pull requests). So you need to protect the stability of your package and you define your own interface. Then you add an Adapter class which implements your interface, yet delegates the work to the FunkyDiffer class:

// Your own interface:

interface Differ
{
    public function generate(string $from, string $to): string;
}

// The Adapter class:

final class DifferUsesFunkyDiffer implements Differ
{
    private $funkyDiffer;

    public function __construct(FunkyDiffer $funkyDiffer)
    {
        $this->funkyDiffer = $funkyDiffer;
    }

    public function generate(string $from, string $to): string
    {
        return implode(
            "\n", 
            $this->funkyDiffer->diff(
                explode("\n", $from),
                explode("\n", $to)
            )
        );
    }
}

The advantage of this approach is that from now on you can always switch to a different library, without changing the bulk of your code. Only the adapter class needs to be rewritten to use that other library.

By the way, a good old Façade might be an option here too, since it would hide the use of the third-party implementation. However, due to the lack of an explicit interface, you wouldn't be able to experiment with alternative implementations. If the code is part of a package, the same goes for its users: they won't be able to write their own implementation of a "differ".

// A simple Façade, no explicit interface:

final class Differ
{
    public function generate(string $from, string $to): string
    {
        $funkyDiffer = new FunkyDiffer();

        // delegate to FunkyDiffer
    }
}

If you want to introduce an abstraction for multiple specific things

If you want to treat different, specific classes in some way that is the same for every one of them, you should introduce an interface that covers their common ground. Such an interface is often called an "abstraction", because it abstracts away the details that don't matter to the client of that interface. A nice example is the VoterInterface from the Symfony Security component. Every application has its own authorization logic, but Symfony’s AccessDecisionManager doesn't care about the exact rules. It can deal with any voter you write, as long as it implements VoterInterface and works according to the instructions provided by the documentation of that interface. An example of such an implementation:

final class MySpecificVoter implements VoterInterface
{
    public function vote(
        TokenInterface $token, 
        $subject, 
        array $attributes
    ): int {
        // ...
    } 
}

In the case of the VoterInterface, the package maintainers serve the users of their package by offering them a way to provide their own authorization rules. But sometimes an abstraction is only there for the code in the package itself. In that case too, don’t hesitate to add it.

If you foresee that the user wants to replace part of the object hierarchy

In most cases, a final class is the best thing you can create. If a user doesn't like your class, they can simply choose not to use it. However, if you're building up a hierarchy of objects you should introduce an interface for every class. That way the user can replace a particular piece of logic somewhere in that hierarchy with their own logic. It will make your code useful in as many situations as possible.

A nice example comes from Tactician, which offers a command bus implementation.

The package ships with a CommandBus class. It's a class, because its implicit interface isn't larger than its explicit interface would be - the only public method is handle().

class CommandBus
{
    // ...

    public function __construct(array $middleware)
    {
        // ...
    }

    public function handle($command)
    {
        // ...
    }

    // ...
}

To set up a working CommandBus instance, you need to instantiate a number of "middleware" classes, which all implement the Middleware interface:

interface Middleware
{
    public function execute($command, callable $next);
}

This is an example of an interface that was introduced as an abstraction, allowing the package maintainer to treat multiple specific things in some generic way, as well as to allow users to plug in their own specific implementations.

One of those middlewares is the CommandHandlerMiddleware, which itself needs a "command name extractor", a "handler locator" and a "method name inflector". All of which have a default implementation inside the package (the command name is the class name, the handler for a command is kept in memory, the handle method is "handle" plus the name of the command):

$handlerMiddleware = new CommandHandlerMiddleware(
    new ClassNameExtractor(),
    new InMemoryLocator([...]),
    new HandleClassNameInflector()
);

$commandBus = new CommandBus(
    [
        // ...,
        $handlerMiddleware,
        // ...
    ]
);

Each collaborating object that gets injected into CommandHandlerMiddleware can easily be replaced by re-implementing the interfaces of these objects (CommandNameExtractor, HandlerLocator and MethodNameInflector respectively). Because CommandHandlerMiddleware depends on interfaces, not on concrete classes, it will remain useful for its users, even if they want to replace part of the built-in logic with their own logic. For example when they would like to use their favorite service locator to retrieve the command handler from.

By the way, adding an interface for those collaborating objects also helps the user to decorate existing implementations of the interface by using object composition.

For everything else: stick to a final class

If your situation doesn't match any of the ones described above, most likely the best thing you can do is not to add an interface, and just stick to using a class, preferably a final class. The advantage of marking a class as "final" is that subclassing is no longer an officially supported way of modifying the behavior of a class. This saves you from a lot of trouble later on when you're changing that class: you won’t have to worry about users who rely on your class's internals in some unexpected way. This advice applies to both package and application developers by the way.

Classes that almost never need an interface are:

  • Classes that model some concept from your domain.
  • Classes that otherwise represent stateful objects (as opposed to classes that represent stateless services).
  • Classes that represent a particular piece of business logic, or a calculation.

What these types of classes have in common is that it's not at all needed nor desirable to swap their implementations out. More specifically, some good examples of these classes are:

  • Entities
  • Value objects
  • Domain services
  • Application services

The reason is that these are classes from the domain or application layer. A domain model should model parts of your business domain, and it doesn't make sense to prepare for certain elements of that model to be replaced. Making things as concrete and specific as possible is usually good advice in this realm. As for application services: they reflect the use cases of the application these services belong to. It would be weird if you'd aim for replaceability and extensibility of these classes. They are unique to this application. So again: let them be plain old classes.

PHP design Comments