0

Edit 1: it seems like I didn't explain myself very well. Class Foo is not an entity. Just a general purpose model that I would like to have an access to the entity manager.

Edit 2: I don't think there is an answer to my question. Basically, I wanted a class that can have access to the EntityManager without this class being called by the service manager, simply due to the fact that it may be called by a class who is also not called by the service manager. In other words, I was trying to achieve what Zend_Registry used to achieve in ZF1. I'll have to find another way of doing what I am trying to do.

I am trying to access Doctrine's entity manager in a model, in a similar way as it done in a controller:

$this->getServiceLocator()->get('Doctrine\ORM\EntityManager');

The ZF2 manual (http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.quick-start.html) says:

By default, the Zend Framework MVC registers an initializer that will inject the ServiceManager instance, which is an implementation of Zend\ServiceManager\ServiceLocatorInterface, into any class implementing Zend\ServiceManager\ServiceLocatorAwareInterface.

So I created a the following class:

<?php
namespace MyModule\Model;

use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class Foo implements ServiceLocatorAwareInterface
{
    protected $services;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
         $this->services = $serviceLocator;
    }

    public function getServiceLocator()
    {
         return $this->services;
    }

    public function test()
    {
        $em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
    }
}   

Then, from another class I call this class as such:

$foo = new \MyModule\Model\Foo();
$foo->test()

which throws the following error:

PHP Fatal error: Call to a member function get() on a non-object

So, I guess I am missing something somewhere, but what? Where? How? Perhaps there is an easier to access the entity manager?

Thanks!

user1510297
  • 51
  • 1
  • 9
  • You should not use the entity manager inside of an doctrine entity. Doctrine 2 entities are just data storages for actions on the tables you may want to look at custom repository classes which have access to the entity manager. If you provide an example of what you want to do with the entity manager inside of the entity I may be able to help you find the proper way to do this. As of your question it should only be inistialized if you use the service locator/DI to get your entity. – Hikaru-Shindo Jan 23 '13 at 09:36
  • 1
    I recommend you not to retrieve the EM manually in any class as this will give you a time creating unit tests. Also, if you change the service alias (doctrine.entitymanager.orm_default) in future, you'll need to refactor everything. Btw, you're creating the instance using the `new` keyword. ZF2 can only inject dependencies if you use the ServiceManager and/or Di. – Daniel M Jan 23 '13 at 09:37
  • Ah yes there were unit tests too. But to have the entity manager inside of the entity is even worst of design Daniel M. It's discouraged by the doctrine team since it's much against doctrine's design pattern. The entity is just a data storage - not more, not less. The first answer on this question: http://stackoverflow.com/questions/4108291/using-entitymanager-inside-doctrine-2-0-entities describes the problem really good I think. – Hikaru-Shindo Jan 23 '13 at 09:50
  • Who said anything about entity manager in entities? The question, if it wasn't clear, was about accessing the entity manager from a general purpose class. – user1510297 Jan 23 '13 at 11:59
  • Daniel M: thanks. Your comment about not using the new keyword at least give me a direction to investigate further. – user1510297 Jan 23 '13 at 12:08

2 Answers2

1

From your question, I see that you have mainly two misunderstandings, one about your design strategy (injecting an EntityManager on your model) and one about how things work with the service manager (ServiceLocatorAwareInterface). In my answer I'll try to focus on the second one.

Initializers are php closures that are called over each instance accessed from the Service Manager before this one returns it to you.

Here is an example of an Initializer :

// Line 146 - 150 of Zend\Mvc\Service\ServiceManagerConfig class + comments

$serviceManager->addInitializer(function ($instance) use ($serviceManager) {
        if ($instance instanceof ServiceManagerAwareInterface) {
            $instance->setServiceManager($serviceManager);
        }
    });

As you can see each time Service Manager is asked to return an instance/object that implements the ServiceManagerAwareInterface interface, it will setup/inject the Service Manager instance to it.

By the way in your previous code you omitted to implement correctly the interface as you didn't define the setServiceManager method. However, this is not your only problem. First, if you want the Service Manager to inject itself in your Model, you need to call/construct your model instance from it (during this process it will call the initializers) through a factory for example if your class has complex dependencies.

[EDIT]

Example:

In your MyModule

namespace MyModule;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use MyModule\Model\Foo;

class Module implements ServiceProviderInterface{

//Previous code

public function getServiceConfig()
{
    return array(
        'instances' => array(
            'myModelClass'        => new Foo(),
            ),
       );

}

Now, when you need a Foo instance you should call the Service Manager:

$serviceManager->get('myModelClass');

Don't forget defining setServiceManager method, otherwise your'e not correctly implementing the ServiceManagerAwareInterface!

yechabbi
  • 1,881
  • 17
  • 14
  • Thanks, but I'm sorry, your answer is all over the place: 1. I know I am missing something, but if I knew what, I would not need to ask. 2. Why would the entity manager know of my models, and even if it did, how does it help me? I'm **not** talking about entity classes here, but about models that reside under \module\MyModule\src\MyModule\Model. – user1510297 Jan 23 '13 at 12:09
  • Sorry your question was not clear enough about the usage of your "Model" classes. However your understanding of implementing the `ServiceLocatorAwareInterface` was indeed wrong. I'll edit my answer for illustrating how you can achieve your need – yechabbi Jan 23 '13 at 12:16
  • Thanks, I really appreciate your effort. I think this had made one thing clear to me. In a sense, I thought that service manager was what Zend_Registry used to be in ZF1, where you can set something and then access it from wherever you need it. What I realised is that I was wrong. I guess I will have to be a little more creative. – user1510297 Jan 23 '13 at 12:45
  • Well, thing are quite different with ZF2. However, in some sense the service manager can be actually used to "put something" and access it from every where. It doesn't have 'static' methods for accessing service and is not a Singleton as the Zend_Registry used to be. Declaring services to Service Manager is generally done through your Module class by implementing the ServiceProviderInterface interface. You have actually other options like declaring a factory or an invokable class. – yechabbi Jan 23 '13 at 12:54
1

I think, the only thing you’re missing, is to add your model class to the list of invokables and retreive it through the service manager.

So basically add this to your module.conf.php:

return array(
    'service_manager' => array(
        'invokables' => array(
            'MyModule\Model\Foo' => 'MyModule\Model\Foo',
        ),
    ),
);

And instantiate your model object like this (if in a controller):

$foo = $this->getServiceLocator()->get('MyModule\Model\Foo');
$foo->test();
Rob
  • 1,158
  • 1
  • 12
  • 22
  • Thanks for your answer. This is what I ended up doing. Originally I wanted to have a class that I can call from the Doctrine repositories, and that class would have had to have access to the entity manager, but since the repositories themselves are not instantiated by the service locator, there was no way to do that. I ended up creating an abstract class that extends the repository class which allows me to do that (but I can't use the repositories in this case). – user1510297 Feb 19 '13 at 13:03