ZF2 – Service Manager – Using factories in a controller

One of my pain points, was trying to call a factory from the controller using the magic __get method, whilst using an alias.

I had setup a service factory, called “pcms” that I wanted to call via the controller:

return array(
    'service_manager' => array(
        'factories' => array(
            'pcms' => function() {
                return new PCMSService(
                    PCMS_SITE_ID,
                    array( ... )
                );
            }
        ),
    ),
);

in my controller, I’d planned on using:

$this->pcms->{functionnamehere}(...);

If I had used an alias in my config, It wouldn’t work:

return array(
    'service_manager' => array(
        'factories' => array(
            'PCMSService' => function() {
                return new PCMSService(
                    PCMS_SITE_ID,
                    array( ... )
                );
            }
        ),
        'aliases' => array(
            'pcms' => 'PCMSService',
        ),
    ),
);

Forego the alias, and simply name the factory whatever you want.

Advertisements

ZF2 – Database Changes from ZF1

ZF2 has some big game changers for database connectivity.  The Zend_Db_Table is no longer, and has since been replaced by the new TableGateway.  What I’m finding so far is that the default database adapter is no longer used, or I’m unsure how to initialize it, so that you must pass the database adapter into each table you use.  This can get crazy, but if you develop a wrapper for your database connections it isn’t so bad.

// somewhere in your db model, which must implement ZendServiceManagerServiceManagerAwareInterface 
$this->adapter = $this->getServiceManager()->get('db');
$table  = new TableGateway("my_db_table", $this->adapter);

The database setup is similar to the original application.ini setup in ZF1:

<?php
// database loading informaiton
return array(
    'service_manager' => array(
        'factories' => array(
            'ZendDbAdapterAdapter' => 'ZendDbAdapterAdapterServiceFactory',
        ),
        'aliases' => array(
            'db' => 'ZendDbAdapterAdapter',
        ),
    ),
    'db' => array(
        'driver' => 'pdo',
        'pdodriver' => 'mysql',
        'host' => 'localhost',
        'database' => 'db_name',
        'username' => 'db_user',
        'password' => 'db_pass',
        'driver_options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"),
    ),
);

Another fun thing that I found was actually easier than in ZF1 was to switch databases while keeping the same exact credentials:

$serviceManager = $this->getServiceManager();
$databaseAdapter = $serviceManager->get('db');

$dbParams = $databaseAdapter->getDriver()->getConnection()->getConnectionParameters();
$dbParams['database'] = $siteDetail['database_location'];

/**
 * Edited 10/15 - found a defect with trying to set the connection parameters, 
 * found that it worked 100% of the time when you simply returned a new instance
 * of the adapter.
 */
return new Adapter($dbParams);

 

ZF2 – Configuration Merging

One big change in ZF2 is the complete change for inheritance with configuration files; Meaning it’s no longer supported.  This can be a difficult change to make from ZF1 especially if you’ve built any or all configurations in a way that does this type of inheritance.

There are solutions to helping you obtain environmental configuration setups but that will only get you a global config, with a simple localized override.  While I don’t completely agree with non-inherited configurations, I can see the benefits.  Loading a configuration and merging it is expensive, but nothing that can’t be cached.

ZF2 didn’t completely remove this logic, they’ve hidden it, here’s a way to merge an 2 existing configurations together:

{
    "_base" : {
        "label" : "base label",
        "properties" : {
            "i" : {
            }
        }
    },
    "attribute" : {
        "_extends" : "_base",
        "label" : "attribute label override."
    }    
}

 

use ZendConfigFactory;
use ZendStdlibArrayUtils;

public function loadConfig($type)
{
    $loadedConfig = Factory::fromFile('config/objects.json');
    $config = isset($loadedConfig->{$type}) ? $loadedConfig->{$type} : null;
    if (isset($config['_extends'])) {
        $configBase = $loadedConfig[$config['_extends']];
        $config = ArrayUtils::merge($configBase, $config);
    }

    return $config;
}

Using the ZF1 JSON variable “_extends” in your config you can see that there’s a one to one load of the configuration file and it’s extension.  This doesn’t recursively call the function but it could easily be updated to do that.  Once the config is generated, it should be cached and returned.  This operation can get expensive and may not hold up well to load.

