I am listening to this talk here : https://www.youtube.com/watch?v=Vp7y65rnN98
At the 44:00 Module.php::getConfig() vs Module.php::getControllerConfig()
looking at an example module like ZFCampuses zf-apigility-admin module ...
I see BOTH config/module.config.php and Module.php::getControllerConfig() defining Controller Factories.
zf-apigility/config/module.config.php
'controllers' => array(
'aliases' => array(
'ZF\Apigility\Admin\Controller\HttpBasicAuthentication' => 'ZF\Apigility\Admin\Controller\Authentication',
'ZF\Apigility\Admin\Controller\HttpDigestAuthentication' => 'ZF\Apigility\Admin\Controller\Authentication',
'ZF\Apigility\Admin\Controller\OAuth2Authentication' => 'ZF\Apigility\Admin\Controller\Authentication',
),
'invokables' => array(
'ZF\Apigility\Admin\Controller\App' => 'ZF\Apigility\Admin\Controller\AppController',
'ZF\Apigility\Admin\Controller\CacheEnabled' => 'ZF\Apigility\Admin\Controller\CacheEnabledController',
'ZF\Apigility\Admin\Controller\FsPermissions' => 'ZF\Apigility\Admin\Controller\FsPermissionsController',
),
'factories' => array(
'ZF\Apigility\Admin\Controller\Documentation' => 'ZF\Apigility\Admin\Controller\DocumentationControllerFactory',
'ZF\Apigility\Admin\Controller\Filters' => 'ZF\Apigility\Admin\Controller\FiltersControllerFactory',
'ZF\Apigility\Admin\Controller\Hydrators' => 'ZF\Apigility\Admin\Controller\HydratorsControllerFactory',
'ZF\Apigility\Admin\Controller\Validators' => 'ZF\Apigility\Admin\Controller\ValidatorsControllerFactory',
'ZF\Apigility\Admin\Controller\InputFilter' => 'ZF\Apigility\Admin\Controller\InputFilterControllerFactory',
),
),
zf-apigility/Module.php
public function getControllerConfig()
{
return array('factories' => array(
'ZF\Apigility\Admin\Controller\Authentication' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\AuthenticationModel');
return new Controller\AuthenticationController($model);
},
'ZF\Apigility\Admin\Controller\Authorization' => function ($controllers) {
$services = $controllers->getServiceLocator();
$factory = $services->get('ZF\Apigility\Admin\Model\AuthorizationModelFactory');
return new Controller\AuthorizationController($factory);
},
'ZF\Apigility\Admin\Controller\Config' => function ($controllers) {
$services = $controllers->getServiceLocator();
return new Controller\ConfigController($services->get('ZF\Configuration\ConfigResource'));
},
'ZF\Apigility\Admin\Controller\ModuleConfig' => function ($controllers) {
$services = $controllers->getServiceLocator();
return new Controller\ModuleConfigController($services->get('ZF\Configuration\ConfigResourceFactory'));
},
'ZF\Apigility\Admin\Controller\ModuleCreation' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\ModuleModel');
return new Controller\ModuleCreationController($model);
},
'ZF\Apigility\Admin\Controller\Source' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\ModuleModel');
return new Controller\SourceController($model);
},
'ZF\Apigility\Admin\Controller\Versioning' => function ($controllers) {
$services = $controllers->getServiceLocator();
$factory = $services->get('ZF\Apigility\Admin\Model\VersioningModelFactory');
return new Controller\VersioningController($factory);
},
));
}
My question is, why is this doubling up occurring here? What am I missing about the usage of Module.php::getControllerConfig() vs merely listing the Factories in Module.config.php ?
Edit : I think I found the answer to my question here https://stackoverflow.com/a/22374072/389976
You'll see that all the factories defined in Module.php use closures (AKA anonymous functions). The convention is for all config to go into module.config.php except for the closures.
ZF has built-in functionality (disabled by default) to cache the merged config files, but it can't do that if the config file contains closures, as these cannot be serialized, and thus are not cacheable. By keeping them separate you can enable config caching, which improves performance.
Edit: Perhaps we're talking at crossed purposes a little here. My answer above hopefully explains why the closures from Module.php shouldn't be moved to the module config file. You could of course create factory classes for each of the closures defined, and then list those in the module config. Some would consider this best practice (personally I rarely do this). At this point it's a trade off between ease of development and performance. It's much more convenient to create a four line closure than create a whole new class.
Related
My question was asked before. I also would like to access to my global configs (config/{,*.}{global,local}.php) located in my personal libraries (in the vendor directory). The closest answer that I think I found is here. I created function in my class
public function getServiceConfig()
{
return array(
'factories' => array(
'funcservice' => function(\Zend\ServiceManager\ServiceLocatorInterface $sm) {
$config = $sm->get('config');
}
)
);
}
And it works however I can't figure out how to get anything from the result.
$config = $this->getServiceConfig();
print_r($config);
gives me
Array
(
[factories] => Array
(
[funcservice] => Closure Object
(
[this] => Tools\Model\StandartFuncs Object
(
[eventIdentifier:protected] => Zend\Mvc\Controller\AbstractActionController
[plugins:protected] =>
[request:protected] =>
[response:protected] =>
[event:protected] =>
[events:protected] =>
[serviceLocator:protected] =>
)
[parameter] => Array
(
[$sm] => <required>
)
)
)
)
and from $config = $this->getServiceConfig()->get('locales'); I get
Fatal error: Call to a member function get() on a non-object
Let's assume you have a locales config file locales.local.php:
<?php
return array(
'hostname' => 'http://apachehost'
);
These global and local config files should be in the config/autoload folder.
Folder structure:
- root
- config
- autoload
- locales.global.php
- locales.local.php
- application.config.php
Then you load them using the following line in your application.config.php. Details on this advanced configuration you can read here in the ZF2 documentation
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{{,*.}global,{,*.}local}.php',
),
)
Now you can access your config from your ServiceManager instance like this:
$config = $serviceManager->get('Config');
This $config variable is an array. So you cannot access anything with getters. You are supposed to use array notation:
$locales = $config['locales'];
If your really want to use getters then you have to make your configuration to an object. You can do this using the Zend\Config\Config class like this:
$config = new \Zend\Config\Config($config, false);
Now you can access like you wrote in your question:
$config->get('locales');
Update
If you want to load auto config files from a vendor module it is common practice to copy those *.local.php and/or *.global.php files that come with the module to the autoload folder and edit the copied files according to your needs.
I don't think you've quite understood the solution you're trying to implement. The factory you're adding to the service config needs to return an instance of your library class. The reason you're putting it in a factory is so that you can inject the config array into it. So your code should look more like this:
public function getServiceConfig()
{
return array(
'factories' => array(
'funcservice' => function(\Zend\ServiceManager\ServiceLocatorInterface $sm) {
$config = $sm->get('config');
return new \SomeLibrary($config);
}
)
);
}
(where SomeLibrary is the name of your library class in /vendor).
You will then need to use the service manager to instantiate your library class whenever you need to access it, e.g. in a controller:
public function someAction()
{
$yourLibrary = $this->getServiceLocator()->get('funcservice');
}
This will create an instance of your library class, passing the config array as the constructor to the first parameter. You should never need to call getServiceConfig() yourself, and this wouldn't achieve anything.
I am using Doctrine 2 in my Zend Framework 2 Project. I have now created a Form and create one of my Dropdowns with Values from the Database. My Problem now is that I want to change which values are used and not the one which I get back from my repository. Okay, here some Code for a better understanding:
$this->add(
array(
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'county',
'options' => array(
'object_manager' => $this->getObjectManager(),
'label' => 'County',
'target_class' => 'Advert\Entity\Geolocation',
'property' => 'county',
'is_method' => true,
'empty_option' => '--- select county ---',
'value_options'=> function($targetEntity) {
$values = array($targetEntity->getCounty() => $targetEntity->getCounty());
return $values;
},
'find_method' => array(
'name' => 'getCounties',
),
),
'allow_empty' => true,
'required' => false,
'attributes' => array(
'id' => 'county',
'multiple' => false,
)
)
);
I want to set the value for my Select to be the County Name and not the ID. I thought that I would need the 'value_options' which needs an array. I tried it like above, but get the
Error Message: Argument 1 passed to Zend\Form\Element\Select::setValueOptions() must be of the type array, object given
Is this possible at all?
I was going to suggest modifying your code, although after checking the ObjectSelect code i'm surprised that (as far as I can tell) this isn't actually possible without extending the class. This is because the value is always generated from the id.
I create all form elements using factories (without the ObjectSelect), especially complex ones that require varied lists.
Alternative solution
First create a new method in the Repository that returns the correct array. This will allow you to reuse that same method should you need it anywhere else (not just for forms!).
class FooRepository extends Repository
{
public function getCounties()
{
// normal method unchanged, returns a collection
// of counties
}
public function getCountiesAsArrayKeyedByCountyName()
{
$counties = array();
foreach($this->getCounties() as $county) {
$counties[$county->getName()] = $county->getName();
}
return $counties;
}
}
Next create a custom select factory that will set the value options for you.
namespace MyModule\Form\Element;
use Zend\Form\Element\Select;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CountiesByNameSelectFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $formElementManager)
{
$element = new Select;
$element->setValueOptions($this->loadValueOptions($formElementManager));
// set other select options etc
$element->setName('foo')
->setOptions(array('foo' => 'bar'));
return $element;
}
protected function loadValueOptions(ServiceLocatorInterface $formElementManager)
{
$serviceManager = $formElementManager->getServiceLocator();
$repository = $serviceManager->get('DoctrineObjectManager')->getRepository('Foo/Entity/Bar');
return $repository->getCountiesAsArrayKeyedByCountyName();
}
}
Register the new element with the service manager by adding a new entry in Module.php or module.config.php.
// Module.php
public function getFormElementConfig()
{
return array(
'factories' => array(
'MyModule\Form\Element\CountiesByNameSelect'
=> 'MyModule\Form\Element\CountiesByNameSelectFactory',
),
);
}
Lastly change the form and remove your current select element and add the new one (use the name that you registered with the service manager as the type key)
$this->add(array(
'name' => 'counties',
'type' => 'MyModule\Form\Element\CountiesByNameSelect',
));
It might seem like a lot more code (because it is) however you will benefit from it being a much clearer separation of concerns and you can now reuse the element on multiple forms and only need to configure it in one place.
In my Zend\Form\Fieldset AddressFieldset it needs a Zend\Db\TableGateway\AbstractTableGateway BundeslandTable for a \Zend\Form\Element\Select().
So i implement \Zend\ServiceManager\ServiceManagerAwareInterface in this AddressFieldset and use the init() instead __construct().
And in module.config.php (not only in 'form_elements' tested, also in 'service_manager')
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($sm) {
$addressFieldset = new MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($sm);
return $addressFieldset;
}
),
),
In a \Zend\Form\Form's init():
$this->add(array(
'type' => 'MyFormway\Form\Fieldset\Address',
'name' => 'address',
));
this throws an error:
Zend\Form\FormElementManager::get was unable to fetch or create an instance for MyFormway\Form\Fieldset\Address
Why is zend unable to fetch an instance of this Fieldset?
edit-----------------------
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($formElementManager) {
die('inna form_elements config');
$addressFieldset = new \MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($formElementManager->getServiceLocator());
return $addressFieldset;
}
),
),
Because i have the Zend\Form\FormElementManager i fetch the ServiceLocator ...perhaps dont needed, because all XxxManager extends the Zend\ServiceManager\AbstractPluginManager and this extends ServiceManager.
In FormElementManager and also in AbstractPluginManager are no method getServiceManager().
But my problem: the die() is not called plus the error above. Is it a bug? ...i stand for a big wall :(
edit-----------------------
It works for a Form but not for a Fieldset!!!
Can you do a quick check if the \Invokable is called at all? Some professional die()-debugging will suffice.
Other than that a potential error source would be your injection of the ServiceManager. In the code you provide you're not actually injecting the ServiceLocator but rather the FormElementManager.
$addressFieldset->setServiceManager($sm->getServiceManager());
Doing it this way is considered Bad-Practice tho. You should only inject the stuff that you actually do need. Given you're injecting the whole manager i assume you're either working with Doctrine or you'll need access to some DB-Data. Do it like this:
'Foo' => function ($formElementManager) {
$sl = $formElementManager->getServiceManager();
$fs = new FooFieldset();
$fs->setDbDependency(
$sl->get('MyDbDependency')
);
return $fs;
}
Last little note: when you're adding a Fieldset, you don't need to add 'name' => 'foo' within the $this->add(), since the name of the fieldset will be defined via the Fieldset __construct('name').
A ZF2 application contains/nneds a lot of different config files: /config/application.config.php, /config/autoload/global.php, /config/autoload/local.php, /module/***/config/module.config.php.
Now I've written a module, that covers the caching functionality for the application, and need different values for the livetime of its items in my local/dev and the live environment. I also would like to be able to switch the cache type dependent on the environment.
Where should such stuff be sored? In /config/autoload/global.php and /config/autoload/local.php? If yes: should it first retrieved from these files in the Module class (e.g. in the onBootstrap() method) or used directly, where it's needed?
(It also would be great, if someone could show a primitive example for saving and getting such config data.)
The solution, I'm currently using is:
/config/autoload/global.php and/or /config/autoload/local.php
return array(
// DB credentials
'db' => array(
'username' => ...,
'password' => ...,
'dbname' => ...,
'host' => ...,
),
'cache_ttl' => 'global/local value for cache live time',
);
Cache Module class
class Module {
private $moduleConfig;
public function onBootstrap(MvcEvent $mvcEvent) {
$application = $mvcEvent->getParam('application');
$this->moduleConfig = $application->getConfig();
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
...
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $this->moduleConfig['cache_ttl'],
...
));
},
...
)
);
}
...
}
}
It works fine, but I'm pretty sure, that it's not the best practice / recomended way.
Your basic approach is the correct one.
For cache configuration stuff, keep your production values in the global file. That should live in your VCS. (EDIT: however, you should probably omit security sensitive configuration such as database passwords. Add that stuff to production via a local.php to keep it out of version control).
In your local environment, use the local file to override anything that needs to be overridden. IIRC the ZendSkeletonApplication has a .gitignore file that will ignore any local configs built in -- so your local configuration never makes it into git.
However, you don't need to mess around with loading the config on bootstrap like you are. You can just grab the config from the serviceManager inside your factory method:
public function getServiceConfig() {
try {
return array (
'factories' => array(
...
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
// you can just grab your config from the service-manager
'ttl' => $serviceManager->get('Config')['cache_ttl'],
...
));
},
...
)
);
}
...
}
Also - I wouldn't stick 'cache_ttl' as a top-level config key. Instead, try:
global.php
return array(
'cache' => array(
'ttl' => ...,
'servers' => ...,
...
)
);
That simplifies your factory to just something like:
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions( $serviceManager->get('cache') );
},
and you can override whatever you want in your local.php config. If all you want to do is change the ttl (leaving all the other global configs):
local.php
return array(
'cache' => array('ttl'=>...)
);
First ZF2 application, getting there, but I think still missing a think or two when it comes to dependency injection and the ServiceManager.
I have a particular problem at the moment with a new database gateway class I'm writing. I won't to inject a database adapter, so I've implemented AdapterAwareInterface. But the setDbAdapter method is never called in my class. I'm wondering if someone would be so kind as to look at my code and suggest what might be going wrong (or what I'm missing!).
So, here is the class in which I implement AdapterAwareInterface.
<?php
namespace Foo\Database;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
use Zend\Log\LoggerAwareInterface;
use Zend\Log\LoggerInterface;
class Gateway implements AdapterAwareInterface, LoggerAwareInterface
{
protected $logger = NULL;
protected $db = NULL;
public function setDbAdapter(Adapter $adapter)
{
$this->db = $adapter;
}
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
This is an extract from my module file showing how I configure my service manager:
public function getServiceConfig()
{
return array(
'factories' => array(
....
),
'invokables' => array(
'FooDatabaseGateway' => 'Foo\Database\Gateway',
),
'abstract_factories' => array(
'AbstractFeedParserFactory' => 'Bookmakers\Odds\Feeds\AbstractFeedParserFactory',
),
);
}
This is how I'm testing:
gateway = $this->getServiceLocator()->get('FooDatabaseGateway');
And this is part of my global config:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=kickoff_manager;host=localhost',
'username' => '****',
'password' => '****',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
Many thanks for any help you can provide.
:wq
OK a fresh pair of eyes on this problem this morning. I think this is the write answer.. At least that is to say its working for me. If anyone wants to suggest an entirely different of better approach, then please do so :-).
So the bit is was missing was to use an initializer in my service manager config to call the setDbAdapter function on any class instances that implement AdapterAwareInterface. So in the array I return from getServiceConfig in my Module.php file, I have added the following entry:
public function getServiceConfig() {
return array(
'initializers' => array(
'db' => function($service, $sm)
{
if ($service instanceof AdapterAwareInterface)
{
$service->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
}
}....
I think what I'm missing while learning ZF2 is that there are a lot of building blocks to work with, but you've got to put a lot of them together yourself.
Things are looking good and I'm enjoying the Framework, but there is a lot to learn, and I'm still not convinced by using Server Manager injection rather than good old constructor injection!
:wq