Fundamental question: Why isn’t the service manager super global?

classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

Fundamental question: Why isn’t the service manager super global?

roberto blanko
Hey devs,

one problem drives me nuts, since I started working with ZF2 (never worked
with Version 1):

I'm in need of the service manager all the time. E.g. to access my config
in config/autoload/local.php via $sm->get('Config'). I need the service
manager everywhere. Controllers, models, you name it. And I don’t want to
pass it around by hand, which would make everything ugly.

Now I’ve started to implement ServiceLocatorAwareInterface in most classes.
But this has the two downsides for me:

1. The classes which make use of my ServiceLocatorAware classes need to
have acces to the service locater, as well, in order to instantiate the
objects. So even more ServiceLocatorAware classes and even more invokables
to be added to module.config.php.

2. The service manager is a property of all ServiceLocatorAware classes,
which can be difficult if you want to persist a ServiceLocatorAware model
e.g. to the session.

Now I'm starting to get the feeling the I haven’t really understood ZF2 and
the service manager. Injecting everything cannot be the solution, can it?
Should I really inject e.g. my global config into all classes? And if you
don’t want to have plain entity models but business models with some
process logic in them, you cannot abstain from the service manager either.

I would really appreciate any clarification on this topic. And I get the
impression a lot of others do, too.

Regards
Rob
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

Marco Pivetta
Superglobals are bad for you. Why would you ever want the service manager
as superglobal?

Keep things in your scope, inject wherever possible and avoid leaking out
of your class' responsibilities.
Things get ugly really fast with static variables and global variables, so
simply don't do it!

So yes, the solution is injecting the config (mustn't be the global one,
you can inject $config['your_stuff']) and generally what you need in your
models/services. That's IOC basics and there's nothing bad about it :)

I personally don't like `ServiceLocatorAware` (and other `Aware`
interfaces) either, so I personally just inject everything I need at
`__construct`.

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/


On 19 February 2013 10:37, roberto blanko <[hidden email]> wrote:

> Hey devs,
>
> one problem drives me nuts, since I started working with ZF2 (never worked
> with Version 1):
>
> I'm in need of the service manager all the time. E.g. to access my config
> in config/autoload/local.php via $sm->get('Config'). I need the service
> manager everywhere. Controllers, models, you name it. And I don’t want to
> pass it around by hand, which would make everything ugly.
>
> Now I’ve started to implement ServiceLocatorAwareInterface in most classes.
> But this has the two downsides for me:
>
> 1. The classes which make use of my ServiceLocatorAware classes need to
> have acces to the service locater, as well, in order to instantiate the
> objects. So even more ServiceLocatorAware classes and even more invokables
> to be added to module.config.php.
>
> 2. The service manager is a property of all ServiceLocatorAware classes,
> which can be difficult if you want to persist a ServiceLocatorAware model
> e.g. to the session.
>
> Now I'm starting to get the feeling the I haven’t really understood ZF2 and
> the service manager. Injecting everything cannot be the solution, can it?
> Should I really inject e.g. my global config into all classes? And if you
> don’t want to have plain entity models but business models with some
> process logic in them, you cannot abstain from the service manager either.
>
> I would really appreciate any clarification on this topic. And I get the
> impression a lot of others do, too.
>
> Regards
> Rob
>
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

weierophinney
Administrator
In reply to this post by roberto blanko
On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <[hidden email]> wrote:
> one problem drives me nuts, since I started working with ZF2 (never worked
> with Version 1):
>
> I'm in need of the service manager all the time. E.g. to access my config
> in config/autoload/local.php via $sm->get('Config'). I need the service
> manager everywhere. Controllers, models, you name it. And I don’t want to
> pass it around by hand, which would make everything ugly.
>
> Now I’ve started to implement ServiceLocatorAwareInterface in most classes.

Don't.

The best option is to define factories for your classes that inject
the dependencies for you. When you do that, you no longer have hidden
dependencies, and you have everything you need up front. When you have
an instance of the object, it's fully configured.

If you need configuration, you're doing it wrong -- that configuration
should either be injected, or the objects the configuration
defines/configures should be injected.

As an example, let's consider a common controller. Let's say it makes
use of a TableGateway, and you have one or more methods in that
TableGateway that return a paginator instance. You want to be able to
specify how many results per page the paginator should use. And for
insert()/update() operations, you want this information tied to a form
so that the validation is done correctly; however, you want a
different form based on the operation (new vs. edit). One element of
the form needs a DB instance in order to validate.

In ZF1, you'd likely do the following:

* Pull the "db" resource from the bootstrap (actually, a DB adapter).
* Create a TableGateway instance, and inject the DB adapter.
* Pull configuration from the registry or the bootstrap.
* Use that configuration to tell the TableGateway how many items per
page to return for Paginator instances.
* You'd create a different form for each operation, and inject the DB
adapter you pulled.

This is a fair bit of work. And it really, really doesn't belong in
your controller, any of it.

Let's look at how to do it in ZF2.

First, I'd create a factory for the TableGateway.

    'my-table-gateway' => function ($services) {
        $db = $services->get('db');
        $config = $services->get('config');
        $perPage = isset($config['per_page']) ? $config['per_page'] : 10;
        $tableGateway = new MyTableGateway($db, $perPage);
        return $tableGateway;
    }

Note that in the _factory_ I'm pulling the configuration, but then I'm
using the values I retrieve from that in order to construct my table
gateway -- this decouples the TableGateway from my configuration. Also
note that I'm retrieving the adapter from the service manager --
dependencies are defined as additional services, and I consume them in
my factories. How is that dependency defined? As a factory:

    'db' => 'Zend\Db\Adapter\AdapterFactoryService',

Next, let's consider my form and validators. In 2.1, we added the
ability to define form elements, filters, and validators via plugin
managers which are managed via the application service manager. This
means that I can create factories for my forms that consume these. By
default, if you

    'validators' => array('factories' => array(
        'MyRecordExists' => function ($validators) {
            $services = $validators->getServiceLocator();
            $db          = $services->get('db');
            return new \Zend\Validator\Db\RecordExists(array(
                'adapter' => $db,
                'table' => 'some_table',
                'field' => 'some_field',
            ));
        },
    )),
    'services' => array('factories' => array(
        'MyCustomForm' => function($services) {
            $validators = $services->get('ValidatorPluginManager');
            $validatorChain = new \Zend\Validator\ValidatorChain();
            $validatorChain->setPluginManager($validators);

            $inputFilterFactory = new \Zend\InputFilter\Factory();
            $inputFilterFactory->setDefaultValidatorChain($validatorChain);
            $inputFilter = new \Zend\InputFilter\InputFilter();
            $inputFilter->setFactory($inputFilterFactory);

            return new MyCustomForm('my-custom-form',
array('input_filter' => $inputFilter));
        },
    )),

In the first case, we've provided a factory for
Zend\Validator\Db\RecordExists that ensures that it is configured with
a DB adapter, and the table and field names we require; note that it
uses its own service name, which allows us to have multiple instances
of the RecordExists validator with different configurations. In the
second case, we create a factory for our form. In there, we create an
input filter instance that has an InputFilter factory passed to it;
that factory is seeded with a validator chain that has our custom
validator plugins in it.

Note that all of this is decoupled from our controller. This allows us
to test any piece of it individually, as well as to re-use it in areas
outside our controller if desired.

Now, for the controller:

    'controllers' => array('factories' => array(
        'MyController' => function ($controllers) {
            $services = $controllers->getServiceLocator();
            $tableGateway = $services->get('my-table-gateway');
            $form = $services->get('MyCustomForm');
            $controller = new MyController();
            $controller->setTableGateway($tableGateway);
            $controller->setForm($form);
            return $controller;
        },
    )),

We grab dependencies, instantiate our controller, and inject the
dependencies. Nice and clean.

Inside our controller, we simply use those dependencies:

    public function newAction()
    {
        $this->form->setValidationGroup('username', 'password', 'confirmation');
        $this->form->setData($this->getRequest()->getPost()->toArray());
        if (!$this->form->isValid()) {
            return new ViewModel(array(
                'form' => $this->form,
                'success' => false,
            ));
        }
        $this->table->insert($this->form->getData());
        $this->redirect()->toRoute('user/profile');
    }

The controller contains no logic for creating the dependencies, or
even fetching them; it simply has setters:

    protected $table;

    public function setTableGateway(TableGateway $tableGateway)
    {
        $this->table = $tableGateway;
    }

This allows the controller to be tested easily, and keeps the messy
logic of obtaining dependencies where it should be -- in factories,
elsewhere.

Notice that I never pass around the service manager or service
locator. If a class needs a dependency, I create a factory for that
class, and use that factory to fetch dependencies and inject them.
This keeps the logic clean inside the individual classes, as they are
only operating on the dependencies passed to them; they don't worry
about instantiating dependencies, or about fetching them. They simply
assume they have them.

> But this has the two downsides for me:
>
> 1. The classes which make use of my ServiceLocatorAware classes need to
> have acces to the service locater, as well, in order to instantiate the
> objects. So even more ServiceLocatorAware classes and even more invokables
> to be added to module.config.php.

As noted, don't use ServiceLocatorAware. Always pass in dependencies.

