Feb 11, 2015 | namespaces, php

How to Organize Class Namespaces

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

In A Brief Introduction to PHP Namespacing, I gave a quick introduction both to namespaces and to how PHP handles them.

But there are some higher-level architectural concerns regarding namespaces that a lot of folks have brought up to me recently, so I figured I'd get a little bit out on "paper".

I've seen a few primary ways of organizing namespaces. I'll discuss the pros and cons of each.

Notes Before We Begin

  • This is not an instructional post, because there may not be one method that is optimal for every situation. It's merely a discussion of a few options, and it's primarily targeted at simple namespace organization in a small-to-medium-sized app--not the enterprise, and not necessarily those with intricate architectural concerns.
  • "Small" and "Medium" and "Large" here purely relate to the number of classes & entities, and have nothing to do with Lines of Code, the number of users of the site, or anything else.
  • We'll be using the example of a Command that sends a Receipt to a User.
  • Our global namespace right now will be App just for brevity, but you can replace that with Vendor\Package.

What's the purpose of namespaces?

Before we even get to talking about the specific ways we can namespace, let's talk about why we're doing it. I'm indebted to Shawn McCool (as always) for helping connect some of my vague thoughts here to actual computer science concepts.

As Shawn pointed out to me, the purpose of namespacing is cohesion: describing how connected some code is to other code. He pointed out that in other languages, namespaces are called "Packages" or "Modules"--and once you realize this, you understand that we're seeing sub-namespaces as little individual modules that should rely on other modules as little as possible (encapsulation). If modularity is one of the primary end goals of our namespacing, then that becomes one (of several) metrics we can use to judge a particular style of namespacing.

Of course, even this statement--that modularity is a primary end goal of namespacing--is under debate. But when I hear it, I like it.

Namespacing Approaches

OK, let's get down to it.

Global namespacing

<?php namespace App;

class SendReceipt {}
src
    Receipt
    ReceiptRepository
    SendReceipt
    SendReceiptHandler
    User
    UserRepository
    CreateUser
    CreateUserHandler

Pros

I guess it's simpler to not have to deal with sub-namespaces? On a very small app, this might be fine. If you have five classes, who's saying you need to sub-namespace them at all? If this is a single package with a single purpose, or an application that has a single "module", it may not need anything other than a single global namespace.

Cons

The moment you get an application of any complexity, it's going to be hard to find your classes in the huge mush of your global namespace. If you have any separation of identity or purpose among your types of classes--for example, Users vs. Receipts--this global namespacing throws them together in a big pot. Not modular at all.

Group by pattern

<?php namespace App\Commands;

class SendReceipt {}
src
    Commands
        SendReceipt
        CreateUser
    Entities
        Receipt
        User
    Handlers
        SendReceiptHandler
        CreateUserHandler
    Repositories
        ReceiptRepository
        UserRepository

Pros

When you want to hunt down a command, you know exactly where it lives. If your brain says "I need to edit one of my commands. Which one? The one that sends receipts", this is a good fit. This is one level more organized than ignoring namespaces, but not so deep that you'll be annoyed using it in a medium-sized site.

Additionally, your related classes (e.g. commands) can live next to each other; you can see any parallels there may be between SendReceipt and SendReminder, for example, and see how they all connect to each other.

This method also allows you to architect relationships between class types programatically. For example, a Command Bus might know that the Handler for a Command (which lives at App\Commands\{commandName}) always lives at App\Handlers\{commandName}Handler.

Cons

Doing it this way leaves you with classes in the same context spread across many different namespaces. For example, you might have App\Commands\SendReceipt, App\Receipt or App\Entities\Receipt, App\Providers\ReceiptServiceProvider, App\Handlers\Commands\SendReceiptHandler, App\Repositories\ReceiptRepository, and on and on. All of your Receipt logic, sprinkled all over the place.

If we're focusing on encapsulation and modularity, this grouping is not winning. Because we've spread all of the code about billing, for example, across our entire namespace landscape, the class organization isn't focusing on creating a billing module. Classes are next to each other purely because they happen to follow the same architectural pattern, not because they're actually related.

Group by context

<?php namespace App\Billing;

class SendReceipt {}
src
    Billing
        Receipt
        ReceiptRepository
        SendReceipt
        SendReceiptHandler
    User
        User
        UserRepository
        CreateUser
        CreateUserHandler

Pros

If you're purely working in Billing right now, you know you'll have everything billing-related together in one spot. For your receipts, the entity, the command, the command handler, the repository, and so on--all together in one nice, neat bundle, easy to address as a single group.

This is where we start experiencing encapsulation and modularity. All of our Billing-related classes, regardless of their design pattern, are together in one place--which helps us group them mentally, even starting to think of them as a unit which may be able to live external to this application.