ZF2 – Using Cache

One does not simply cache in ZF2. The best way I’ve found is to create a factory in the Service manager to use cache.

<?php
return array(
    'modules' => array(
        'Application',
        'Rest'
    ),
    'module_listener_options' => array(
        'config_cache_enabled' => false,
        'cache_dir' => 'data/cache',
        'config_glob_paths'    => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
        'module_paths' => array(
            './module',
            './vendor',
        ),
    ),
    'service_manager' => array(
        'factories' => array(
            'ZendCacheStorageFactory' => function() {
                return ZendCacheStorageFactory::factory(
                    array(
                        'adapter' => array(
                            'name' => 'filesystem',
                            'options' => array(
                                'dirLevel' => 2,
                                'cacheDir' => 'data/cache',
                                'dirPermission' => 0755,
                                'filePermission' => 0666,
                                'namespaceSeparator' => '-db-'
                            ),
                        ),
                        'plugins' => array('serializer'),
                    )
                );
            }
        ),
        'aliases' => array(
            'cache' => 'ZendCacheStorageFactory',
        ),
    ),
);

Above is an example of my applicaiton.config.php file.  I’ve created a new StorageFactory object using the alias of “cache”.  This will allow me to create a service manager alias that I can then reference in my application.  The next step involves my controller and a class that is serviceManagerAware.

public function IndexAction()
{
        $this->assembler  = new Assembler($controller->getServiceLocator());
}

My Assembler class implements ServiceManagerAwareInterface and in the construct will simply set the service manager to the passed $sm parameter:

namespace ApplicationModelObj;

use ZendServiceManagerServiceManager;
use ZendServiceManagerServiceManagerAwareInterface;

class Assembler implements ServiceManagerAwareInterface
{
    /**
     * Creates the configuration for the service assembler
     * @param ZendServiceManagerServiceManager $request
     */
    public function __construct(ServiceManager $sm)
    {
        $this->setServiceManager($sm);
        $this->cache = $this->getServiceManager()->get('cache');
    }

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

    public function getServiceManager()
    {
        return $this->_sm;
    }
}

Using the cache is slightly different than ZF1.  You no longer can save tags with your cache.

# Simply return a key from the cache
$result = $this->cache->getItem($key);
if (!empty($result)) {
    return $result;
}

# Store a result into cache using the key, $saved will result in a boolean value.
$saved = $this->cache->addItem($key, $value);

# Tag the key when saving
$this->cache->setTags($key, array('tag1', 'tag2'));

 

ZF2 – Service Manager

The Service Manager for ZF2 is the new Zend_Registry from ZF1.  This class will allow you to create factories on the fly, but more importantly, will allow you to inject simple setups into your application.  Databases, and Cache are the two examples that I’ll use to show the examples of how easy it will be to access these objects from everywhere in your application.

<?php
return array(
    'modules' => array(
        'Application',
        'Rest'
    ),
    'module_listener_options' => array(
        'config_cache_enabled' => false,
        'cache_dir' => 'data/cache',
        'config_glob_paths'    => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
        'module_paths' => array(
            './module',
            './vendor',
        ),
    ),
    'service_manager' => array(
        'factories' => array(
            'ZendCacheStorageFactory' => function() {
                return ZendCacheStorageFactory::factory(
                    array(
                        'adapter' => array( 
                            'name' => 'filesystem', 
                            'options' => array( 
                                'dirLevel' => 2, 
                                'cacheDir' => 'data/cache',
                                'dirPermission' => 0755,
                                'filePermission' => 0666,
                                'namespaceSeparator' => '-db-'
                            ), 
                        ), 
                        'plugins' => array('serializer'), 
                    )
                );
            }
        ),
        'aliases' => array(
            'cache' => 'ZendCacheStorageFactory',
        ),
    ),  
);

Notice that there’s a Closure function for the factory.  If you’ve run into the error that the class you’re trying to create is not a valid ZendServiceManagerAbstractFactoryInterface, then you simply have to wrap the object you’re going to return in a Closure function to fix.