Second, you _should_ define services for the service manager. This is
a good practice. Those services will only be instantiated if the
current request needs them. Furthermore, defining them means that
someone later can provide _substitutions_ for them. This is
tremendously powerful -- it allows developers to extend your class,
and still have it injected where it needs to be. (I've done this to
work around issues I've found in the past!)

> 2. The service manager is a property of all ServiceLocatorAware classes,
> which can be difficult if you want to persist a ServiceLocatorAware model
> e.g. to the session.

Again, don't make things ServiceLocatorAware. And if you do, don't
persist them to the session. You should persist very little to the
session, and your models typically should be plain old PHP objects
anyways, without knowledge of persistence. If you're tying them to the
persistence layer directly, use hydrators to extract information as
well as hydrate them, if you need to serialize them into the session.

> Now I'm starting to get the feeling the I haven’t really understood ZF2 and
> the service manager. Injecting everything cannot be the solution, can it?

Yes, it is. Because it solves the problems of re-usability,
substitution, and dependency resolution -- all of which were problems
in ZF1.

> Should I really inject e.g. my global config into all classes?

No. Extract the configuration you need inside a factory, and use that
to create an instance. Your objects should only get exactly what they
need, no more, no less.

> And if you
> don’t want to have plain entity models but business models with some
> process logic in them, you cannot abstain from the service manager either.

There are ways to do this, too - service layers can compose
context-specific service managers, or be injected with the
services/configuration that the domain layer may need in order to
operate. You can then use this information when constructing domain
object instances, to create prototypes of domain objects, or to seed
factories. There are many options here.


--
Matthew Weier O'Phinney
Project Lead            | [hidden email]
Zend Framework          | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc

--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

roberto blanko
Matthew, thanks for this elaborate answer. Makes a lot of things clearer
for me.

So, in that case I stick to injecting everything I need. This really bloats
my module.config.php with a lot of redundant stuff and slows me down quite
a bit, but I see your point.


On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
<[hidden email]>wrote:

> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <[hidden email]>
> wrote:
> > one problem drives me nuts, since I started working with ZF2 (never
> worked
> > with Version 1):
> >
> > I'm in need of the service manager all the time. E.g. to access my config
> > in config/autoload/local.php via $sm->get('Config'). I need the service
> > manager everywhere. Controllers, models, you name it. And I don’t want to
> > pass it around by hand, which would make everything ugly.
> >
> > Now I’ve started to implement ServiceLocatorAwareInterface in most
> classes.
>
> Don't.
>
> The best option is to define factories for your classes that inject
> the dependencies for you. When you do that, you no longer have hidden
> dependencies, and you have everything you need up front. When you have
> an instance of the object, it's fully configured.
>
> If you need configuration, you're doing it wrong -- that configuration
> should either be injected, or the objects the configuration
> defines/configures should be injected.
>
> As an example, let's consider a common controller. Let's say it makes
> use of a TableGateway, and you have one or more methods in that
> TableGateway that return a paginator instance. You want to be able to
> specify how many results per page the paginator should use. And for
> insert()/update() operations, you want this information tied to a form
> so that the validation is done correctly; however, you want a
> different form based on the operation (new vs. edit). One element of
> the form needs a DB instance in order to validate.
>
> In ZF1, you'd likely do the following:
>
> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
> * Create a TableGateway instance, and inject the DB adapter.
> * Pull configuration from the registry or the bootstrap.
> * Use that configuration to tell the TableGateway how many items per
> page to return for Paginator instances.
> * You'd create a different form for each operation, and inject the DB
> adapter you pulled.
>
> This is a fair bit of work. And it really, really doesn't belong in
> your controller, any of it.
>
> Let's look at how to do it in ZF2.
>
> First, I'd create a factory for the TableGateway.
>
>     'my-table-gateway' => function ($services) {
>         $db = $services->get('db');
>         $config = $services->get('config');
>         $perPage = isset($config['per_page']) ? $config['per_page'] : 10;
>         $tableGateway = new MyTableGateway($db, $perPage);
>         return $tableGateway;
>     }
>
> Note that in the _factory_ I'm pulling the configuration, but then I'm
> using the values I retrieve from that in order to construct my table
> gateway -- this decouples the TableGateway from my configuration. Also
> note that I'm retrieving the adapter from the service manager --
> dependencies are defined as additional services, and I consume them in
> my factories. How is that dependency defined? As a factory:
>
>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
>
> Next, let's consider my form and validators. In 2.1, we added the
> ability to define form elements, filters, and validators via plugin
> managers which are managed via the application service manager. This
> means that I can create factories for my forms that consume these. By
> default, if you
>
>     'validators' => array('factories' => array(
>         'MyRecordExists' => function ($validators) {
>             $services = $validators->getServiceLocator();
>             $db          = $services->get('db');
>             return new \Zend\Validator\Db\RecordExists(array(
>                 'adapter' => $db,
>                 'table' => 'some_table',
>                 'field' => 'some_field',
>             ));
>         },
>     )),
>     'services' => array('factories' => array(
>         'MyCustomForm' => function($services) {
>             $validators = $services->get('ValidatorPluginManager');
>             $validatorChain = new \Zend\Validator\ValidatorChain();
>             $validatorChain->setPluginManager($validators);
>
>             $inputFilterFactory = new \Zend\InputFilter\Factory();
>             $inputFilterFactory->setDefaultValidatorChain($validatorChain);
>             $inputFilter = new \Zend\InputFilter\InputFilter();
>             $inputFilter->setFactory($inputFilterFactory);
>
>             return new MyCustomForm('my-custom-form',
> array('input_filter' => $inputFilter));
>         },
>     )),
>
> In the first case, we've provided a factory for
> Zend\Validator\Db\RecordExists that ensures that it is configured with
> a DB adapter, and the table and field names we require; note that it
> uses its own service name, which allows us to have multiple instances
> of the RecordExists validator with different configurations. In the
> second case, we create a factory for our form. In there, we create an
> input filter instance that has an InputFilter factory passed to it;
> that factory is seeded with a validator chain that has our custom
> validator plugins in it.
>
> Note that all of this is decoupled from our controller. This allows us
> to test any piece of it individually, as well as to re-use it in areas
> outside our controller if desired.
>
> Now, for the controller:
>
>     'controllers' => array('factories' => array(
>         'MyController' => function ($controllers) {
>             $services = $controllers->getServiceLocator();
>             $tableGateway = $services->get('my-table-gateway');
>             $form = $services->get('MyCustomForm');
>             $controller = new MyController();
>             $controller->setTableGateway($tableGateway);
>             $controller->setForm($form);
>             return $controller;
>         },
>     )),
>
> We grab dependencies, instantiate our controller, and inject the
> dependencies. Nice and clean.
>
> Inside our controller, we simply use those dependencies:
>
>     public function newAction()
>     {
>         $this->form->setValidationGroup('username', 'password',
> 'confirmation');
>         $this->form->setData($this->getRequest()->getPost()->toArray());
>         if (!$this->form->isValid()) {
>             return new ViewModel(array(
>                 'form' => $this->form,
>                 'success' => false,
>             ));
>         }
>         $this->table->insert($this->form->getData());
>         $this->redirect()->toRoute('user/profile');
>     }
>
> The controller contains no logic for creating the dependencies, or
> even fetching them; it simply has setters:
>
>     protected $table;
>
>     public function setTableGateway(TableGateway $tableGateway)
>     {
>         $this->table = $tableGateway;
>     }
>
> This allows the controller to be tested easily, and keeps the messy
> logic of obtaining dependencies where it should be -- in factories,
> elsewhere.
>
> Notice that I never pass around the service manager or service
> locator. If a class needs a dependency, I create a factory for that
> class, and use that factory to fetch dependencies and inject them.
> This keeps the logic clean inside the individual classes, as they are
> only operating on the dependencies passed to them; they don't worry
> about instantiating dependencies, or about fetching them. They simply
> assume they have them.
>
> > But this has the two downsides for me:
> >
> > 1. The classes which make use of my ServiceLocatorAware classes need to
> > have acces to the service locater, as well, in order to instantiate the
> > objects. So even more ServiceLocatorAware classes and even more
> invokables
> > to be added to module.config.php.
>
> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
>
> Second, you _should_ define services for the service manager. This is
> a good practice. Those services will only be instantiated if the
> current request needs them. Furthermore, defining them means that
> someone later can provide _substitutions_ for them. This is
> tremendously powerful -- it allows developers to extend your class,
> and still have it injected where it needs to be. (I've done this to
> work around issues I've found in the past!)
>
> > 2. The service manager is a property of all ServiceLocatorAware classes,
> > which can be difficult if you want to persist a ServiceLocatorAware model
> > e.g. to the session.
>
> Again, don't make things ServiceLocatorAware. And if you do, don't
> persist them to the session. You should persist very little to the
> session, and your models typically should be plain old PHP objects
> anyways, without knowledge of persistence. If you're tying them to the
> persistence layer directly, use hydrators to extract information as
> well as hydrate them, if you need to serialize them into the session.
>
> > Now I'm starting to get the feeling the I haven’t really understood ZF2
> and
> > the service manager. Injecting everything cannot be the solution, can it?
>
> Yes, it is. Because it solves the problems of re-usability,
> substitution, and dependency resolution -- all of which were problems
> in ZF1.
>
> > Should I really inject e.g. my global config into all classes?
>
> No. Extract the configuration you need inside a factory, and use that
> to create an instance. Your objects should only get exactly what they
> need, no more, no less.
>
> > And if you
> > don’t want to have plain entity models but business models with some
> > process logic in them, you cannot abstain from the service manager
> either.
>
> There are ways to do this, too - service layers can compose
> context-specific service managers, or be injected with the
> services/configuration that the domain layer may need in order to
> operate. You can then use this information when constructing domain
> object instances, to create prototypes of domain objects, or to seed
> factories. There are many options here.
>
>
> --
> Matthew Weier O'Phinney
> Project Lead            | [hidden email]
> Zend Framework          | http://framework.zend.com/
> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

weierophinney
Administrator
On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <[hidden email]> wrote:
> Matthew, thanks for this elaborate answer. Makes a lot of things clearer
> for me.
>
> So, in that case I stick to injecting everything I need. This really bloats
> my module.config.php with a lot of redundant stuff and slows me down quite
> a bit, but I see your point.


What kind of redundant stuff are you seeing? Depending on what it is,
and whether others report similarly, we may be able to come up with
some solutions to reduce repetition.


> On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
> <[hidden email]>wrote:
>
>> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <[hidden email]>
>> wrote:
>> > one problem drives me nuts, since I started working with ZF2 (never
>> worked
>> > with Version 1):
>> >
>> > I'm in need of the service manager all the time. E.g. to access my config
>> > in config/autoload/local.php via $sm->get('Config'). I need the service
>> > manager everywhere. Controllers, models, you name it. And I don’t want to
>> > pass it around by hand, which would make everything ugly.
>> >
>> > Now I’ve started to implement ServiceLocatorAwareInterface in most
>> classes.
>>
>> Don't.
>>
>> The best option is to define factories for your classes that inject
>> the dependencies for you. When you do that, you no longer have hidden
>> dependencies, and you have everything you need up front. When you have
>> an instance of the object, it's fully configured.
>>
>> If you need configuration, you're doing it wrong -- that configuration
>> should either be injected, or the objects the configuration
>> defines/configures should be injected.
>>
>> As an example, let's consider a common controller. Let's say it makes
>> use of a TableGateway, and you have one or more methods in that
>> TableGateway that return a paginator instance. You want to be able to
>> specify how many results per page the paginator should use. And for
>> insert()/update() operations, you want this information tied to a form
>> so that the validation is done correctly; however, you want a
>> different form based on the operation (new vs. edit). One element of
>> the form needs a DB instance in order to validate.
>>
>> In ZF1, you'd likely do the following:
>>
>> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
>> * Create a TableGateway instance, and inject the DB adapter.
>> * Pull configuration from the registry or the bootstrap.
>> * Use that configuration to tell the TableGateway how many items per
>> page to return for Paginator instances.
>> * You'd create a different form for each operation, and inject the DB
>> adapter you pulled.
>>
>> This is a fair bit of work. And it really, really doesn't belong in
>> your controller, any of it.
>>
>> Let's look at how to do it in ZF2.
>>
>> First, I'd create a factory for the TableGateway.
>>
>>     'my-table-gateway' => function ($services) {
>>         $db = $services->get('db');
>>         $config = $services->get('config');
>>         $perPage = isset($config['per_page']) ? $config['per_page'] : 10;
>>         $tableGateway = new MyTableGateway($db, $perPage);
>>         return $tableGateway;
>>     }
>>
>> Note that in the _factory_ I'm pulling the configuration, but then I'm
>> using the values I retrieve from that in order to construct my table
>> gateway -- this decouples the TableGateway from my configuration. Also
>> note that I'm retrieving the adapter from the service manager --
>> dependencies are defined as additional services, and I consume them in
>> my factories. How is that dependency defined? As a factory:
>>
>>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
>>
>> Next, let's consider my form and validators. In 2.1, we added the
>> ability to define form elements, filters, and validators via plugin
>> managers which are managed via the application service manager. This
>> means that I can create factories for my forms that consume these. By
>> default, if you
>>
>>     'validators' => array('factories' => array(
>>         'MyRecordExists' => function ($validators) {
>>             $services = $validators->getServiceLocator();
>>             $db          = $services->get('db');
>>             return new \Zend\Validator\Db\RecordExists(array(
>>                 'adapter' => $db,
>>                 'table' => 'some_table',
>>                 'field' => 'some_field',
>>             ));
>>         },
>>     )),
>>     'services' => array('factories' => array(
>>         'MyCustomForm' => function($services) {
>>             $validators = $services->get('ValidatorPluginManager');
>>             $validatorChain = new \Zend\Validator\ValidatorChain();
>>             $validatorChain->setPluginManager($validators);
>>
>>             $inputFilterFactory = new \Zend\InputFilter\Factory();
>>             $inputFilterFactory->setDefaultValidatorChain($validatorChain);
>>             $inputFilter = new \Zend\InputFilter\InputFilter();
>>             $inputFilter->setFactory($inputFilterFactory);
>>
>>             return new MyCustomForm('my-custom-form',
>> array('input_filter' => $inputFilter));
>>         },
>>     )),
>>
>> In the first case, we've provided a factory for
>> Zend\Validator\Db\RecordExists that ensures that it is configured with
>> a DB adapter, and the table and field names we require; note that it
>> uses its own service name, which allows us to have multiple instances
>> of the RecordExists validator with different configurations. In the
>> second case, we create a factory for our form. In there, we create an
>> input filter instance that has an InputFilter factory passed to it;
>> that factory is seeded with a validator chain that has our custom
>> validator plugins in it.
>>
>> Note that all of this is decoupled from our controller. This allows us
>> to test any piece of it individually, as well as to re-use it in areas
>> outside our controller if desired.
>>
>> Now, for the controller:
>>
>>     'controllers' => array('factories' => array(
>>         'MyController' => function ($controllers) {
>>             $services = $controllers->getServiceLocator();
>>             $tableGateway = $services->get('my-table-gateway');
>>             $form = $services->get('MyCustomForm');
>>             $controller = new MyController();
>>             $controller->setTableGateway($tableGateway);
>>             $controller->setForm($form);
>>             return $controller;
>>         },
>>     )),
>>
>> We grab dependencies, instantiate our controller, and inject the
>> dependencies. Nice and clean.
>>
>> Inside our controller, we simply use those dependencies:
>>
>>     public function newAction()
>>     {
>>         $this->form->setValidationGroup('username', 'password',
>> 'confirmation');
>>         $this->form->setData($this->getRequest()->getPost()->toArray());
>>         if (!$this->form->isValid()) {
>>             return new ViewModel(array(
>>                 'form' => $this->form,
>>                 'success' => false,
>>             ));
>>         }
>>         $this->table->insert($this->form->getData());
>>         $this->redirect()->toRoute('user/profile');
>>     }
>>
>> The controller contains no logic for creating the dependencies, or
>> even fetching them; it simply has setters:
>>
>>     protected $table;
>>
>>     public function setTableGateway(TableGateway $tableGateway)
>>     {
>>         $this->table = $tableGateway;
>>     }
>>
>> This allows the controller to be tested easily, and keeps the messy
>> logic of obtaining dependencies where it should be -- in factories,
>> elsewhere.
>>
>> Notice that I never pass around the service manager or service
>> locator. If a class needs a dependency, I create a factory for that
>> class, and use that factory to fetch dependencies and inject them.
>> This keeps the logic clean inside the individual classes, as they are
>> only operating on the dependencies passed to them; they don't worry
>> about instantiating dependencies, or about fetching them. They simply
>> assume they have them.
>>
>> > But this has the two downsides for me:
>> >
>> > 1. The classes which make use of my ServiceLocatorAware classes need to
>> > have acces to the service locater, as well, in order to instantiate the
>> > objects. So even more ServiceLocatorAware classes and even more
>> invokables
>> > to be added to module.config.php.
>>
>> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
>>
>> Second, you _should_ define services for the service manager. This is
>> a good practice. Those services will only be instantiated if the
>> current request needs them. Furthermore, defining them means that
>> someone later can provide _substitutions_ for them. This is
>> tremendously powerful -- it allows developers to extend your class,
>> and still have it injected where it needs to be. (I've done this to
>> work around issues I've found in the past!)
>>
>> > 2. The service manager is a property of all ServiceLocatorAware classes,
>> > which can be difficult if you want to persist a ServiceLocatorAware model
>> > e.g. to the session.
>>
>> Again, don't make things ServiceLocatorAware. And if you do, don't
>> persist them to the session. You should persist very little to the
>> session, and your models typically should be plain old PHP objects
>> anyways, without knowledge of persistence. If you're tying them to the
>> persistence layer directly, use hydrators to extract information as
>> well as hydrate them, if you need to serialize them into the session.
>>
>> > Now I'm starting to get the feeling the I haven’t really understood ZF2
>> and
>> > the service manager. Injecting everything cannot be the solution, can it?
>>
>> Yes, it is. Because it solves the problems of re-usability,
>> substitution, and dependency resolution -- all of which were problems
>> in ZF1.
>>
>> > Should I really inject e.g. my global config into all classes?
>>
>> No. Extract the configuration you need inside a factory, and use that
>> to create an instance. Your objects should only get exactly what they
>> need, no more, no less.
>>
>> > And if you
>> > don’t want to have plain entity models but business models with some
>> > process logic in them, you cannot abstain from the service manager
>> either.
>>
>> There are ways to do this, too - service layers can compose
>> context-specific service managers, or be injected with the
>> services/configuration that the domain layer may need in order to
>> operate. You can then use this information when constructing domain
>> object instances, to create prototypes of domain objects, or to seed
>> factories. There are many options here.
>>
>>
>> --
>> Matthew Weier O'Phinney
>> Project Lead            | [hidden email]
>> Zend Framework          | http://framework.zend.com/
>> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>>



--
Matthew Weier O'Phinney
Project Lead            | [hidden email]
Zend Framework          | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc

--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

roberto blanko
In terms of redundancy, this is just a simple example but should make my
point clear:

return array(
    'service_manager' => array(
        'factories' => array(
            'Main\Model\StreamRepository' => function($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                return new \Main\Model\StreamRepository($sm, $dbAdapter);
            },
            'Main\Model\AccountRepository' => function($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                return new \Main\Model\AccountRepository($sm, $dbAdapter);
            },
            'Main\Model\UserRepository' => function($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                return new \Main\Model\TwitterRepository($sm, $dbAdapter);
            },
            'Main\Model\ProjectRepository' => function($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                return new \Main\Model\GoogleRepository($sm, $dbAdapter);
            },
        ),
    ),
);

Each of my repositories (which extends AbstractTableGateway) needs to have
the DB adapter injected. It would make things faster, if factories could be
defined based on inherited classes. E.g. in the case above the factories
for all classes inheriting from Main\Model\AbstractRepository need to have
the same factory. Or is there a solution for this already? I could use an
initializer class but this wouldn’t perform very well, would it? Since the
initializer would be called whether I needed it or not.


On Mon, Feb 25, 2013 at 3:08 PM, Matthew Weier O'Phinney
<[hidden email]>wrote:

> On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <[hidden email]>
> wrote:
> > Matthew, thanks for this elaborate answer. Makes a lot of things clearer
> > for me.
> >
> > So, in that case I stick to injecting everything I need. This really
> bloats
> > my module.config.php with a lot of redundant stuff and slows me down
> quite
> > a bit, but I see your point.
>
>
> What kind of redundant stuff are you seeing? Depending on what it is,
> and whether others report similarly, we may be able to come up with
> some solutions to reduce repetition.
>
>
> > On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
> > <[hidden email]>wrote:
> >
> >> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <
> [hidden email]>
> >> wrote:
> >> > one problem drives me nuts, since I started working with ZF2 (never
> >> worked
> >> > with Version 1):
> >> >
> >> > I'm in need of the service manager all the time. E.g. to access my
> config
> >> > in config/autoload/local.php via $sm->get('Config'). I need the
> service
> >> > manager everywhere. Controllers, models, you name it. And I don’t
> want to
> >> > pass it around by hand, which would make everything ugly.
> >> >
> >> > Now I’ve started to implement ServiceLocatorAwareInterface in most
> >> classes.
> >>
> >> Don't.
> >>
> >> The best option is to define factories for your classes that inject
> >> the dependencies for you. When you do that, you no longer have hidden
> >> dependencies, and you have everything you need up front. When you have
> >> an instance of the object, it's fully configured.
> >>
> >> If you need configuration, you're doing it wrong -- that configuration
> >> should either be injected, or the objects the configuration
> >> defines/configures should be injected.
> >>
> >> As an example, let's consider a common controller. Let's say it makes
> >> use of a TableGateway, and you have one or more methods in that
> >> TableGateway that return a paginator instance. You want to be able to
> >> specify how many results per page the paginator should use. And for
> >> insert()/update() operations, you want this information tied to a form
> >> so that the validation is done correctly; however, you want a
> >> different form based on the operation (new vs. edit). One element of
> >> the form needs a DB instance in order to validate.
> >>
> >> In ZF1, you'd likely do the following:
> >>
> >> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
> >> * Create a TableGateway instance, and inject the DB adapter.
> >> * Pull configuration from the registry or the bootstrap.
> >> * Use that configuration to tell the TableGateway how many items per
> >> page to return for Paginator instances.
> >> * You'd create a different form for each operation, and inject the DB
> >> adapter you pulled.
> >>
> >> This is a fair bit of work. And it really, really doesn't belong in
> >> your controller, any of it.
> >>
> >> Let's look at how to do it in ZF2.
> >>
> >> First, I'd create a factory for the TableGateway.
> >>
> >>     'my-table-gateway' => function ($services) {
> >>         $db = $services->get('db');
> >>         $config = $services->get('config');
> >>         $perPage = isset($config['per_page']) ? $config['per_page'] :
> 10;
> >>         $tableGateway = new MyTableGateway($db, $perPage);
> >>         return $tableGateway;
> >>     }
> >>
> >> Note that in the _factory_ I'm pulling the configuration, but then I'm
> >> using the values I retrieve from that in order to construct my table
> >> gateway -- this decouples the TableGateway from my configuration. Also
> >> note that I'm retrieving the adapter from the service manager --
> >> dependencies are defined as additional services, and I consume them in
> >> my factories. How is that dependency defined? As a factory:
> >>
> >>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
> >>
> >> Next, let's consider my form and validators. In 2.1, we added the
> >> ability to define form elements, filters, and validators via plugin
> >> managers which are managed via the application service manager. This
> >> means that I can create factories for my forms that consume these. By
> >> default, if you
> >>
> >>     'validators' => array('factories' => array(
> >>         'MyRecordExists' => function ($validators) {
> >>             $services = $validators->getServiceLocator();
> >>             $db          = $services->get('db');
> >>             return new \Zend\Validator\Db\RecordExists(array(
> >>                 'adapter' => $db,
> >>                 'table' => 'some_table',
> >>                 'field' => 'some_field',
> >>             ));
> >>         },
> >>     )),
> >>     'services' => array('factories' => array(
> >>         'MyCustomForm' => function($services) {
> >>             $validators = $services->get('ValidatorPluginManager');
> >>             $validatorChain = new \Zend\Validator\ValidatorChain();
> >>             $validatorChain->setPluginManager($validators);
> >>
> >>             $inputFilterFactory = new \Zend\InputFilter\Factory();
> >>
> $inputFilterFactory->setDefaultValidatorChain($validatorChain);
> >>             $inputFilter = new \Zend\InputFilter\InputFilter();
> >>             $inputFilter->setFactory($inputFilterFactory);
> >>
> >>             return new MyCustomForm('my-custom-form',
> >> array('input_filter' => $inputFilter));
> >>         },
> >>     )),
> >>
> >> In the first case, we've provided a factory for
> >> Zend\Validator\Db\RecordExists that ensures that it is configured with
> >> a DB adapter, and the table and field names we require; note that it
> >> uses its own service name, which allows us to have multiple instances
> >> of the RecordExists validator with different configurations. In the
> >> second case, we create a factory for our form. In there, we create an
> >> input filter instance that has an InputFilter factory passed to it;
> >> that factory is seeded with a validator chain that has our custom
> >> validator plugins in it.
> >>
> >> Note that all of this is decoupled from our controller. This allows us
> >> to test any piece of it individually, as well as to re-use it in areas
> >> outside our controller if desired.
> >>
> >> Now, for the controller:
> >>
> >>     'controllers' => array('factories' => array(
> >>         'MyController' => function ($controllers) {
> >>             $services = $controllers->getServiceLocator();
> >>             $tableGateway = $services->get('my-table-gateway');
> >>             $form = $services->get('MyCustomForm');
> >>             $controller = new MyController();
> >>             $controller->setTableGateway($tableGateway);
> >>             $controller->setForm($form);
> >>             return $controller;
> >>         },
> >>     )),
> >>
> >> We grab dependencies, instantiate our controller, and inject the
> >> dependencies. Nice and clean.
> >>
> >> Inside our controller, we simply use those dependencies:
> >>
> >>     public function newAction()
> >>     {
> >>         $this->form->setValidationGroup('username', 'password',
> >> 'confirmation');
> >>         $this->form->setData($this->getRequest()->getPost()->toArray());
> >>         if (!$this->form->isValid()) {
> >>             return new ViewModel(array(
> >>                 'form' => $this->form,
> >>                 'success' => false,
> >>             ));
> >>         }
> >>         $this->table->insert($this->form->getData());
> >>         $this->redirect()->toRoute('user/profile');
> >>     }
> >>
> >> The controller contains no logic for creating the dependencies, or
> >> even fetching them; it simply has setters:
> >>
> >>     protected $table;
> >>
> >>     public function setTableGateway(TableGateway $tableGateway)
> >>     {
> >>         $this->table = $tableGateway;
> >>     }
> >>
> >> This allows the controller to be tested easily, and keeps the messy
> >> logic of obtaining dependencies where it should be -- in factories,
> >> elsewhere.
> >>
> >> Notice that I never pass around the service manager or service
> >> locator. If a class needs a dependency, I create a factory for that
> >> class, and use that factory to fetch dependencies and inject them.
> >> This keeps the logic clean inside the individual classes, as they are
> >> only operating on the dependencies passed to them; they don't worry
> >> about instantiating dependencies, or about fetching them. They simply
> >> assume they have them.
> >>
> >> > But this has the two downsides for me:
> >> >
> >> > 1. The classes which make use of my ServiceLocatorAware classes need
> to
> >> > have acces to the service locater, as well, in order to instantiate
> the
> >> > objects. So even more ServiceLocatorAware classes and even more
> >> invokables
> >> > to be added to module.config.php.
> >>
> >> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
> >>
> >> Second, you _should_ define services for the service manager. This is
> >> a good practice. Those services will only be instantiated if the
> >> current request needs them. Furthermore, defining them means that
> >> someone later can provide _substitutions_ for them. This is
> >> tremendously powerful -- it allows developers to extend your class,
> >> and still have it injected where it needs to be. (I've done this to
> >> work around issues I've found in the past!)
> >>
> >> > 2. The service manager is a property of all ServiceLocatorAware
> classes,
> >> > which can be difficult if you want to persist a ServiceLocatorAware
> model
> >> > e.g. to the session.
> >>
> >> Again, don't make things ServiceLocatorAware. And if you do, don't
> >> persist them to the session. You should persist very little to the
> >> session, and your models typically should be plain old PHP objects
> >> anyways, without knowledge of persistence. If you're tying them to the
> >> persistence layer directly, use hydrators to extract information as
> >> well as hydrate them, if you need to serialize them into the session.
> >>
> >> > Now I'm starting to get the feeling the I haven’t really understood
> ZF2
> >> and
> >> > the service manager. Injecting everything cannot be the solution, can
> it?
> >>
> >> Yes, it is. Because it solves the problems of re-usability,
> >> substitution, and dependency resolution -- all of which were problems
> >> in ZF1.
> >>
> >> > Should I really inject e.g. my global config into all classes?
> >>
> >> No. Extract the configuration you need inside a factory, and use that
> >> to create an instance. Your objects should only get exactly what they
> >> need, no more, no less.
> >>
> >> > And if you
> >> > don’t want to have plain entity models but business models with some
> >> > process logic in them, you cannot abstain from the service manager
> >> either.
> >>
> >> There are ways to do this, too - service layers can compose
> >> context-specific service managers, or be injected with the
> >> services/configuration that the domain layer may need in order to
> >> operate. You can then use this information when constructing domain
> >> object instances, to create prototypes of domain objects, or to seed
> >> factories. There are many options here.
> >>
> >>
> >> --
> >> Matthew Weier O'Phinney
> >> Project Lead            | [hidden email]
> >> Zend Framework          | http://framework.zend.com/
> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
> >>
>
>
>
> --
> Matthew Weier O'Phinney
> Project Lead            | [hidden email]
> Zend Framework          | http://framework.zend.com/
> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>
> --
> List: [hidden email]
> Info: http://framework.zend.com/archives
> Unsubscribe: [hidden email]
>
>
>
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

Marco Pivetta
Initializers don't necessarily perform bad, but add some overhead and are
hard to debug, and also force you to have eventual hard dependencies
configured as soft dependencies (because of setter injection).

An alternative solution (with significant performance drop) is using
`Zend\Di` for that.

You are doing it correctly right now in my opinion, and I don't see that
much redunancy in there, and wouldn't change your current way of doing it

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/


On 26 February 2013 10:46, roberto blanko <[hidden email]> wrote:

> In terms of redundancy, this is just a simple example but should make my
> point clear:
>
> return array(
>     'service_manager' => array(
>         'factories' => array(
>             'Main\Model\StreamRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\StreamRepository($sm, $dbAdapter);
>             },
>             'Main\Model\AccountRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\AccountRepository($sm, $dbAdapter);
>             },
>             'Main\Model\UserRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\TwitterRepository($sm, $dbAdapter);
>             },
>             'Main\Model\ProjectRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\GoogleRepository($sm, $dbAdapter);
>             },
>         ),
>     ),
> );
>
> Each of my repositories (which extends AbstractTableGateway) needs to have
> the DB adapter injected. It would make things faster, if factories could be
> defined based on inherited classes. E.g. in the case above the factories
> for all classes inheriting from Main\Model\AbstractRepository need to have
> the same factory. Or is there a solution for this already? I could use an
> initializer class but this wouldn’t perform very well, would it? Since the
> initializer would be called whether I needed it or not.
>
>
> On Mon, Feb 25, 2013 at 3:08 PM, Matthew Weier O'Phinney
> <[hidden email]>wrote:
>
> > On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <[hidden email]
> >
> > wrote:
> > > Matthew, thanks for this elaborate answer. Makes a lot of things
> clearer
> > > for me.
> > >
> > > So, in that case I stick to injecting everything I need. This really
> > bloats
> > > my module.config.php with a lot of redundant stuff and slows me down
> > quite
> > > a bit, but I see your point.
> >
> >
> > What kind of redundant stuff are you seeing? Depending on what it is,
> > and whether others report similarly, we may be able to come up with
> > some solutions to reduce repetition.
> >
> >
> > > On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
> > > <[hidden email]>wrote:
> > >
> > >> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <
> > [hidden email]>
> > >> wrote:
> > >> > one problem drives me nuts, since I started working with ZF2 (never
> > >> worked
> > >> > with Version 1):
> > >> >
> > >> > I'm in need of the service manager all the time. E.g. to access my
> > config
> > >> > in config/autoload/local.php via $sm->get('Config'). I need the
> > service
> > >> > manager everywhere. Controllers, models, you name it. And I don’t
> > want to
> > >> > pass it around by hand, which would make everything ugly.
> > >> >
> > >> > Now I’ve started to implement ServiceLocatorAwareInterface in most
> > >> classes.
> > >>
> > >> Don't.
> > >>
> > >> The best option is to define factories for your classes that inject
> > >> the dependencies for you. When you do that, you no longer have hidden
> > >> dependencies, and you have everything you need up front. When you have
> > >> an instance of the object, it's fully configured.
> > >>
> > >> If you need configuration, you're doing it wrong -- that configuration
> > >> should either be injected, or the objects the configuration
> > >> defines/configures should be injected.
> > >>
> > >> As an example, let's consider a common controller. Let's say it makes
> > >> use of a TableGateway, and you have one or more methods in that
> > >> TableGateway that return a paginator instance. You want to be able to
> > >> specify how many results per page the paginator should use. And for
> > >> insert()/update() operations, you want this information tied to a form
> > >> so that the validation is done correctly; however, you want a
> > >> different form based on the operation (new vs. edit). One element of
> > >> the form needs a DB instance in order to validate.
> > >>
> > >> In ZF1, you'd likely do the following:
> > >>
> > >> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
> > >> * Create a TableGateway instance, and inject the DB adapter.
> > >> * Pull configuration from the registry or the bootstrap.
> > >> * Use that configuration to tell the TableGateway how many items per
> > >> page to return for Paginator instances.
> > >> * You'd create a different form for each operation, and inject the DB
> > >> adapter you pulled.
> > >>
> > >> This is a fair bit of work. And it really, really doesn't belong in
> > >> your controller, any of it.
> > >>
> > >> Let's look at how to do it in ZF2.
> > >>
> > >> First, I'd create a factory for the TableGateway.
> > >>
> > >>     'my-table-gateway' => function ($services) {
> > >>         $db = $services->get('db');
> > >>         $config = $services->get('config');
> > >>         $perPage = isset($config['per_page']) ? $config['per_page'] :
> > 10;
> > >>         $tableGateway = new MyTableGateway($db, $perPage);
> > >>         return $tableGateway;
> > >>     }
> > >>
> > >> Note that in the _factory_ I'm pulling the configuration, but then I'm
> > >> using the values I retrieve from that in order to construct my table
> > >> gateway -- this decouples the TableGateway from my configuration. Also
> > >> note that I'm retrieving the adapter from the service manager --
> > >> dependencies are defined as additional services, and I consume them in
> > >> my factories. How is that dependency defined? As a factory:
> > >>
> > >>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
> > >>
> > >> Next, let's consider my form and validators. In 2.1, we added the
> > >> ability to define form elements, filters, and validators via plugin
> > >> managers which are managed via the application service manager. This
> > >> means that I can create factories for my forms that consume these. By
> > >> default, if you
> > >>
> > >>     'validators' => array('factories' => array(
> > >>         'MyRecordExists' => function ($validators) {
> > >>             $services = $validators->getServiceLocator();
> > >>             $db          = $services->get('db');
> > >>             return new \Zend\Validator\Db\RecordExists(array(
> > >>                 'adapter' => $db,
> > >>                 'table' => 'some_table',
> > >>                 'field' => 'some_field',
> > >>             ));
> > >>         },
> > >>     )),
> > >>     'services' => array('factories' => array(
> > >>         'MyCustomForm' => function($services) {
> > >>             $validators = $services->get('ValidatorPluginManager');
> > >>             $validatorChain = new \Zend\Validator\ValidatorChain();
> > >>             $validatorChain->setPluginManager($validators);
> > >>
> > >>             $inputFilterFactory = new \Zend\InputFilter\Factory();
> > >>
> > $inputFilterFactory->setDefaultValidatorChain($validatorChain);
> > >>             $inputFilter = new \Zend\InputFilter\InputFilter();
> > >>             $inputFilter->setFactory($inputFilterFactory);
> > >>
> > >>             return new MyCustomForm('my-custom-form',
> > >> array('input_filter' => $inputFilter));
> > >>         },
> > >>     )),
> > >>
> > >> In the first case, we've provided a factory for
> > >> Zend\Validator\Db\RecordExists that ensures that it is configured with
> > >> a DB adapter, and the table and field names we require; note that it
> > >> uses its own service name, which allows us to have multiple instances
> > >> of the RecordExists validator with different configurations. In the
> > >> second case, we create a factory for our form. In there, we create an
> > >> input filter instance that has an InputFilter factory passed to it;
> > >> that factory is seeded with a validator chain that has our custom
> > >> validator plugins in it.
> > >>
> > >> Note that all of this is decoupled from our controller. This allows us
> > >> to test any piece of it individually, as well as to re-use it in areas
> > >> outside our controller if desired.
> > >>
> > >> Now, for the controller:
> > >>
> > >>     'controllers' => array('factories' => array(
> > >>         'MyController' => function ($controllers) {
> > >>             $services = $controllers->getServiceLocator();
> > >>             $tableGateway = $services->get('my-table-gateway');
> > >>             $form = $services->get('MyCustomForm');
> > >>             $controller = new MyController();
> > >>             $controller->setTableGateway($tableGateway);
> > >>             $controller->setForm($form);
> > >>             return $controller;
> > >>         },
> > >>     )),
> > >>
> > >> We grab dependencies, instantiate our controller, and inject the
> > >> dependencies. Nice and clean.
> > >>
> > >> Inside our controller, we simply use those dependencies:
> > >>
> > >>     public function newAction()
> > >>     {
> > >>         $this->form->setValidationGroup('username', 'password',
> > >> 'confirmation');
> > >>
> $this->form->setData($this->getRequest()->getPost()->toArray());
> > >>         if (!$this->form->isValid()) {
> > >>             return new ViewModel(array(
> > >>                 'form' => $this->form,
> > >>                 'success' => false,
> > >>             ));
> > >>         }
> > >>         $this->table->insert($this->form->getData());
> > >>         $this->redirect()->toRoute('user/profile');
> > >>     }
> > >>
> > >> The controller contains no logic for creating the dependencies, or
> > >> even fetching them; it simply has setters:
> > >>
> > >>     protected $table;
> > >>
> > >>     public function setTableGateway(TableGateway $tableGateway)
> > >>     {
> > >>         $this->table = $tableGateway;
> > >>     }
> > >>
> > >> This allows the controller to be tested easily, and keeps the messy
> > >> logic of obtaining dependencies where it should be -- in factories,
> > >> elsewhere.
> > >>
> > >> Notice that I never pass around the service manager or service
> > >> locator. If a class needs a dependency, I create a factory for that
> > >> class, and use that factory to fetch dependencies and inject them.
> > >> This keeps the logic clean inside the individual classes, as they are
> > >> only operating on the dependencies passed to them; they don't worry
> > >> about instantiating dependencies, or about fetching them. They simply
> > >> assume they have them.
> > >>
> > >> > But this has the two downsides for me:
> > >> >
> > >> > 1. The classes which make use of my ServiceLocatorAware classes need
> > to
> > >> > have acces to the service locater, as well, in order to instantiate
> > the
> > >> > objects. So even more ServiceLocatorAware classes and even more
> > >> invokables
> > >> > to be added to module.config.php.
> > >>
> > >> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
> > >>
> > >> Second, you _should_ define services for the service manager. This is
> > >> a good practice. Those services will only be instantiated if the
> > >> current request needs them. Furthermore, defining them means that
> > >> someone later can provide _substitutions_ for them. This is
> > >> tremendously powerful -- it allows developers to extend your class,
> > >> and still have it injected where it needs to be. (I've done this to
> > >> work around issues I've found in the past!)
> > >>
> > >> > 2. The service manager is a property of all ServiceLocatorAware
> > classes,
> > >> > which can be difficult if you want to persist a ServiceLocatorAware
> > model
> > >> > e.g. to the session.
> > >>
> > >> Again, don't make things ServiceLocatorAware. And if you do, don't
> > >> persist them to the session. You should persist very little to the
> > >> session, and your models typically should be plain old PHP objects
> > >> anyways, without knowledge of persistence. If you're tying them to the
> > >> persistence layer directly, use hydrators to extract information as
> > >> well as hydrate them, if you need to serialize them into the session.
> > >>
> > >> > Now I'm starting to get the feeling the I haven’t really understood
> > ZF2
> > >> and
> > >> > the service manager. Injecting everything cannot be the solution,
> can
> > it?
> > >>
> > >> Yes, it is. Because it solves the problems of re-usability,
> > >> substitution, and dependency resolution -- all of which were problems
> > >> in ZF1.
> > >>
> > >> > Should I really inject e.g. my global config into all classes?
> > >>
> > >> No. Extract the configuration you need inside a factory, and use that
> > >> to create an instance. Your objects should only get exactly what they
> > >> need, no more, no less.
> > >>
> > >> > And if you
> > >> > don’t want to have plain entity models but business models with some
> > >> > process logic in them, you cannot abstain from the service manager
> > >> either.
> > >>
> > >> There are ways to do this, too - service layers can compose
> > >> context-specific service managers, or be injected with the
> > >> services/configuration that the domain layer may need in order to
> > >> operate. You can then use this information when constructing domain
> > >> object instances, to create prototypes of domain objects, or to seed
> > >> factories. There are many options here.
> > >>
> > >>
> > >> --
> > >> Matthew Weier O'Phinney
> > >> Project Lead            | [hidden email]
> > >> Zend Framework          | http://framework.zend.com/
> > >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
> > >>
> >
> >
> >
> > --
> > Matthew Weier O'Phinney
> > Project Lead            | [hidden email]
> > Zend Framework          | http://framework.zend.com/
> > PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
> >
> > --
> > List: [hidden email]
> > Info: http://framework.zend.com/archives
> > Unsubscribe: [hidden email]
> >
> >
> >
>
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

weierophinney
Administrator
In reply to this post by roberto blanko
On Tue, Feb 26, 2013 at 3:46 AM, roberto blanko <[hidden email]> wrote:

> In terms of redundancy, this is just a simple example but should make my
> point clear:
>
> return array(
>     'service_manager' => array(
>         'factories' => array(
>             'Main\Model\StreamRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\StreamRepository($sm, $dbAdapter);
>             },
>             'Main\Model\AccountRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\AccountRepository($sm, $dbAdapter);
>             },
>             'Main\Model\UserRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\TwitterRepository($sm, $dbAdapter);
>             },
>             'Main\Model\ProjectRepository' => function($sm) {
>                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>                 return new \Main\Model\GoogleRepository($sm, $dbAdapter);
>             },
>         ),
>     ),
> );
>
> Each of my repositories (which extends AbstractTableGateway) needs to have
> the DB adapter injected. It would make things faster, if factories could be
> defined based on inherited classes. E.g. in the case above the factories
> for all classes inheriting from Main\Model\AbstractRepository need to have
> the same factory. Or is there a solution for this already? I could use an
> initializer class but this wouldn’t perform very well, would it? Since the
> initializer would be called whether I needed it or not.

