We’re not going to have any User Interface at this stage of development. No views, no controllers: we’ll just create and test our model. This should help web developers who could not even imagine approaching an architecture like this. Here is the directories structure I suggest for the app
Automated unit tests will go to the “tests” directory. “Services” is for Service Layer, “Domain” is for Domain Layer and “Infrastructure” is the place where some application logic will go to keep our services clear (e.g. special class for sending emails). To find out more about layering in general and about this particular choice I recommend to read a book by Eric Evans called “Domain Driven Design”, the “Layered Architecture” part. The “configs” directory is to hold all the configuration files our application may need.
Things to get started
PHP 5.3
Doctrine 2
PHPUnit
Zend Framework 1
MySql
Dependencies
There are many ways to manage dependencies: global variables, registry, Inversion of Control, Service Locator pattern. A good container is always better in real life, however for simplicity sake I will use Service Locator pattern in this application, to avoid extra dependencies.
Config
At this moment we’re just going to have one configuration file: “configs/config.ini”. To access the file I’ll utilize Zend\Config. You’ll see how we do it in the ServiceLocator class.
APPLICATION\PATH constant
APPLICATION\PATH is defined everywhere and refers to the “application” directory. It should be defined in any entry point our application has. For instance when we add MVC layer and use index.php file in Document Root on our web server - that would be the place where the constant is defined. Another example is bootstrap.php which I’m going to use as entry point for our Test Suite.
ORM
I’m going to use Doctrine’s EntityManager without any additional abstractions added. EntityManager will be used from services only for persisting entities and for transactions. Repositories, however, will be only retrieved from ServiceLocator.
It is not generally a good practice to depend on any ORM in your services. While it’s not a matter of this series of articles you may want to add an additional abstraction layer in your projects.
We also need a directory to store proxies for domain entities that Doctrine ORM generates. Those are going to “Infrastructure/Proxies”.
Adjustable Doctrine parameters will go to the config.ini file.
The First Task
As starting point I chose the process of registering a company in the system. This seems to be logical as it’s actually the starting point for the end-users. First, let’s describe the process once again:
In order to register a company, customers enter their company name, identifier for the subscription plan’s they choose and data for their first admin user account: email, name and password repeated twice to avoid mistakes. As the result: a message with email confirmation link is sent, a new not activated company is created and new user admin account is created.
Now, let’s stick to terms of object oriented programming and define objects this process includes and known domain logic details:
Company
has a unique identifier for reference
has a not empty name
has an associated subscription plan
has at least one user and that user must be administrator
may be either activated or not activated
is not activated by default
has a collection of users belonging to it
User
has a unique identifier for reference
has a name
has a valid email
has a password not shorter than 6 characters, hashed
may be either admin or not admin (admin has some special privileges)
is not admin by default
there is a way to define whether a user is an admin or not
belongs to a single company
Subscription (plan)
has a unique identifier for reference
has a name
To hold the process itself we’re going to use a Service Layer method, so let it be CompanyService and registerCompany method which will do the following:
finds the subscription plan by its identifier in the data storage
error if the plan is for some reason not found
error if two passwords provided are not equal
creates new user admin account based on the email, name and password provided
creates company based on company name provided, new admin user and the subscription plan found
saves the new company in the data storage
Note how the requirements translate into code:
Adding missing parts to the ServiceLocator class:
and add parameters doctrine needs to config.ini
To conclude, here is the result structure of the project’s directory:
Comments and feedback are much appreciated. See contact details here