service layer pattern implementation in Phalcon

I have looked at the GitHub examples at https://github.com/phalcon/mvc/tree/master/multiple-service-layer-model. There is a service layer pattern implementation but it is very simple. It does not perform any real operation - because of that it looks a little bit abstract (contrary to other Phalcon's valueable examples like vokuro, invo etc).

Does anyone can provide any code of implementation of the service layer pattern in Phalcon? I mean the code that actually do something? Some GitHub's repo? Some articles? Fragments (even small) of your own code?

I know this forum might not be the best place to look for information how service layer shoud be implemented but I have read some articles recently and still have problems how it should be done good under Phalcon. Thanks a lot in advance!



84.5k

What problems are you having with that example? It just use a common directory for models that can be used between modules



17.4k
edited Sep '14

A little misunderstanding. I don't have any problems with that example at all! ;)

I have found lot of articles about the service layer pattern in the Internet. The problem is they are all theoretical and my mind is messed up. I am trying to figure out how this pattern should be implemented in practice. I would like to see how real tasks are performed within this pattern under PHP MVC framework. The best would be Phalcon as this is the only framework I have ever used.



9.9k
edited Sep '14

We have recently rewritten out project to use the service layer approach, mostly based on the github example form your link. The basic project structure is similar, but slightly modified to better fit a medium size project - screenshot

The service loader is once again, is once again a modified version from the example.

abstract class Services
{
    private static $registry = null;

    public static function getService($name)
    {
        if( self::$registry == null) {
             self::$registry = new \Phalcon\Registry();
             //self::$registry = new \Phalcon\DI();
        }

        if( !isset(self::$registry->{$name})) { 
            $className = "\\Xxx\\Services\\Service\\{$name}";

            if ( ! class_exists($className)) {
                throw new Exceptions\InvalidServiceException("Class {$className} doesn't exists.");
            }
            self::$registry->{$name} = new $className();
        }

        return self::$registry->{$name};
    }

        /**
    * Returns account service
    *
    * @return \Xxx\Services\Service\Account
    */
    public static function getAccountService()
    {
        return self::getService('account');
    }

    (...)
}

Identical function is used for loading model repositories and with all logic being moved to services, actions become really small.

All services throw a common ‘control’ exception in case of an error, which is then caught and converted into a user friendly response – JSON in this case. We also have a bunch of helper function for getting the most popular services (like Services::getAccountService() ), but they serve purely aesthetic purpose for IDE autocomplete to work correctly :)

    public function checkEmailAction() {
        try {
            Services::getAccountService()->checkEmail($this->request);
        } catch (InternalServiceException $e) {
            $this->returnErrorResponse($e->getMessage());
        }            
    }

Hope this helps a bit. Let me know if you’d like some more info!

edited Sep '14

Disclaimers

  1. Not a CS graduate, just self taught since 1999
  2. Not an expert on design patterns/principles, just try my best
  3. 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