On structuring PHP projects

An indispensable part of every programming project is how you structure it, which involves organizing files and sources into directories, naming conventions, and similar. As your application grows, so does the need for structuring it in way that it is easy to manage and maintain.

In most cases, structure of an average PHP-based application is dictated or influenced by the framework that is being used, which is something I'm opposed to.

With no intention to be intrusive and without any bias towards specific framework, I'll share with you how I think about organizing my PHP projects.

Directory structure

First impression is the most important they say, and when it comes to projects, you gain it over its directory structure. This is the one that works best for me:

This is pretty much standard for applications, but also for libraries, whereas in their case, it is unlikely to find public/ or templates/ directories. In addition, libraries typically have following type of files: docs/, CHANGELOG, LICENSE, README. Of course, applications can have these, too.

I consider this directory structure a framework-agnostic, and based on it I've successfully managed to build different projects that were using different frameworks, like Zend Expressive, Slim and Laravel.

Recently, I found out that there is a PHP-PDS initiative, which proposes standard for a directory structure of PHP packages. I strongly support such attempts, for the same reasons I admire PHP-FIG's work and efforts. I was happy to see that naming conventions that I use are fully in compliance with the proposed standard.

PHP source code

In relation to the introduced directory structure, you might be wondering: "What about the app/ directory?", and my answer to that is: there is no app/ directory, and there shouldn't be. Everything in addition to src/ introduces unnecessary level of complexity.

Now that we have PSR-4 and Composer autoloader, all you need to do is to use namespaces. So for example, instead of app/controllers/, you have src/Controller/. By doing so, directory structure becomes simple and unambiguous. Same goes for autoloading, since there is no need for any custom or framework-enabled autoloading mechanism. Single directive to composer.json does all the work:

{
"autoload": {
"psr-4": {
"MyApp\\": "src/",
}
}
}

Now that we've busted all the myths about where PHP sources should be located, let's consider options for organizing them inside src/ directory, because that's where the juice is.

Grouping by Archetype

Common trend is to organize things by their archetype, for example:

src/
Controller/
Entity/
Repository/
Service/

This is tolerable for smaller apps, but still it forces developer to often switch multiple directories when working on a certain feature. Also, this approach does not scale well, so in case of 20+ controllers, entities or whatever, directory structure becomes unwieldy and inefficient, and it gets more difficult for developers to find stuff.

Grouping by Feature

The idea here is that when you are looking for the code that makes a feature, it is located in one place. This approach results in an intuitive directory structure that speaks for itself, whereas code is organized based on functional areas. Only by looking inside src/ directory, developer can immediately gain insight into what the application does.

If we take a simple e-commerce website as an example, here's how things should be organized in accordance with this approach:

I didn't get too much into detail with actual classes, interfaces, etc., because I wanted to focus on grouping things.

You might argue that if for a feature, there are multiple services for example, it would however make sense to do some archetype grouping. You can introduce additional grouping by type within the feature, but I prefer a flatter structure until I realize a specific value in creating a new directory. For example, I see a value in keeping Controllers and CLI commands (I love Symfony Console) in special namespaces:

Separate domain and general-purpose code

There's a great chance that besides domain-specific code, there will be need for creating some general, reusable, framework-like code inside src/ directory, for example custom log handler (though it's most likely that Monolog already has it), cache facilitation, filters, and similar. That stuff is not your domain logic, it's not specific for your application and it's probably reusable between projects. What's more, it isn't supposed to be part of your application, but rather in a standalone library, and in a separate repository.

I've seen a solution, in which all the general-purpose code is put in a separate namespace, for example MyApp\Framework or MyApp\Common:

src/
Cart/
Framework/
Logger/
Cache/
Product/
User/

This is totally fine, but I see a drawback in this approach, which is not related to how it affects application that contains this general-purpose code, but the code itself. Remember, ideally, that code should be in a separate repository, and effort of moving it would also require to get rid of that extra "Framework" or "Common" part of the namespace, which in turn requires not only modifying all the class definitions, but also usages.

For this reason, I choose a completely different approach, whereas domain code itself is isolated in a separate namespace. What's important with this approach is that you wisely choose name for a top-level namespace. MyApp is probably not the best choice with regard to the issue of extracting general-purpose code into a separate repository. When devising a top-level namespace, think outside the scope a project you're working on. Typically, that should be the name of your company, brand or ultimately your own name. As for the namespace part of the domain code, it should typically describe what the application does, like Eshop for example, but I prefer to use a generic word for that purposes - Domain:

src/
Cache/
Domain/
Cart/
Product/
User/
Logger/

As a result, you end up with namespaces like Company\Cache and Company\Domain\User. Now, when you decide to extract that Cache facilitation into a separate, reusable library, no changes are required in relation to its namespace.

Final thoughts

Locating code needs to be intuitive and fast. Structuring the application accordingly saves a ton of developers' time and effort.