Disclaimers
- Not a CS graduate, just self taught since 1999
- Not an expert on design patterns/principles, just try my best
- Have spent the last year building out an "onion architecture" based RESTful codebase
Main Components
- Controllers (resources)
- Services (Service Pattern, Service Facade, Boundary)
- Repositories (Repository Pattern, DAO - Data Access Object)
- Queries (to keep repos a bit cleaner we push out ORM query builder creation to individual query classes)
- Entity (model, entity - represents a row in a table)
- DTO and assemblers (Data Transfer Object)
- Request / Response Mappers (turn DTO into a json/xml object)
- Factories (Factory Pattern, Factory Method Pattern)
DI / SL / IoC
Dependency Injection, Service Locator, Inversion of Control.
We currently use Phalcon\Di as a service locator. This is an anti-pattern however. We have factories and the normal request, response, dispatcher framework components loaded using closures (lazy loading).
We are currently in the process of switching to a DI container that recursively builds out dependencies using constructor injection.
An example:
$di->set('queryFactory', function () use ($di) {
return new \Example\Query\Factory($di);
}, true);
A service factory would then be injected with the DIC so it can use it to load up service objects.
Namespaces
We have our code organized as such:
- Example\Controller\UserController, Example\Controller\User\AddressController
- Example\Service\UserService, Example\Service\User\AddressService
- Example\Repository\UserRepo, Example\Repository\User\AddressRepository
- Example\Query\UserQuery, Example\Query\User\AddressQuery
- Example\Dto\UserDto, Example\Dto\User\AddressDto
- Example\Entity\UserEntity, Example\Entity\User\AddressEntity
Some factories:
- Example\Service\Factory
- Example\Repository\Factory
- Example\Query\Factory
- Example\Dto\Factory
- Example\Entity\Factory
Services
Services are called from controllers. We aim for thin controllers. An example might look like below (formatting for brevity, not PSR standards). Services are responsible for business logic, filtering, validation, etc.
namespace Example\Controller;
class UserController extends BaseController {
/** GET /users/{id} */
public function getInstance() {
// access service factory
$user = $this->serviceFactory
// create a service, the context is "user" which just maps to a class via yaml config
->create('user')
// call the service->findById() and pass in the id from route match
->findById($this->dispatcher->getParam('id', 'int'));
}
}
The service probably looks like:
namespace Example\Service;
class UserService {
public function findById($id) {
// do some business stuff
if (!$this->isValidId($id)) { throw Exception; }
// userRepo is injected or retrieved from service locator
return $this->userRepo->findById($id);
}
}
Repositories
Also DAO I guess? Repository is responsible for talking to database layer.
To keep repository methods smaller, we break out bigger query building code into query classes when appropriate.
namespace Example\Repository;
class UserRepo {
public function findById($id) {
// queryFactory is injected or retrieved via service locator
return $this->queryFactory
// user query factory to return a UserQuery object
->create('user')
// pass the id to the query object, real implementation could vary
->set('id', $id)
// the build method just creates a QueryBuilder object (ORM) and returns it
->build();
}
}
Conclusion
You can probably figure out where the rest of this is going. Some of the design principle stuff we tried to go with was avoiding "new Blah" dependencies in our code (hence factories for everything).
We are also working on switching from service location to injection (better testability).
Things we make big attempts to avoid - singletons, static methods/properties, hard coding of class names, follow PSR standards
PhalconPHP Components
Off the top of my head, we use these:
- Di
- Dispatcher
- Controller
- Http Request and Response
- Class autoloader
- File logger
- Filter/validator