2

EDIT: a few weeks after I posted this question Evan Coury wrote an excellent blog post on the topic of the ZF2 ServiceManager, which is where I found the best answers to my questions: http://blog.evan.pro/introduction-to-the-zend-framework-2-servicemanager

--

I'm working on a project using ZendFramework 2.0.0beta4 and am having trouble using the Zend\ServiceManager to handle dependencies. Here is the current ZF2 ServiceManager documentation

It lists 6 sub-keys to use when registering classes with the ServiceManager for use in our modules: abstract_factories, aliases, factories, invokables, services, and shared. If I just want to register a model class which I'm going to use in my controller to pull data from a database, which one is best? I'm specifically trying to adapt an example from the ZF2 Skeleton Application shown below to my own application (DashboardTable is a model), and this example uses the factories way.

public function getServiceConfiguration()
{
    return array(
        'factories' => array(
            'album-table' => function($sm) {
                $dbAdapter = $sm->get('db-adapter');
                $table = new DashboardTable($dbAdapter);
                return $table;
            },
            'test-model' => Dashboard\Model\TestModel(),
        ),
    );
}

However, I don't know how 'db-adapter' is getting into the ServiceManager ($sm) in my separate working example from the SkeletonApplication - it has to do with an entry in the autoloaded global.php config file which has a 'db' entry containing the DB info. Because I don't know exactly how that's getting from the config file to ServiceManager, I created the simple entry below that to reduce the problem to its base components - "test-model". When I comment out the 'dashboard-table' entry and call a function from TestModel in my controller which simply outputs some text. Below is the ServiceManager config from my Module.php

<?php

namespace Dashboard\Model;

class TestModel {

public function testMethod()
{
    $testResult = "Hello";
    return $testResult;
}

}

Which is then passed from my controller to the view:

<?php

namespace Dashboard\Controller;

use Zend\Mvc\Controller\ActionController;
use Zend\View\Model\ViewModel;
use Dashboard\Model\AlbumTable;
use Dashboard\Model\TestModel;
use Dashboard\Model\Dashboard;

class DashboardController extends ActionController
{
    public function indexAction()
    {   
        return new ViewModel(array(
            'users' => $this->getTestModel()->testMethod(),
        ));
    }

    public function getAlbumTable()
    {
        if (!$this->albumTable) {
            $sm = $this->getServiceLocator();
            $this->albumTable = $sm->get('album-table');
        }
        return $this->albumTable;
    }

    public function getTestModel()
    {
        if (!$this->testModel) {
            $sm = $this->getServiceLocator();
            $this->testModel = $sm->get('test-model');
        }
        return $this->testModel;
    }

}

This code gives me a completely blank page, no errors. When I comment out the ServiceManager config from Module.php and just render a new ViewModel without any passing any arguments in my DashboardController.php file the page renders normally - loading layout.phtml and index.phtml.

I believe I'm misunderstanding a fundamental piece of how to use the ServiceManager or possible ZF2 in general, and will greatly appreciate any insight anybody can give. This is also my first question on StackOverflow so I welcome any advice on formatting my question. Thanks.

Stoyan Dimov
  • 5,250
  • 2
  • 28
  • 44
Alex Ross
  • 3,729
  • 3
  • 26
  • 26

3 Answers3

3

There are two good options to get factories from service managers. One is the creation of factory classes, which happens most time in the Zend Framework code itself. The second one is using closures, as you are doing.

Make sure you do not type things like:

'test-model' => Dashboard\Model\TestModel(),

But a real closure like your first one is a good example. Secondly, the Service Manager always gives an exception when you try to get a service which fails to instantiate. Note this exception does not include the message why: the class might not be found or an exception is thrown during instantiation (for example because the service manager cannot instantiate a dependency of the service you are trying to get).

A last remark is you do not need to import FQCN (fully qualified class names) with use statements at the location you are trying to get. But you need to import the FQCNs when you are trying to instantiate.

So this works:

<?php

class MyClass
{
  protected $sm;

  public function setServiceManager($sm)
  {
    $this->sm = $sm;
  }

  public function doSomething()
  {
    $this->sm->get('some-special-key');
  }
}

And this too:

<?php

use Foo\Bar\Baz;

$serviceConfig = array(
  'factories' => array(
    'some-special-key' => function($sm) {
      return new Baz;
    }
  ),
);

But this not (if you try to get a Foo\Bar\Baz):

<?php

$serviceConfig = array(
  'factories' => array(
    'some-special-key' => function($sm) {
      return new Baz;
    }
  ),
);

You might want to checkout my SlmCmfKernel repository. In my Module.php I include a service configuration file, which is put in a separate location. In another part of the code I get a service from the manager.

Jurian Sluiman
  • 13,498
  • 3
  • 67
  • 99
  • This was a great response, thank you. Every remark helped clarify bits for me - particularly where I do and do not need import FQCNs and using full closures in the service manager. Also thank you for the link to your repo - my biggest challenge with ZF2 so far is the dearth of examples, so I'll frequently be using yours as a reference point :) – Alex Ross Jun 25 '12 at 22:50
2

Just to clarify:

public function getServiceConfiguration()
{
    return array(
        'factories' => array(
            'test-model' => function($sm){
                return new Model\TestModel;
            },
        ),
    );
}

Can also be written as an invokable:

public function getServiceConfiguration()
{
    return array(
        'invokables' => array(
            'test-model' => 'Model\TestModel',
        ),
    );
}

In that case, you might want to consider having it defined in a config file instead of Module.php, as you'd be able to take advantage of config caching since it's simply an array of scalars.

EvanDotPro
  • 1,141
  • 8
  • 5
1

I ended up finding the answer to my own question through more debugging (I previously hadn't had ini_set('display_errors', '1'); set - silly me).

The proper syntax to add a class to the ZF2 ServiceManager (within your Module.php) appears to be:

public function getServiceConfiguration()
{
    return array(
        'factories' => array(
            'album-table' => function($sm) {
                $dbAdapter = $sm->get('db-adapter');
                $table = new AlbumTable($dbAdapter);
                return $table;
            },
            'test-model' => function($sm){
                return new Model\TestModel;
            },
        ),
    );
}

And just for completeness, in order to call a method from the class you're including you can use this in your controller file (DashboardController.php in my case) as long as you're extending the ActionController class:

class DashboardController extends ActionController
{
    public function indexAction()
    {   
        return new ViewModel(array(
            'users' => $this->getTestModel()->testMethod(),
        ));
    }

    public function getTestModel()
    {
        if (!$this->testModel) {
            $sm = $this->getServiceLocator();
            $this->testModel = $sm->get('test-model');
        }
        return $this->testModel;
    }

}

Where testMethod() is a method from within the TestModel class. Some notes from this for anybody new to Zend or namespaces - one of my issues was that I was using the full class name reference (Dashboard\Model\TestModel) when I had set the namespace at the top of the file to Dashboard, so the first Dashboard was unnecessary and caused PHP to look for Dashboard\Dashboard\Model\TestModel. Also, as of this writing sample ZF2 module are scarce - I recommend looking to samples like ZfcUser by EvanDotPro for examples on this type of thing.

My original confusion about the various sub-keys for adding classes to the ServiceManager still lingers though, so if you have any insight as to that I will continue to monitor this question and will mark your answer as "the" answer should you solve that bit, thank you :).

Alex Ross
  • 3,729
  • 3
  • 26
  • 26