Cons

Your commands are now sprinkled across the code base. Your repositories, too. And your entities. And your command handlers.

Group by context and pattern

<?php namespace App\Billing\Commands;

class SendReceipt {}
src
    Billing
        Entities
            Receipt
        Repositories
            ReceiptRepository
        Commands
            SendReceipt
        Handlers
            SendReceiptHandler
    User
        Entities
            User
        Repositories
            UserRepository
        Commands
            CreateUser
        Handlers
            CreateUserHandler

Pros

Separating it this way gives you the greatest level of namespace separation--that alone is a pro for some people. This is especially useful if you have a large codebase with a lot of classes--the more classes, the more you'll appreciate additional options for separation. Imagine adding an UpdateUser command, a DeleteUser command, a Subscription entity and repository and associated handlers...

Just like Group by pattern, you can programatically relate classes.

And while your classes are separated by pattern, they're still grouped by the context, so you still do have all of your Receipt code together in one place. We still get the benefit of modularity that we did in Group by context.

Cons

The longer the namespace definition is for your class, the more mental energy has to be spent understanding the entire namespace stack. There's more opportunity for typos and confusion. And with a small or medium-sized application, this may seem overkill.

Since you're grouping your classes by pattern at the lowest level, you don't get as much of the grouped-by-context benefit as the Group by context style.

Sample

OK, so that's a lot of abstract theory about it. But what about a concrete example? I've taken a few classes from SaveMyProposals as an example. Let's look at how we manage Talks, Conferences, and how we propose talks to conferences:

Global namespacing

app
    Conference
    ConferenceRepository
    CreateConference
    CreateConferenceHandler
    CreateTalk
    CreateTalkHandler
    DeleteConference
    DeleteConferenceHandler
    DeleteTalk
    DeleteTalkHandler
    ProposeTalkToConference
    ProposeTalkToConferenceHandler
    RetractTalkProposal
    RetractTalkProposalHandler
    Talk
    TalkRepository
    UpdateConference
    UpdateConferenceHandler
    UpdateTalk
    UpdateTalkHandler

Group by pattern

app
    Commands
        CreateConference
        CreateTalk
        DeleteConference
        DeleteProposal
        DeleteTalk
        ProposeTalkToConference
        RetractTalkProposal
        UpdateConference
        UpdateTalk
    Entities
        Conference
        Proposal
        Talk
    Handlers
        CreateConferenceHandler
        CreateTalkHandler
        CreateProposalHandler
        DeleteConferenceHandler
        DeleteProposalHandler
        DeleteTalkHandler
        ProposeTalkToConferenceHandler
        RetractTalkProposalHandler
        UpdateConferenceHandler
        UpdateTalkHandler
    Repositories
        ConferenceRepository
        TalkRepository

Group by context

app
    Conferences
        Conference
        ConferenceRepository
        CreateConference
        CreateConferenceHandler
        DeleteConference
        DeleteConferenceHandler
        UpdateConference
        UpdateConferenceHandler
    Talks
        CreateTalk
        CreateTalkHandler
        DeleteTalk
        DeleteTalkHandler
        ProposeTalkToConference
        ProposeTalkToConferenceHandler
        Talk
        TalkRepository
        RetractTalkProposal
        RetractTalkProposalHandler
        UpdateTalk
        UpdateTalkHandler

Group by context and pattern

app
    Conferences
        Commands
            CreateConference
            DeleteConference
            UpdateConference
        Entities
            Conference
        Handlers
            CreateConferenceHandler
            DeleteConferenceHandler
            UpdateConferenceHandler
        Repositories
            ConferenceRepository
    Talks
        Commands
            CreateTalk
            DeleteTalk
            ProposeTalkToConference
            RetractTalkProposal
            UpdateTalk
        Entities
            Talk
        Handlers
            CreateTalkHandler
            DeleteTalkHandler
            ProposeTalkToConferenceHandler
            RetractTalkProposalHandler
            UpdateTalkHandler
        Repositories
            TalkRepository

Conclusion

So, what's the answer?

It depends.

It's possible that the simpler organizational structures work better for applications with less classes & entities, whereas the larger organizational structures match better with the more robust organizational systems. But that's not a hard rule. I'm not even 100% sure it's a rule at all.

I think the modularity & encapsulation ideas are work giving your brain some time with. Think about how you would design it if each sub-namespace were to be removed from the others.

But in the end, I'd say just try them all out. Figure out what you like. Figure out what bugs you. Figure out what benefits you gain from each. You'll figure this out.


Comments? I'm @stauffermatt on Twitter


Tags: namespaces  •  php

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.