I'm looking for a good example of how to correctly implement Service Layer with Zend Framework and Doctrine2
I've seen some implementations but all of them have access to the EM from the controller when instantiating the service, and I think that might be wrong or not?
Exmaple:
http://cobbweb.me/2010/11/integrate-doctrine-2-zend-framework-application/
Also I got to this project but not really sure how to implement it:
Thanks
Use a helper to act like a factory for the services:
You need to create an Action Helper and inject the EntityManager on it when you register the instance in the Front Controller.
This Action Helper receives as parameter in the direct() method the name of the service class that the factory should create.
Inside this method you should try to instantiate the service class requested, and return it (or throw an exception if the same is not found).
Let your service classes receive as parameter in the constructor the EntityManager and inject it during the construction on the factory.
The rest should already be clear to you. In your controller you only need to use something like:
SomeController extends Zend_Controller_Action {
//...
public function someAction ()
{
$myService = $this->_helper->service( 'MyService' );
$myService->doSomething();
}
}
Related
I have made a repository pattern app, having a repo and interface:
class UserRepository extends EloquentRepository implements UserRepositoryInterface
{
public function __construct()
{
$this->model = new User();
}
...
}
The repository and interfaces as well as extended class and its interface is registered in service provider and is called on app boot.
The questions I have are:
Is there a need to watch out for the order of registering? For example, should EloquentRepository class be loaded before the
repo, or does Laravel handle that on its own?
In case I inject UserRepositoryInterface in a controller, is the constructor method called automatically even though I didn't really new-up a class?
How long does the DI injection "live"? If I inject it in a page controller which calls some other controller and needs the same dependency, does the constructor call twice then, and operate separately in each controller?
Is there a difference if I call it like App::make() instead of DI?
Is there a need to watch out for the order of registering? For example, should EloquentRepository class be loaded before the repo, or does Laravel handle that on its own?
I don't quite understand where you would load EloquentRepository as (from the code posted) it seems you're only extending it. Which shouldn't be a problem.
In case I inject UserRepositoryInterface in a controller, is the constructor method called automatically even though I didn't really new-up a class?
Yes. Most of Laravel's main classes (controllers included) are loaded with DI in mind and the dependencies will be resolved automatically.
That being said, since you are injecting an interface and an interface by default cannot be initialized as a class, since it has no implementation - you need to bind an implementation to the interface first in order to use it.
How long does the DI injection "live"? If I inject it in a page controller which calls some other controller and needs the same dependency, does the constructor call twice then, and operate separately in each controller?
My understanding is that a new instance of the class will be created when the next controller is initialized. Unless you bind a class as a singleton.
Is there a difference if I call it like App::make() instead of DI?
App::make(some::class) will automatically resolve the dependencies of class some.
For example:
namespace App;
use App\Dependancy;
class Test
{
protected $d;
public function __construct(Dependancy $d)
{
$this->d = $d;
}
}
If you call this in the controller: $a = new \App\Test() you will get an error that \App\Test constructor expects class Dependency as first parameter.
But if you initialize it like this: $a = \App::make(\App\Test::class) the Dependency will be automatically resolved.
try to make the repositories abstract in the controllers and inject these through constructor.
like this here:
public function __construct(EloquentRepository $repository)
{
$this->repository = $repository;
}
And in the AppServiceProvider you can inject repositories you will need.
public function boot()
{
// provides any Repository in SomeController
$this->app->when(SomeController::class)
->needs(EloquentRepository::class)
->give(function (Application $app) {
return $app->make(SomeRepositoryInterface::class)
});
}
I'm using Apigility, built on ZF2. Once request is dispatched to the controller's action, I need to choose proper adapter to hanle request - based on incomming parameters.
Normally, Controller is instantiated by ControllerFactory, where you can provide all dependencies, let say I need som kind of mapper class to be injected. It's easy, if I know, which one I will use in within the controller. It's problematic if I need to let controller decide which mapper to use.
Let's say user is requesting something like getStatus with param 'adapter1' and another user is accessing same action, but with param 'adapter2'.
So, I need to inject adapter1 mapper OR adapter2 mapper, which has similar interface, but different constructor.
What's the proper way how to handle this situation ?
On possible solution is to supply some kind of factory method, which will provide requested adapter, but - using the SM int the model class should be avoided.
Another way is to use SM directly in within Controller's action, but this in not best approach, because I can't reuse 'switch-case' logic for another actions / controllers.
How to handle this, please ?
You could use controller plugins for this.
Like that you can get the adapter inside your controller when you need it without injecting a ServiceManager and without adding all the logic to the factory. The adapter will only be instantiated when you request it in your controller action method.
First you need to create your controller plugin class (extending Zend\Mvc\Controller\Plugin\AbstractPlugin):
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AdapterPlugin extends AbstractPlugin{
protected $adapterProviderService;
public function __constuct(AdapterProviderService $adapterProviderService){
$this->adapterProviderService = $adapterProviderService;
}
public function getAdapter($param){
// get the adapter using the param passed from controller
}
}
Then a factory to inject your service in the class:
<?php
namespace Application\Controller\Plugin\Factory;
use Application\Controller\Plugin\AdapterPlugin;
class AdapterPluginFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceController
* #return AdapterPlugin
*/
public function createService(ServiceLocatorInterface $serviceController)
{
$serviceManager = $serviceController->getServiceLocator();
$adapterProvicerService = $serviceManager>get('Application\Service\AdapterProviderService');
return new AdapterPlugin($adapterProviderService);
}
}
Then you need to register your plugin in your module.config.php:
<?php
return array(
//...
'controller_plugins' => array(
'factories' => array(
'AdapterPlugin' => 'Application\Controller\Plugin\Factory\AdapterPluginFactory',
)
),
// ...
);
Now you can use it inside your controller action like this:
protected function controllerAction(){
$plugin = $this->plugin('AdapterPlugin');
// Get the param for getting the correct adapter
$param = $this->getParamForAdapter();
// now you can get the adapter using the plugin
$plugin->getAdapter($param);
}
Read more on controller plugins here in the documentation
for someone it might seem stupid, anyway I am new to the Symfony world and reading the documentation I came up with a doubt:
How does Symfony inject the DI Container instance inside of each Controller class which extends Controller in such a way that you can easily access a service from the container using the get($id) method??? Like here:
use Acme\HelloBundle\Newsletter\NewsletterManager;
// ...
public function sendNewsletterAction()
{
$mailer = $this->get('my_mailer');
$newsletter = new NewsletterManager($mailer);
// ...
}
I saw the source of the Controller class from GitHub, the Controller class in Symfony extends the abstract class ContainerAware which implements the ContainerAwareInterface interface, which has a method setContainer(ContainerInterface $container = null);
I can assume that the Controller itself attempts to set a reference to the Container instance calling setContainer inherited from the ContainerAware abstract class, but I am not sure whether I am right or not, but since I know that in Symfony2 every service (object with particular functionality) is under the supervision of the DI Container, who is responsible to inject the container to the Controller setContainer() setter? The Container itself? But how?
Thanks for the attention!
With controllers, Symfony2 uses the ControllerResolver class to determine if the Controller is an implementation of ContainerAwareInterface, and will inject the container that way. See this file (lines highlighted):
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php#L79-L82
As for other services, you will have to manually inject the container into them via service definitions.
I have problem or misunderstanding of zf2's serviceLocator. Which is the right way to get a library/class with serviceLocator, which need configuration in the __construct method.
For example:
class PhpVersion extends AbstractTest implements TestInterface
{
public function __construct($expectedVersion, $operator = '>=')
{
}
}
The problem is that, the ServiceLocator's get method requires only one variable - Libraries name, without any place for configuration or dependency
P.S. My solution is with making a factory, but I think there should be a smarter way.
you can create your own service locator by extending the zends service locator class and customizing the get method to accepts arguments and pass them on to the constructor of that object.
I have started to create a project using Symfony 2. I need to share data between all controllers.
I have added a base controller which extends symfony\controller and each of my controllers extends this base controller
class BaseController extends Controller
class HomeController extends BaseController
This base controller will be used for things like assigning global twig variables ( I know I can do this in the config but some of the variables will be gotten from other config files and database ).
So I thought I could reference container since Controller is container aware, however it isn't at the point I am using the functions (from constructor).
public function __construct ()
I have seen people mention passing the container in as a parameter and mention services but I have had a look and cannot figure it out. All I want to achieve is this:
public function __construct (Container $container) {
$container->get('twig').addGlobal('foo');
}
This is a common stumbling block to Symfony 2 newbies. The controller/container question has been asked hundreds of time before so you are not alone(hint).
Why doesn't your controller constructor code work?
Start by looking under vendor/symfony...FrameworkBundle/Controller/Controller.php. Hmm. No constructor there so where the heck is the container coming from? We see that Controller extends ContainerAware. That seems promising. We look at ContainerAware (the namespace helps to find where the file is) and once again, no constructor. There is however a setContainer method so we can assume that the container is injected into the controller after the constructor is called. Quite common in a dependency injection based framework.
So now we know why the constructor code fails. The container has not yet been injected. Stupid design right? Time for a different framework? Not really. Let's face it, having to have all your controllers extend a base controller just to get some twig variables set is not really the best design.
The Symfony way to execute code before the controller action is executed is to make a controller event listener. It will look something like this:
namespace Cerad\Bundle\CoreBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => array(
array('doTwig', 0), // 0 is just the priority
));
}
public function doTwig(FilterControllerEvent $event)
{
// Ignore sub requests
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) return;
$this->container->get('twig')->addGlobal('foo');
}
}
// This goes in services.yml
parameters:
cerad_core__model_event_listener__class:
Cerad\Bundle\CoreBundle\EventListener\ModelEventListener
services:
cerad_core__model_event_listener:
class: '%cerad_core__model_event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
So now we have the desired functionality without the need for a base controller class.
Notice also that the controller can be accessed through the event. Since the controller has been created but the action method not yet called, you could call controller methods or inject data directly into the controller. This is seldom needed. In most cases, you would add additional information to the request object which then gets injected into the controller's action method.
It's really a nice design once you get comfortable with listeners and services.
Please read carefully that question - Symfony2 passing data between bundles & controllers, try to use code included in it.
You can use service to solve your problem, for example.
If you look at the Controller class you'll se the following:
class Controller extends ContainerAware
This means you can retrieve twig from the container as simple as this:
$twig = $this->get('twig');
But I would recommend you to use custom twig extension in your case.