Namespaces in PHP are one of things that are left without a proper attention from the standard creators. While this gives a lot of space for creativity, this also results in a messy unpredictable packages structure, sometimes even when just one developer works on a package. Not to say when you work in a team.
The rules below are reflection of my experience of working on large projects in couple-of-pizzas teams.
All of them are based on the single principle: predictability.
Standard
Stick to PSR-4 Autoloader standard. A standard is good for predictability, and this one is a good one. If you are forced to use PSR-1 (or whatever) by the framework, that’s not a big deal. Just follow the rest of the rules, they are not dependant on PSR.
Public/internal
Put the classes that are a public API of your package as closer to the package root as it is possible.
Use deeper nesting level (sub-namespaces) for internal API – the components of the public API classes. Internal API classes should be treated as private.
Sub-namespaces
Sub-namespaces may be of three types:
- components,
- group of classes,
- subpackage.
Some rules apply to each type.
Components
Assuming you are using composition and SRP, your public API classes will be built from smaller components represented by other classes
(a DDD Aggregate is a good example). Normally these components are only used by their container class and should be treated as private/protected.
Components are put into a sub-namespace named by the container class.
Example:
1 | \Acme\ConfigReader // Public API, uses Merger and Parser as components. |
Here the \Acme\ConfigReader
class is a public API, and no one except it should work directly with \Acme\ConfigReader\Merger
or \Acme\ConfigReader\Parser
.
Of course, a component can act as a container for its own sub-components, so this rule applies recursively.
Group of classes
Some design patterns, for example Strategy, which I use often, require several classes that do the same sort of a thing but somehow differently. These classes are often represent different implementation of some Type (or Interface if you like) and are used in a polymorphic manner.
Such classes may be put into a sub-namespace named by their Type in a plural form.
Example:
1 | \Acme\ConfigReaders\Filesystem |
The Type should not be duplicated in the class names.
1 | // Incorrect |
Quite often you would also need to put a Type interface somewhere and a Factory that creates a final implementation based on some input.
Here is a common structure:
1 | \Acme\ConfigReaders\Filesystem // Internal API (concrete implementation), implements \Acme\ConfigReaderInterface |
Another use case for this technique is domain-level exceptions classes:
1 | \Acme\Exceptions\SomethingWeirdHappened |
However in this case exceptions are a public API of the package.
Subpackages
Sometimes you might want to organize your package into a subpackages. This may be valid, for example, when your package is a plug-in that affects the behavior of different sub-systems, i.e.: Catalog, Cart and Checkout.
Respective classes are then put into a sub-namespace named by the subpackage. This approach is similar to the components, but there is no container class in this structure.
Example:
1 | \Acme\Catalog\... (classes related to Catalog) |
Grouping by subpackages only makes sense if you have many classes to put in each of them.
Otherwise you can just mention the respective sub-system in a class name:
1 | \Acme\CatalogMessageRenderer |
Anyway I would suggest keeping your packages small. When you are going to introduce a subpackage consider creating a new package first.