fezfez / ServiceLocatorFactory

Allow you to get ServiceManager from everywhere in your application
MIT License
18 stars 4 forks source link

static #1

Open RWOverdijk opened 11 years ago

RWOverdijk commented 11 years ago

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?)

bacinsky commented 11 years ago

+1

fezfez commented 11 years ago

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! :) )

RWOverdijk commented 11 years ago

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.

fezfez commented 11 years ago

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 ?

RWOverdijk commented 11 years ago

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.

fezfez commented 11 years ago

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);
        }
    }
}
RWOverdijk commented 11 years ago

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

bgallagher commented 11 years ago

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.

seyfer commented 9 years ago

@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.

seyfer commented 9 years ago

@RWOverdijk

for example I have Helperwhich 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.

seyfer commented 9 years ago

@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? )