There are two approaches you could use here.

First is an initializer. There is actually very little performance
impact from that, and it would reduce the above to four lines (one
line for each class, defined as invokables).

Second is to use an AbstractFactory. The idea behind the
AbstractFactory is when you have many classes that have the same
creational pattern. You might define it like this:

    use Zend\ServiceManager\AbstractFactoryInterface;
    use Zend\ServiceManager\ServiceLocatorInterface;

    class DbAwareFactory implements AbstractFactoryInterface
    {
        protected $serviceClasses = array(
            'Main\Model\AccountRepository',
            'Main\Model\GoogleRepository',
            'Main\Model\StreamRepository',
            'Main\Model\TwitterRepository',
        );

        public function
canCreateServiceWithName(ServiceLocatorInterface $services, $name,
$requestedName)
        {
            return in_array($name, $this->serviceClasses);
        }

        public function createServiceWithName(ServiceLocatorInterface
$services, $name, $requestedName)
        {
            $dbAdapter = $services->get('Zend\Db\Adapter\Adapter');
            return new $name($services, $dbAdapter);
        }
    }

You would then register it like:

    'abstract_factories => array(
        'DbAwareFactory',
    ),

Both initializers and abstract factories were designed with this
problem in mind. While I like initializers, Marco Pivetta points out
that (a) if you're not aware they exist, they may lead to hard to
trace issues when injections you don't expect happen, and (b) if you
do not register an initializer, but thought it was already, again, you
can have some potential hard-to-trace issues. If you feel those might
be problems, the abstract factory is the way to go.

