Open RWOverdijk opened 11 years ago
+1
The problem is, that we can't give a context to the ZF2 DI.
I do know mess generated by static call like Zend_Registry. I made this ServiceLocatorFactory to allow getting service locator from Model Factory, and for sure not directly from concret class.
If you wan't to get the context, you have to get your service locator from your controller, and so degrade your factory signature with Service Locator for instanciation from a controller.
For example, a factory of yours allow you to get a cache version, and a normal one with decorator pattern.
class myFactory
{
private function __construct()
{
}
public static function getInstance($noCache = false)
{
// Some stuff here
if ($noCache) {
return $concretClass;
} else {
return $cacheClass;
}
}
}
If you wan't to use that kind of architecture, how can you with actual DI without making mess in your controller? (It's a real question, i'm interrested! :) )
That's not true. The factory gets the actual ServiceManager instance already. If your architecture is setup correctly, there's no need to fetch this statically.
It was made difficult on purpose, so you'd know that your application architecture is wrong.
Update So ServiceFactory::createService($sm); gets the ServiceLocator.
Indeed, it do. but the real question is the context, not simply get the ServiceManager. If I can't give a context to my factory, I can't get contextual Factory and it's a probleme.
How can you inject context to your Factory ?
I have no idea what you're talking about. Could you try explaining it? No offense but your English is a bit difficult to understand.
No problem about it, i do know about my weird english.
Imagin that you want to choose in one of your service, for example a data access object,on wich source of data it will be plug, depeding on context.
In on case you would like to get data from a local source (a database), in other one from a distant webservice.
The service manager allow us to get our service where ever we want to get it, that great, but it dosn't allow us to set option for the createService() method.
If I want to be sure that only dependancy for the source I will use will be inject, how can I set it up?
<?php
namespace Corp\News;
use Corp\News\DAO\NewsWebservice;
use Corp\News\DAO\NewsDatabase;
use Corp\News\NewsDAOInterface;
use Zend\ServiceManager\ServiceManager;
use ServiceLocatorFactory\ServiceLocatorFactory;
class NewsDAOFactory
{
private function __construct()
{
}
/**
* My news Factory determine my concrete class depending on option (context)
* @return \Corp\News\NewsDAOInterface
*/
public static function getInstance($localData = true)
{
// My default param $localData is set to true
if($localData === true) {
$sm = ServiceLocatorFactory::getInstance();
$em = $sm->get('doctrine.entitymanager.orm_default');
// Return a concrete NewsDAO which is plug to a database
return new NewsDatabase($em);
} else {
$sm = ServiceLocatorFactory::getInstance();
$webservice = $sm->get('webservice.news');
// Return a concrete NewsDAO which is plug to a webservice
return new NewsWebservice($webservice);
}
}
}
In your module.config.php:
<?php
return array(
'service_manager' => array(
'factories' => array(
'Corp\News\NewsDAOFactory' => function($sm) {
return new \Corp\News\NewsDAOFactory($sm);
}
),
),
);
In your NewsDAOFactory.php:
<?php
namespace Corp\News;
use Corp\News\DAO\NewsWebservice;
use Corp\News\DAO\NewsDatabase;
use Corp\News\NewsDAOInterface;
use Zend\ServiceManager\ServiceManager;
use ServiceLocatorFactory\ServiceLocatorFactory;
class NewsDAOFactory
{
protected $serviceManager;
public function __construct($serviceManager)
{
$this->serviceManager = $serviceManager;
}
}
Now, if you do $factory = $servicemanager->get('Corp\News\NewsDAOFactory');
it will have the serviceManager injected.
You could also make your "factory" implement the interface to have it set, and add the module as an invokable.
Factories: https://github.com/RWOverdijk/AssetManager/blob/master/src/AssetManager/Service/AssetFilterManagerServiceFactory.php https://github.com/RWOverdijk/AssetManager/blob/master/config/module.config.php#L6
Interface: https://github.com/RWOverdijk/AssetManager/blob/master/src/AssetManager/Service/AssetFilterManager.php#L13 https://github.com/RWOverdijk/AssetManager/blob/master/src/AssetManager/Service/AssetFilterManager.php#L170
Yea this is a bad idea, it goes against DI.
Instead of tightly coupling your object to their dependencies, you're tightly coupling your objectes to the serviceManager.
Like @RWOverdijk illustrated - use injection.
@RWOverdijk
I'm look at your example $factory = $servicemanager->get('Corp\News\NewsDAOFactory');
And I see, that i need $servicemanager
to grab a factory for another service. But what if I need it deep in my business logic and I haven't access to $servicemanager
from this place?
How I can create needed instance or service without $servicemanager
?
That's why it's need to be called statically sometimes.
If you want to tell me about refactoring code and make it more flatten (not have many deep levels) - architecture became around $servicemanager
, because I can't write anything without access to $servicemanager
in this case.
So, for me problem not in options for factories, but in accessing $servicemanager
from deep business logic to create something from registered in $servicemanager
factories.
@RWOverdijk
for example I have Helper
which has method like findSomethingById($id)
and it's implemented with Doctrine
(so i need EntityManager
from ServiceLocator
)
I need use this Helper
method in many places where I can't inject it like dependency.
Also inside this method I need not to interact with Doctrine EntityRepository
directly, but from some Model
, which uses EntityRepository
inside. So in method findSomethingById($id)
I need to get this Model
(which need ServiceLocator
to init EntityManager
) and some times I need use the Model
from other place where I haven't access to ServiceLocator
.
So, in my example I have places in code, where I need from ServiceLocator
call Model
factory and inject ServiceLocator
into Model
to call Doctrine EntityRepository
later.
But In this place I haven't access to ServiceLocator
! Or I need to call Model
in static method.
Only static call for ServiceLocator
can solve this situation in this case.
And I need call Model
from ONE PLACE because I want to change it to another Model
if I need (change storage to Mongo). That's why I use static method Registry::getModelRequest();
and inside Registry
I can get service or factory from static ServiceLocator
and change it only in this place, If I need change Model
instance for whole app.
@RWOverdijk
example pseudo code
<?php
class Helper {
public static method findSomethingById($id) {
//prepare and check code
//...
$model = Registy::getModel();
$found = $model->find($id);
return $found;
}
public static updateSomething($id, $data) {
$model = Registy::getModel();
$result = $model->update($id, $data);
return $result;
}
}
class Registry {
public static getModel() {
//there I call ServiceLocator
$sm = ServiceLocatorFactory::getInstance();
$model = $sm->get('mysqlModel'); //or mongoModel
$model->setSM($sm);
//or call factory and factory use config inside, but I need ServiceLocator to call factory and inject it to model :)
//use config
$model = $sm->get('modelFactory');
return $model;
}
}
class DoctrineModel {
public function setSM($sm) {
$this->sm = $sm;
}
public function find ($id) {
//there I need $sm
$repo = $this->sm->get(Doctrine\EntityManager)->getRepository('blahblah');
return $repo->find($id);
}
}
//and client code
class SomeClient {
private finction someMethod() {
$id = '1';
$entity = Helper::findSomethingById($id);
}
}
How solve it without static call? )
There's a reason why the servicemanager can't be accessed statically. (It can, but we're not advertising it). It generates a mess (remember Zend_Registry?)