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 aReceipt
to aUser
. - Our global namespace right now will be
App
just for brevity, but you can replace that withVendor\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