> On Mon, Feb 25, 2013 at 3:08 PM, Matthew Weier O'Phinney
> <[hidden email]>wrote:
>
>> On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <[hidden email]>
>> wrote:
>> > Matthew, thanks for this elaborate answer. Makes a lot of things clearer
>> > for me.
>> >
>> > So, in that case I stick to injecting everything I need. This really
>> bloats
>> > my module.config.php with a lot of redundant stuff and slows me down
>> quite
>> > a bit, but I see your point.
>>
>>
>> What kind of redundant stuff are you seeing? Depending on what it is,
>> and whether others report similarly, we may be able to come up with
>> some solutions to reduce repetition.
>>
>>
>> > On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
>> > <[hidden email]>wrote:
>> >
>> >> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <
>> [hidden email]>
>> >> wrote:
>> >> > one problem drives me nuts, since I started working with ZF2 (never
>> >> worked
>> >> > with Version 1):
>> >> >
>> >> > I'm in need of the service manager all the time. E.g. to access my
>> config
>> >> > in config/autoload/local.php via $sm->get('Config'). I need the
>> service
>> >> > manager everywhere. Controllers, models, you name it. And I don’t
>> want to
>> >> > pass it around by hand, which would make everything ugly.
>> >> >
>> >> > Now I’ve started to implement ServiceLocatorAwareInterface in most
>> >> classes.
>> >>
>> >> Don't.
>> >>
>> >> The best option is to define factories for your classes that inject
>> >> the dependencies for you. When you do that, you no longer have hidden
>> >> dependencies, and you have everything you need up front. When you have
>> >> an instance of the object, it's fully configured.
>> >>
>> >> If you need configuration, you're doing it wrong -- that configuration
>> >> should either be injected, or the objects the configuration
>> >> defines/configures should be injected.
>> >>
>> >> As an example, let's consider a common controller. Let's say it makes
>> >> use of a TableGateway, and you have one or more methods in that
>> >> TableGateway that return a paginator instance. You want to be able to
>> >> specify how many results per page the paginator should use. And for
>> >> insert()/update() operations, you want this information tied to a form
>> >> so that the validation is done correctly; however, you want a
>> >> different form based on the operation (new vs. edit). One element of
>> >> the form needs a DB instance in order to validate.
>> >>
>> >> In ZF1, you'd likely do the following:
>> >>
>> >> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
>> >> * Create a TableGateway instance, and inject the DB adapter.
>> >> * Pull configuration from the registry or the bootstrap.
>> >> * Use that configuration to tell the TableGateway how many items per
>> >> page to return for Paginator instances.
>> >> * You'd create a different form for each operation, and inject the DB
>> >> adapter you pulled.
>> >>
>> >> This is a fair bit of work. And it really, really doesn't belong in
>> >> your controller, any of it.
>> >>
>> >> Let's look at how to do it in ZF2.
>> >>
>> >> First, I'd create a factory for the TableGateway.
>> >>
>> >>     'my-table-gateway' => function ($services) {
>> >>         $db = $services->get('db');
>> >>         $config = $services->get('config');
>> >>         $perPage = isset($config['per_page']) ? $config['per_page'] :
>> 10;
>> >>         $tableGateway = new MyTableGateway($db, $perPage);
>> >>         return $tableGateway;
>> >>     }
>> >>
>> >> Note that in the _factory_ I'm pulling the configuration, but then I'm
>> >> using the values I retrieve from that in order to construct my table
>> >> gateway -- this decouples the TableGateway from my configuration. Also
>> >> note that I'm retrieving the adapter from the service manager --
>> >> dependencies are defined as additional services, and I consume them in
>> >> my factories. How is that dependency defined? As a factory:
>> >>
>> >>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
>> >>
>> >> Next, let's consider my form and validators. In 2.1, we added the
>> >> ability to define form elements, filters, and validators via plugin
>> >> managers which are managed via the application service manager. This
>> >> means that I can create factories for my forms that consume these. By
>> >> default, if you
>> >>
>> >>     'validators' => array('factories' => array(
>> >>         'MyRecordExists' => function ($validators) {
>> >>             $services = $validators->getServiceLocator();
>> >>             $db          = $services->get('db');
>> >>             return new \Zend\Validator\Db\RecordExists(array(
>> >>                 'adapter' => $db,
>> >>                 'table' => 'some_table',
>> >>                 'field' => 'some_field',
>> >>             ));
>> >>         },
>> >>     )),
>> >>     'services' => array('factories' => array(
>> >>         'MyCustomForm' => function($services) {
>> >>             $validators = $services->get('ValidatorPluginManager');
>> >>             $validatorChain = new \Zend\Validator\ValidatorChain();
>> >>             $validatorChain->setPluginManager($validators);
>> >>
>> >>             $inputFilterFactory = new \Zend\InputFilter\Factory();
>> >>
>> $inputFilterFactory->setDefaultValidatorChain($validatorChain);
>> >>             $inputFilter = new \Zend\InputFilter\InputFilter();
>> >>             $inputFilter->setFactory($inputFilterFactory);
>> >>
>> >>             return new MyCustomForm('my-custom-form',
>> >> array('input_filter' => $inputFilter));
>> >>         },
>> >>     )),
>> >>
>> >> In the first case, we've provided a factory for
>> >> Zend\Validator\Db\RecordExists that ensures that it is configured with
>> >> a DB adapter, and the table and field names we require; note that it
>> >> uses its own service name, which allows us to have multiple instances
>> >> of the RecordExists validator with different configurations. In the
>> >> second case, we create a factory for our form. In there, we create an
>> >> input filter instance that has an InputFilter factory passed to it;
>> >> that factory is seeded with a validator chain that has our custom
>> >> validator plugins in it.
>> >>
>> >> Note that all of this is decoupled from our controller. This allows us
>> >> to test any piece of it individually, as well as to re-use it in areas
>> >> outside our controller if desired.
>> >>
>> >> Now, for the controller:
>> >>
>> >>     'controllers' => array('factories' => array(
>> >>         'MyController' => function ($controllers) {
>> >>             $services = $controllers->getServiceLocator();
>> >>             $tableGateway = $services->get('my-table-gateway');
>> >>             $form = $services->get('MyCustomForm');
>> >>             $controller = new MyController();
>> >>             $controller->setTableGateway($tableGateway);
>> >>             $controller->setForm($form);
>> >>             return $controller;
>> >>         },
>> >>     )),
>> >>
>> >> We grab dependencies, instantiate our controller, and inject the
>> >> dependencies. Nice and clean.
>> >>
>> >> Inside our controller, we simply use those dependencies:
>> >>
>> >>     public function newAction()
>> >>     {
>> >>         $this->form->setValidationGroup('username', 'password',
>> >> 'confirmation');
>> >>         $this->form->setData($this->getRequest()->getPost()->toArray());
>> >>         if (!$this->form->isValid()) {
>> >>             return new ViewModel(array(
>> >>                 'form' => $this->form,
>> >>                 'success' => false,
>> >>             ));
>> >>         }
>> >>         $this->table->insert($this->form->getData());
>> >>         $this->redirect()->toRoute('user/profile');
>> >>     }
>> >>
>> >> The controller contains no logic for creating the dependencies, or
>> >> even fetching them; it simply has setters:
>> >>
>> >>     protected $table;
>> >>
>> >>     public function setTableGateway(TableGateway $tableGateway)
>> >>     {
>> >>         $this->table = $tableGateway;
>> >>     }
>> >>
>> >> This allows the controller to be tested easily, and keeps the messy
>> >> logic of obtaining dependencies where it should be -- in factories,
>> >> elsewhere.
>> >>
>> >> Notice that I never pass around the service manager or service
>> >> locator. If a class needs a dependency, I create a factory for that
>> >> class, and use that factory to fetch dependencies and inject them.
>> >> This keeps the logic clean inside the individual classes, as they are
>> >> only operating on the dependencies passed to them; they don't worry
>> >> about instantiating dependencies, or about fetching them. They simply
>> >> assume they have them.
>> >>
>> >> > But this has the two downsides for me:
>> >> >
>> >> > 1. The classes which make use of my ServiceLocatorAware classes need
>> to
>> >> > have acces to the service locater, as well, in order to instantiate
>> the
>> >> > objects. So even more ServiceLocatorAware classes and even more
>> >> invokables
>> >> > to be added to module.config.php.
>> >>
>> >> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
>> >>
>> >> Second, you _should_ define services for the service manager. This is
>> >> a good practice. Those services will only be instantiated if the
>> >> current request needs them. Furthermore, defining them means that
>> >> someone later can provide _substitutions_ for them. This is
>> >> tremendously powerful -- it allows developers to extend your class,
>> >> and still have it injected where it needs to be. (I've done this to
>> >> work around issues I've found in the past!)
>> >>
>> >> > 2. The service manager is a property of all ServiceLocatorAware
>> classes,
>> >> > which can be difficult if you want to persist a ServiceLocatorAware
>> model
>> >> > e.g. to the session.
>> >>
>> >> Again, don't make things ServiceLocatorAware. And if you do, don't
>> >> persist them to the session. You should persist very little to the
>> >> session, and your models typically should be plain old PHP objects
>> >> anyways, without knowledge of persistence. If you're tying them to the
>> >> persistence layer directly, use hydrators to extract information as
>> >> well as hydrate them, if you need to serialize them into the session.
>> >>
>> >> > Now I'm starting to get the feeling the I haven’t really understood
>> ZF2
>> >> and
>> >> > the service manager. Injecting everything cannot be the solution, can
>> it?
>> >>
>> >> Yes, it is. Because it solves the problems of re-usability,
>> >> substitution, and dependency resolution -- all of which were problems
>> >> in ZF1.
>> >>
>> >> > Should I really inject e.g. my global config into all classes?
>> >>
>> >> No. Extract the configuration you need inside a factory, and use that
>> >> to create an instance. Your objects should only get exactly what they
>> >> need, no more, no less.
>> >>
>> >> > And if you
>> >> > don’t want to have plain entity models but business models with some
>> >> > process logic in them, you cannot abstain from the service manager
>> >> either.
>> >>
>> >> There are ways to do this, too - service layers can compose
>> >> context-specific service managers, or be injected with the
>> >> services/configuration that the domain layer may need in order to
>> >> operate. You can then use this information when constructing domain
>> >> object instances, to create prototypes of domain objects, or to seed
>> >> factories. There are many options here.
>> >>
>> >>
>> >> --
>> >> Matthew Weier O'Phinney
>> >> Project Lead            | [hidden email]
>> >> Zend Framework          | http://framework.zend.com/
>> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>> >>
>>
>>
>>
>> --
>> Matthew Weier O'Phinney
>> Project Lead            | [hidden email]
>> Zend Framework          | http://framework.zend.com/
>> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>>
>> --
>> List: [hidden email]
>> Info: http://framework.zend.com/archives
>> Unsubscribe: [hidden email]
>>
>>
>>



--
Matthew Weier O'Phinney
Project Lead            | [hidden email]
Zend Framework          | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc

--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

roberto blanko
Having seen both ways, initializers and abstract factories (which were new
to me), I’d go for the latter. However I still do not feel good about fact,
that DbAwareFactory::canCreateServiceWithName() is being called for every
service initiation. But if, as you stated, the performance impact is
minimal, I’ll go with it.

Probably a nice way to handle things would be something like this:

    'abstract_factories => array(
        'DbAwareFactory' => array(
            'Main\Model\AccountRepository',
            'Main\Model\GoogleRepository',
            'Main\Model\StreamRepository',
            'Main\Model\TwitterRepository',
        )
    ),

Would pull the class names out of the AbstractFactory and give the service
manager a chance to know whether to call an abstract factory or not.

Anyways, this discussion has been very enlightening to me! Thanks for your
time to both of you!


On Tue, Feb 26, 2013 at 7:15 PM, Matthew Weier O'Phinney
<[hidden email]>wrote:

> On Tue, Feb 26, 2013 at 3:46 AM, roberto blanko <[hidden email]>
> wrote:
> > In terms of redundancy, this is just a simple example but should make my
> > point clear:
> >
> > return array(
> >     'service_manager' => array(
> >         'factories' => array(
> >             'Main\Model\StreamRepository' => function($sm) {
> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
> >                 return new \Main\Model\StreamRepository($sm, $dbAdapter);
> >             },
> >             'Main\Model\AccountRepository' => function($sm) {
> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
> >                 return new \Main\Model\AccountRepository($sm,
> $dbAdapter);
> >             },
> >             'Main\Model\UserRepository' => function($sm) {
> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
> >                 return new \Main\Model\TwitterRepository($sm,
> $dbAdapter);
> >             },
> >             'Main\Model\ProjectRepository' => function($sm) {
> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
> >                 return new \Main\Model\GoogleRepository($sm, $dbAdapter);
> >             },
> >         ),
> >     ),
> > );
> >
> > Each of my repositories (which extends AbstractTableGateway) needs to
> have
> > the DB adapter injected. It would make things faster, if factories could
> be
> > defined based on inherited classes. E.g. in the case above the factories
> > for all classes inheriting from Main\Model\AbstractRepository need to
> have
> > the same factory. Or is there a solution for this already? I could use an
> > initializer class but this wouldn’t perform very well, would it? Since
> the
> > initializer would be called whether I needed it or not.
>
> There are two approaches you could use here.
>
> First is an initializer. There is actually very little performance
> impact from that, and it would reduce the above to four lines (one
> line for each class, defined as invokables).
>
> Second is to use an AbstractFactory. The idea behind the
> AbstractFactory is when you have many classes that have the same
> creational pattern. You might define it like this:
>
>     use Zend\ServiceManager\AbstractFactoryInterface;
>     use Zend\ServiceManager\ServiceLocatorInterface;
>
>     class DbAwareFactory implements AbstractFactoryInterface
>     {
>         protected $serviceClasses = array(
>             'Main\Model\AccountRepository',
>             'Main\Model\GoogleRepository',
>             'Main\Model\StreamRepository',
>             'Main\Model\TwitterRepository',
>         );
>
>         public function
> canCreateServiceWithName(ServiceLocatorInterface $services, $name,
> $requestedName)
>         {
>             return in_array($name, $this->serviceClasses);
>         }
>
>         public function createServiceWithName(ServiceLocatorInterface
> $services, $name, $requestedName)
>         {
>             $dbAdapter = $services->get('Zend\Db\Adapter\Adapter');
>             return new $name($services, $dbAdapter);
>         }
>     }
>
> You would then register it like:
>
>     'abstract_factories => array(
>         'DbAwareFactory',
>     ),
>
> Both initializers and abstract factories were designed with this
> problem in mind. While I like initializers, Marco Pivetta points out
> that (a) if you're not aware they exist, they may lead to hard to
> trace issues when injections you don't expect happen, and (b) if you
> do not register an initializer, but thought it was already, again, you
> can have some potential hard-to-trace issues. If you feel those might
> be problems, the abstract factory is the way to go.
>
> > On Mon, Feb 25, 2013 at 3:08 PM, Matthew Weier O'Phinney
> > <[hidden email]>wrote:
> >
> >> On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <
> [hidden email]>
> >> wrote:
> >> > Matthew, thanks for this elaborate answer. Makes a lot of things
> clearer
> >> > for me.
> >> >
> >> > So, in that case I stick to injecting everything I need. This really
> >> bloats
> >> > my module.config.php with a lot of redundant stuff and slows me down
> >> quite
> >> > a bit, but I see your point.
> >>
> >>
> >> What kind of redundant stuff are you seeing? Depending on what it is,
> >> and whether others report similarly, we may be able to come up with
> >> some solutions to reduce repetition.
> >>
> >>
> >> > On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
> >> > <[hidden email]>wrote:
> >> >
> >> >> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <
> >> [hidden email]>
> >> >> wrote:
> >> >> > one problem drives me nuts, since I started working with ZF2 (never
> >> >> worked
> >> >> > with Version 1):
> >> >> >
> >> >> > I'm in need of the service manager all the time. E.g. to access my
> >> config
> >> >> > in config/autoload/local.php via $sm->get('Config'). I need the
> >> service
> >> >> > manager everywhere. Controllers, models, you name it. And I don’t
> >> want to
> >> >> > pass it around by hand, which would make everything ugly.
> >> >> >
> >> >> > Now I’ve started to implement ServiceLocatorAwareInterface in most
> >> >> classes.
> >> >>
> >> >> Don't.
> >> >>
> >> >> The best option is to define factories for your classes that inject
> >> >> the dependencies for you. When you do that, you no longer have hidden
> >> >> dependencies, and you have everything you need up front. When you
> have
> >> >> an instance of the object, it's fully configured.
> >> >>
> >> >> If you need configuration, you're doing it wrong -- that
> configuration
> >> >> should either be injected, or the objects the configuration
> >> >> defines/configures should be injected.
> >> >>
> >> >> As an example, let's consider a common controller. Let's say it makes
> >> >> use of a TableGateway, and you have one or more methods in that
> >> >> TableGateway that return a paginator instance. You want to be able to
> >> >> specify how many results per page the paginator should use. And for
> >> >> insert()/update() operations, you want this information tied to a
> form
> >> >> so that the validation is done correctly; however, you want a
> >> >> different form based on the operation (new vs. edit). One element of
> >> >> the form needs a DB instance in order to validate.
> >> >>
> >> >> In ZF1, you'd likely do the following:
> >> >>
> >> >> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
> >> >> * Create a TableGateway instance, and inject the DB adapter.
> >> >> * Pull configuration from the registry or the bootstrap.
> >> >> * Use that configuration to tell the TableGateway how many items per
> >> >> page to return for Paginator instances.
> >> >> * You'd create a different form for each operation, and inject the DB
> >> >> adapter you pulled.
> >> >>
> >> >> This is a fair bit of work. And it really, really doesn't belong in
> >> >> your controller, any of it.
> >> >>
> >> >> Let's look at how to do it in ZF2.
> >> >>
> >> >> First, I'd create a factory for the TableGateway.
> >> >>
> >> >>     'my-table-gateway' => function ($services) {
> >> >>         $db = $services->get('db');
> >> >>         $config = $services->get('config');
> >> >>         $perPage = isset($config['per_page']) ? $config['per_page'] :
> >> 10;
> >> >>         $tableGateway = new MyTableGateway($db, $perPage);
> >> >>         return $tableGateway;
> >> >>     }
> >> >>
> >> >> Note that in the _factory_ I'm pulling the configuration, but then
> I'm
> >> >> using the values I retrieve from that in order to construct my table
> >> >> gateway -- this decouples the TableGateway from my configuration.
> Also
> >> >> note that I'm retrieving the adapter from the service manager --
> >> >> dependencies are defined as additional services, and I consume them
> in
> >> >> my factories. How is that dependency defined? As a factory:
> >> >>
> >> >>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
> >> >>
> >> >> Next, let's consider my form and validators. In 2.1, we added the
> >> >> ability to define form elements, filters, and validators via plugin
> >> >> managers which are managed via the application service manager. This
> >> >> means that I can create factories for my forms that consume these. By
> >> >> default, if you
> >> >>
> >> >>     'validators' => array('factories' => array(
> >> >>         'MyRecordExists' => function ($validators) {
> >> >>             $services = $validators->getServiceLocator();
> >> >>             $db          = $services->get('db');
> >> >>             return new \Zend\Validator\Db\RecordExists(array(
> >> >>                 'adapter' => $db,
> >> >>                 'table' => 'some_table',
> >> >>                 'field' => 'some_field',
> >> >>             ));
> >> >>         },
> >> >>     )),
> >> >>     'services' => array('factories' => array(
> >> >>         'MyCustomForm' => function($services) {
> >> >>             $validators = $services->get('ValidatorPluginManager');
> >> >>             $validatorChain = new \Zend\Validator\ValidatorChain();
> >> >>             $validatorChain->setPluginManager($validators);
> >> >>
> >> >>             $inputFilterFactory = new \Zend\InputFilter\Factory();
> >> >>
> >> $inputFilterFactory->setDefaultValidatorChain($validatorChain);
> >> >>             $inputFilter = new \Zend\InputFilter\InputFilter();
> >> >>             $inputFilter->setFactory($inputFilterFactory);
> >> >>
> >> >>             return new MyCustomForm('my-custom-form',
> >> >> array('input_filter' => $inputFilter));
> >> >>         },
> >> >>     )),
> >> >>
> >> >> In the first case, we've provided a factory for
> >> >> Zend\Validator\Db\RecordExists that ensures that it is configured
> with
> >> >> a DB adapter, and the table and field names we require; note that it
> >> >> uses its own service name, which allows us to have multiple instances
> >> >> of the RecordExists validator with different configurations. In the
> >> >> second case, we create a factory for our form. In there, we create an
> >> >> input filter instance that has an InputFilter factory passed to it;
> >> >> that factory is seeded with a validator chain that has our custom
> >> >> validator plugins in it.
> >> >>
> >> >> Note that all of this is decoupled from our controller. This allows
> us
> >> >> to test any piece of it individually, as well as to re-use it in
> areas
> >> >> outside our controller if desired.
> >> >>
> >> >> Now, for the controller:
> >> >>
> >> >>     'controllers' => array('factories' => array(
> >> >>         'MyController' => function ($controllers) {
> >> >>             $services = $controllers->getServiceLocator();
> >> >>             $tableGateway = $services->get('my-table-gateway');
> >> >>             $form = $services->get('MyCustomForm');
> >> >>             $controller = new MyController();
> >> >>             $controller->setTableGateway($tableGateway);
> >> >>             $controller->setForm($form);
> >> >>             return $controller;
> >> >>         },
> >> >>     )),
> >> >>
> >> >> We grab dependencies, instantiate our controller, and inject the
> >> >> dependencies. Nice and clean.
> >> >>
> >> >> Inside our controller, we simply use those dependencies:
> >> >>
> >> >>     public function newAction()
> >> >>     {
> >> >>         $this->form->setValidationGroup('username', 'password',
> >> >> 'confirmation');
> >> >>
> $this->form->setData($this->getRequest()->getPost()->toArray());
> >> >>         if (!$this->form->isValid()) {
> >> >>             return new ViewModel(array(
> >> >>                 'form' => $this->form,
> >> >>                 'success' => false,
> >> >>             ));
> >> >>         }
> >> >>         $this->table->insert($this->form->getData());
> >> >>         $this->redirect()->toRoute('user/profile');
> >> >>     }
> >> >>
> >> >> The controller contains no logic for creating the dependencies, or
> >> >> even fetching them; it simply has setters:
> >> >>
> >> >>     protected $table;
> >> >>
> >> >>     public function setTableGateway(TableGateway $tableGateway)
> >> >>     {
> >> >>         $this->table = $tableGateway;
> >> >>     }
> >> >>
> >> >> This allows the controller to be tested easily, and keeps the messy
> >> >> logic of obtaining dependencies where it should be -- in factories,
> >> >> elsewhere.
> >> >>
> >> >> Notice that I never pass around the service manager or service
> >> >> locator. If a class needs a dependency, I create a factory for that
> >> >> class, and use that factory to fetch dependencies and inject them.
> >> >> This keeps the logic clean inside the individual classes, as they are
> >> >> only operating on the dependencies passed to them; they don't worry
> >> >> about instantiating dependencies, or about fetching them. They simply
> >> >> assume they have them.
> >> >>
> >> >> > But this has the two downsides for me:
> >> >> >
> >> >> > 1. The classes which make use of my ServiceLocatorAware classes
> need
> >> to
> >> >> > have acces to the service locater, as well, in order to instantiate
> >> the
> >> >> > objects. So even more ServiceLocatorAware classes and even more
> >> >> invokables
> >> >> > to be added to module.config.php.
> >> >>
> >> >> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
> >> >>
> >> >> Second, you _should_ define services for the service manager. This is
> >> >> a good practice. Those services will only be instantiated if the
> >> >> current request needs them. Furthermore, defining them means that
> >> >> someone later can provide _substitutions_ for them. This is
> >> >> tremendously powerful -- it allows developers to extend your class,
> >> >> and still have it injected where it needs to be. (I've done this to
> >> >> work around issues I've found in the past!)
> >> >>
> >> >> > 2. The service manager is a property of all ServiceLocatorAware
> >> classes,
> >> >> > which can be difficult if you want to persist a ServiceLocatorAware
> >> model
> >> >> > e.g. to the session.
> >> >>
> >> >> Again, don't make things ServiceLocatorAware. And if you do, don't
> >> >> persist them to the session. You should persist very little to the
> >> >> session, and your models typically should be plain old PHP objects
> >> >> anyways, without knowledge of persistence. If you're tying them to
> the
> >> >> persistence layer directly, use hydrators to extract information as
> >> >> well as hydrate them, if you need to serialize them into the session.
> >> >>
> >> >> > Now I'm starting to get the feeling the I haven’t really understood
> >> ZF2
> >> >> and
> >> >> > the service manager. Injecting everything cannot be the solution,
> can
> >> it?
> >> >>
> >> >> Yes, it is. Because it solves the problems of re-usability,
> >> >> substitution, and dependency resolution -- all of which were problems
> >> >> in ZF1.
> >> >>
> >> >> > Should I really inject e.g. my global config into all classes?
> >> >>
> >> >> No. Extract the configuration you need inside a factory, and use that
> >> >> to create an instance. Your objects should only get exactly what they
> >> >> need, no more, no less.
> >> >>
> >> >> > And if you
> >> >> > don’t want to have plain entity models but business models with
> some
> >> >> > process logic in them, you cannot abstain from the service manager
> >> >> either.
> >> >>
> >> >> There are ways to do this, too - service layers can compose
> >> >> context-specific service managers, or be injected with the
> >> >> services/configuration that the domain layer may need in order to
> >> >> operate. You can then use this information when constructing domain
> >> >> object instances, to create prototypes of domain objects, or to seed
> >> >> factories. There are many options here.
> >> >>
> >> >>
> >> >> --
> >> >> Matthew Weier O'Phinney
> >> >> Project Lead            | [hidden email]
> >> >> Zend Framework          | http://framework.zend.com/
> >> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
> >> >>
> >>
> >>
> >>
> >> --
> >> Matthew Weier O'Phinney
> >> Project Lead            | [hidden email]
> >> Zend Framework          | http://framework.zend.com/
> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
> >>
> >> --
> >> List: [hidden email]
> >> Info: http://framework.zend.com/archives
> >> Unsubscribe: [hidden email]
> >>
> >>
> >>
>
>
>
> --
> Matthew Weier O'Phinney
> Project Lead            | [hidden email]
> Zend Framework          | http://framework.zend.com/
> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>
Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

weierophinney
Administrator
On Mon, Mar 4, 2013 at 10:51 AM, roberto blanko <[hidden email]> wrote:
> Having seen both ways, initializers and abstract factories (which were new
> to me), I’d go for the latter. However I still do not feel good about fact,
> that DbAwareFactory::canCreateServiceWithName() is being called for every
> service initiation.

It won't be. It will only be called if no explicit service, factory,
or invokable is found first. Additionally, abstract factories are only
checked until one returns "true" from canCreateServiceWithName().
Checks for service/factory/invokable are hashmap lookups, so they're
very fast. The logic in canCreateServiceWithName() should similarly be
very lightweight.

> But if, as you stated, the performance impact is
> minimal, I’ll go with it.

It is -- particularly in 5.4, where method/function calls have become
quite cheap. Keep in mind the precedence rules I stated above, and you
should not see any issues.

> Probably a nice way to handle things would be something like this:
>
>     'abstract_factories => array(
>         'DbAwareFactory' => array(
>             'Main\Model\AccountRepository',
>             'Main\Model\GoogleRepository',
>             'Main\Model\StreamRepository',
>             'Main\Model\TwitterRepository',
>         )
>     ),
>
> Would pull the class names out of the AbstractFactory and give the service
> manager a chance to know whether to call an abstract factory or not.

Well, the problem is: what does the above mean? Inside the
ServiceManager itself, we're really only saying "if no lookup is found
in the existing services, factories, or invokables, let's ask each of
the abstract managers if they can handle it." I think adding logic
saying, "map this class to this abstract factory" will complicate the
internals quite a bit, and potentially lead to some performance
degradation. If, on the other hand, you wanted to pass the array as an
argument to the abstract factory _constructor_, however, that might be
something we could add to Zend\ServiceManager\ServiceConfig. However,
since we have no requirements on the constructors, this may lead to
some issues where somebody passes such an array to an abstract factory
that doesn't define a constructor, which would lead to configuration
loss.

Still, something to think about!

>
> Anyways, this discussion has been very enlightening to me! Thanks for your
> time to both of you!
>
>
> On Tue, Feb 26, 2013 at 7:15 PM, Matthew Weier O'Phinney
> <[hidden email]>wrote:
>
>> On Tue, Feb 26, 2013 at 3:46 AM, roberto blanko <[hidden email]>
>> wrote:
>> > In terms of redundancy, this is just a simple example but should make my
>> > point clear:
>> >
>> > return array(
>> >     'service_manager' => array(
>> >         'factories' => array(
>> >             'Main\Model\StreamRepository' => function($sm) {
>> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>> >                 return new \Main\Model\StreamRepository($sm, $dbAdapter);
>> >             },
>> >             'Main\Model\AccountRepository' => function($sm) {
>> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>> >                 return new \Main\Model\AccountRepository($sm,
>> $dbAdapter);
>> >             },
>> >             'Main\Model\UserRepository' => function($sm) {
>> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>> >                 return new \Main\Model\TwitterRepository($sm,
>> $dbAdapter);
>> >             },
>> >             'Main\Model\ProjectRepository' => function($sm) {
>> >                 $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
>> >                 return new \Main\Model\GoogleRepository($sm, $dbAdapter);
>> >             },
>> >         ),
>> >     ),
>> > );
>> >
>> > Each of my repositories (which extends AbstractTableGateway) needs to
>> have
>> > the DB adapter injected. It would make things faster, if factories could
>> be
>> > defined based on inherited classes. E.g. in the case above the factories
>> > for all classes inheriting from Main\Model\AbstractRepository need to
>> have
>> > the same factory. Or is there a solution for this already? I could use an
>> > initializer class but this wouldn’t perform very well, would it? Since
>> the
>> > initializer would be called whether I needed it or not.
>>
>> There are two approaches you could use here.
>>
>> First is an initializer. There is actually very little performance
>> impact from that, and it would reduce the above to four lines (one
>> line for each class, defined as invokables).
>>
>> Second is to use an AbstractFactory. The idea behind the
>> AbstractFactory is when you have many classes that have the same
>> creational pattern. You might define it like this:
>>
>>     use Zend\ServiceManager\AbstractFactoryInterface;
>>     use Zend\ServiceManager\ServiceLocatorInterface;
>>
>>     class DbAwareFactory implements AbstractFactoryInterface
>>     {
>>         protected $serviceClasses = array(
>>             'Main\Model\AccountRepository',
>>             'Main\Model\GoogleRepository',
>>             'Main\Model\StreamRepository',
>>             'Main\Model\TwitterRepository',
>>         );
>>
>>         public function
>> canCreateServiceWithName(ServiceLocatorInterface $services, $name,
>> $requestedName)
>>         {
>>             return in_array($name, $this->serviceClasses);
>>         }
>>
>>         public function createServiceWithName(ServiceLocatorInterface
>> $services, $name, $requestedName)
>>         {
>>             $dbAdapter = $services->get('Zend\Db\Adapter\Adapter');
>>             return new $name($services, $dbAdapter);
>>         }
>>     }
>>
>> You would then register it like:
>>
>>     'abstract_factories => array(
>>         'DbAwareFactory',
>>     ),
>>
>> Both initializers and abstract factories were designed with this
>> problem in mind. While I like initializers, Marco Pivetta points out
>> that (a) if you're not aware they exist, they may lead to hard to
>> trace issues when injections you don't expect happen, and (b) if you
>> do not register an initializer, but thought it was already, again, you
>> can have some potential hard-to-trace issues. If you feel those might
>> be problems, the abstract factory is the way to go.
>>
>> > On Mon, Feb 25, 2013 at 3:08 PM, Matthew Weier O'Phinney
>> > <[hidden email]>wrote:
>> >
>> >> On Sat, Feb 23, 2013 at 4:31 PM, roberto blanko <
>> [hidden email]>
>> >> wrote:
>> >> > Matthew, thanks for this elaborate answer. Makes a lot of things
>> clearer
>> >> > for me.
>> >> >
>> >> > So, in that case I stick to injecting everything I need. This really
>> >> bloats
>> >> > my module.config.php with a lot of redundant stuff and slows me down
>> >> quite
>> >> > a bit, but I see your point.
>> >>
>> >>
>> >> What kind of redundant stuff are you seeing? Depending on what it is,
>> >> and whether others report similarly, we may be able to come up with
>> >> some solutions to reduce repetition.
>> >>
>> >>
>> >> > On Tue, Feb 19, 2013 at 5:08 PM, Matthew Weier O'Phinney
>> >> > <[hidden email]>wrote:
>> >> >
>> >> >> On Tue, Feb 19, 2013 at 3:37 AM, roberto blanko <
>> >> [hidden email]>
>> >> >> wrote:
>> >> >> > one problem drives me nuts, since I started working with ZF2 (never
>> >> >> worked
>> >> >> > with Version 1):
>> >> >> >
>> >> >> > I'm in need of the service manager all the time. E.g. to access my
>> >> config
>> >> >> > in config/autoload/local.php via $sm->get('Config'). I need the
>> >> service
>> >> >> > manager everywhere. Controllers, models, you name it. And I don’t
>> >> want to
>> >> >> > pass it around by hand, which would make everything ugly.
>> >> >> >
>> >> >> > Now I’ve started to implement ServiceLocatorAwareInterface in most
>> >> >> classes.
>> >> >>
>> >> >> Don't.
>> >> >>
>> >> >> The best option is to define factories for your classes that inject
>> >> >> the dependencies for you. When you do that, you no longer have hidden
>> >> >> dependencies, and you have everything you need up front. When you
>> have
>> >> >> an instance of the object, it's fully configured.
>> >> >>
>> >> >> If you need configuration, you're doing it wrong -- that
>> configuration
>> >> >> should either be injected, or the objects the configuration
>> >> >> defines/configures should be injected.
>> >> >>
>> >> >> As an example, let's consider a common controller. Let's say it makes
>> >> >> use of a TableGateway, and you have one or more methods in that
>> >> >> TableGateway that return a paginator instance. You want to be able to
>> >> >> specify how many results per page the paginator should use. And for
>> >> >> insert()/update() operations, you want this information tied to a
>> form
>> >> >> so that the validation is done correctly; however, you want a
>> >> >> different form based on the operation (new vs. edit). One element of
>> >> >> the form needs a DB instance in order to validate.
>> >> >>
>> >> >> In ZF1, you'd likely do the following:
>> >> >>
>> >> >> * Pull the "db" resource from the bootstrap (actually, a DB adapter).
>> >> >> * Create a TableGateway instance, and inject the DB adapter.
>> >> >> * Pull configuration from the registry or the bootstrap.
>> >> >> * Use that configuration to tell the TableGateway how many items per
>> >> >> page to return for Paginator instances.
>> >> >> * You'd create a different form for each operation, and inject the DB
>> >> >> adapter you pulled.
>> >> >>
>> >> >> This is a fair bit of work. And it really, really doesn't belong in
>> >> >> your controller, any of it.
>> >> >>
>> >> >> Let's look at how to do it in ZF2.
>> >> >>
>> >> >> First, I'd create a factory for the TableGateway.
>> >> >>
>> >> >>     'my-table-gateway' => function ($services) {
>> >> >>         $db = $services->get('db');
>> >> >>         $config = $services->get('config');
>> >> >>         $perPage = isset($config['per_page']) ? $config['per_page'] :
>> >> 10;
>> >> >>         $tableGateway = new MyTableGateway($db, $perPage);
>> >> >>         return $tableGateway;
>> >> >>     }
>> >> >>
>> >> >> Note that in the _factory_ I'm pulling the configuration, but then
>> I'm
>> >> >> using the values I retrieve from that in order to construct my table
>> >> >> gateway -- this decouples the TableGateway from my configuration.
>> Also
>> >> >> note that I'm retrieving the adapter from the service manager --
>> >> >> dependencies are defined as additional services, and I consume them
>> in
>> >> >> my factories. How is that dependency defined? As a factory:
>> >> >>
>> >> >>     'db' => 'Zend\Db\Adapter\AdapterFactoryService',
>> >> >>
>> >> >> Next, let's consider my form and validators. In 2.1, we added the
>> >> >> ability to define form elements, filters, and validators via plugin
>> >> >> managers which are managed via the application service manager. This
>> >> >> means that I can create factories for my forms that consume these. By
>> >> >> default, if you
>> >> >>
>> >> >>     'validators' => array('factories' => array(
>> >> >>         'MyRecordExists' => function ($validators) {
>> >> >>             $services = $validators->getServiceLocator();
>> >> >>             $db          = $services->get('db');
>> >> >>             return new \Zend\Validator\Db\RecordExists(array(
>> >> >>                 'adapter' => $db,
>> >> >>                 'table' => 'some_table',
>> >> >>                 'field' => 'some_field',
>> >> >>             ));
>> >> >>         },
>> >> >>     )),
>> >> >>     'services' => array('factories' => array(
>> >> >>         'MyCustomForm' => function($services) {
>> >> >>             $validators = $services->get('ValidatorPluginManager');
>> >> >>             $validatorChain = new \Zend\Validator\ValidatorChain();
>> >> >>             $validatorChain->setPluginManager($validators);
>> >> >>
>> >> >>             $inputFilterFactory = new \Zend\InputFilter\Factory();
>> >> >>
>> >> $inputFilterFactory->setDefaultValidatorChain($validatorChain);
>> >> >>             $inputFilter = new \Zend\InputFilter\InputFilter();
>> >> >>             $inputFilter->setFactory($inputFilterFactory);
>> >> >>
>> >> >>             return new MyCustomForm('my-custom-form',
>> >> >> array('input_filter' => $inputFilter));
>> >> >>         },
>> >> >>     )),
>> >> >>
>> >> >> In the first case, we've provided a factory for
>> >> >> Zend\Validator\Db\RecordExists that ensures that it is configured
>> with
>> >> >> a DB adapter, and the table and field names we require; note that it
>> >> >> uses its own service name, which allows us to have multiple instances
>> >> >> of the RecordExists validator with different configurations. In the
>> >> >> second case, we create a factory for our form. In there, we create an
>> >> >> input filter instance that has an InputFilter factory passed to it;
>> >> >> that factory is seeded with a validator chain that has our custom
>> >> >> validator plugins in it.
>> >> >>
>> >> >> Note that all of this is decoupled from our controller. This allows
>> us
>> >> >> to test any piece of it individually, as well as to re-use it in
>> areas
>> >> >> outside our controller if desired.
>> >> >>
>> >> >> Now, for the controller:
>> >> >>
>> >> >>     'controllers' => array('factories' => array(
>> >> >>         'MyController' => function ($controllers) {
>> >> >>             $services = $controllers->getServiceLocator();
>> >> >>             $tableGateway = $services->get('my-table-gateway');
>> >> >>             $form = $services->get('MyCustomForm');
>> >> >>             $controller = new MyController();
>> >> >>             $controller->setTableGateway($tableGateway);
>> >> >>             $controller->setForm($form);
>> >> >>             return $controller;
>> >> >>         },
>> >> >>     )),
>> >> >>
>> >> >> We grab dependencies, instantiate our controller, and inject the
>> >> >> dependencies. Nice and clean.
>> >> >>
>> >> >> Inside our controller, we simply use those dependencies:
>> >> >>
>> >> >>     public function newAction()
>> >> >>     {
>> >> >>         $this->form->setValidationGroup('username', 'password',
>> >> >> 'confirmation');
>> >> >>
>> $this->form->setData($this->getRequest()->getPost()->toArray());
>> >> >>         if (!$this->form->isValid()) {
>> >> >>             return new ViewModel(array(
>> >> >>                 'form' => $this->form,
>> >> >>                 'success' => false,
>> >> >>             ));
>> >> >>         }
>> >> >>         $this->table->insert($this->form->getData());
>> >> >>         $this->redirect()->toRoute('user/profile');
>> >> >>     }
>> >> >>
>> >> >> The controller contains no logic for creating the dependencies, or
>> >> >> even fetching them; it simply has setters:
>> >> >>
>> >> >>     protected $table;
>> >> >>
>> >> >>     public function setTableGateway(TableGateway $tableGateway)
>> >> >>     {
>> >> >>         $this->table = $tableGateway;
>> >> >>     }
>> >> >>
>> >> >> This allows the controller to be tested easily, and keeps the messy
>> >> >> logic of obtaining dependencies where it should be -- in factories,
>> >> >> elsewhere.
>> >> >>
>> >> >> Notice that I never pass around the service manager or service
>> >> >> locator. If a class needs a dependency, I create a factory for that
>> >> >> class, and use that factory to fetch dependencies and inject them.
>> >> >> This keeps the logic clean inside the individual classes, as they are
>> >> >> only operating on the dependencies passed to them; they don't worry
>> >> >> about instantiating dependencies, or about fetching them. They simply
>> >> >> assume they have them.
>> >> >>
>> >> >> > But this has the two downsides for me:
>> >> >> >
>> >> >> > 1. The classes which make use of my ServiceLocatorAware classes
>> need
>> >> to
>> >> >> > have acces to the service locater, as well, in order to instantiate
>> >> the
>> >> >> > objects. So even more ServiceLocatorAware classes and even more
>> >> >> invokables
>> >> >> > to be added to module.config.php.
>> >> >>
>> >> >> As noted, don't use ServiceLocatorAware. Always pass in dependencies.
>> >> >>
>> >> >> Second, you _should_ define services for the service manager. This is
>> >> >> a good practice. Those services will only be instantiated if the
>> >> >> current request needs them. Furthermore, defining them means that
>> >> >> someone later can provide _substitutions_ for them. This is
>> >> >> tremendously powerful -- it allows developers to extend your class,
>> >> >> and still have it injected where it needs to be. (I've done this to
>> >> >> work around issues I've found in the past!)
>> >> >>
>> >> >> > 2. The service manager is a property of all ServiceLocatorAware
>> >> classes,
>> >> >> > which can be difficult if you want to persist a ServiceLocatorAware
>> >> model
>> >> >> > e.g. to the session.
>> >> >>
>> >> >> Again, don't make things ServiceLocatorAware. And if you do, don't
>> >> >> persist them to the session. You should persist very little to the
>> >> >> session, and your models typically should be plain old PHP objects
>> >> >> anyways, without knowledge of persistence. If you're tying them to
>> the
>> >> >> persistence layer directly, use hydrators to extract information as
>> >> >> well as hydrate them, if you need to serialize them into the session.
>> >> >>
>> >> >> > Now I'm starting to get the feeling the I haven’t really understood
>> >> ZF2
>> >> >> and
>> >> >> > the service manager. Injecting everything cannot be the solution,
>> can
>> >> it?
>> >> >>
>> >> >> Yes, it is. Because it solves the problems of re-usability,
>> >> >> substitution, and dependency resolution -- all of which were problems
>> >> >> in ZF1.
>> >> >>
>> >> >> > Should I really inject e.g. my global config into all classes?
>> >> >>
>> >> >> No. Extract the configuration you need inside a factory, and use that
>> >> >> to create an instance. Your objects should only get exactly what they
>> >> >> need, no more, no less.
>> >> >>
>> >> >> > And if you
>> >> >> > don’t want to have plain entity models but business models with
>> some
>> >> >> > process logic in them, you cannot abstain from the service manager
>> >> >> either.
>> >> >>
>> >> >> There are ways to do this, too - service layers can compose
>> >> >> context-specific service managers, or be injected with the
>> >> >> services/configuration that the domain layer may need in order to
>> >> >> operate. You can then use this information when constructing domain
>> >> >> object instances, to create prototypes of domain objects, or to seed
>> >> >> factories. There are many options here.
>> >> >>
>> >> >>
>> >> >> --
>> >> >> Matthew Weier O'Phinney
>> >> >> Project Lead            | [hidden email]
>> >> >> Zend Framework          | http://framework.zend.com/
>> >> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>> >> >>
>> >>
>> >>
>> >>
>> >> --
>> >> Matthew Weier O'Phinney
>> >> Project Lead            | [hidden email]
>> >> Zend Framework          | http://framework.zend.com/
>> >> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>> >>
>> >> --
>> >> List: [hidden email]
>> >> Info: http://framework.zend.com/archives
>> >> Unsubscribe: [hidden email]
>> >>
>> >>
>> >>
>>
>>
>>
>> --
>> Matthew Weier O'Phinney
>> Project Lead            | [hidden email]
>> Zend Framework          | http://framework.zend.com/
>> PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
>>



--
Matthew Weier O'Phinney
Project Lead            | [hidden email]
Zend Framework          | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc

--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

basz
On 5 mrt. 2013, at 15:29, Matthew Weier O'Phinney <[hidden email]> wrote:

> The logic in canCreateServiceWithName() should similarly be
> very lightweight.

interesting read.

Do you see any problems with the following...

This way you would be able to just slap on an interface on any class and wouldn't have to maintain the AbstractInterface::$serviceClasses array

public function canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
       {
           return in_array("aninterface", class_implements($name))
       }



--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

weierophinney
Administrator
On Mon, Mar 11, 2013 at 4:30 PM, Bas Kamer <[hidden email]> wrote:

> On 5 mrt. 2013, at 15:29, Matthew Weier O'Phinney <[hidden email]> wrote:
>
>> The logic in canCreateServiceWithName() should similarly be
>> very lightweight.
>
> interesting read.
>
> Do you see any problems with the following...
>
> This way you would be able to just slap on an interface on any class and wouldn't have to maintain the AbstractInterface::$serviceClasses array
>
> public function canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
>        {
>            return in_array("aninterface", class_implements($name))
>        }

That makes perfect sense as far as I'm concerned; I showed using an
array property just to demonstrate, but the logic can be done any way
you want. For things like adapters, where polymorphism is less likely
and the creation of the adapter will likely be the same across
different implementations, this approach would be quite simple and
effective.



--
Matthew Weier O'Phinney
Project Lead            | [hidden email]
Zend Framework          | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc

--
List: [hidden email]
Info: http://framework.zend.com/archives
Unsubscribe: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Fundamental question: Why isn’t the service manager super global?

richard
In reply to this post by weierophinney
I use an approach that utilizes both methods.

I use a Instance::setOptions method to inject configuration and dependencies into an object.

If a class implements OptionsAwareInterface, an Options Manager is used in the Options Aware Inititalizer.

This Options Manager looks in configuration for an Options Class defined for the FQCN or Service Manager Alias of the instance being initialized.

An Options Object is created and populated with data matching the Options Objects configuration key definition.

The Options Manager calls OptionsClass::createOptions with the Service Manager as an argument.

The Options Object can then use the Service Manager to set dependencies as properties of the Options Object.

If the Options Object has child Options Classes, those are created and the process repeated recursively.

The Options Manager calls Instance::setOptions which then sets the Instance Object up as desired.

The benefits of this approach IMHO are vastly cleaner Configuration files, and potentially well documented Options Classes with several levels of error handling available. The Options Class is in essence a type of Factory invoked via an Initializer.