The zend file application.config.php offers some way to cache the config, which I find very nice for a production system:
return array(
'modules' => array(
'Application',
),
'module_listener_options' => array(
'module_paths' => array(
'./module',
'./vendor'
),
'config_glob_paths' => array('config/autoload/{,*.}{global,local}.php'),
'config_cache_enabled' => true,
'config_cache_key' => md5('config'),
'module_map_cache_enabled' => true,
'module_map_cache_key' => md5('module_map'),
'cache_dir' => './data/cache',
),
);
However, activating that leads immediately to errors like
Fatal error: Call to undefined method Closure::__set_state()
This has to do with factories written as closures, like these:
'service_manager' => array(
'factories' => array(
'auth.service' => function($sm) {
/* hic sunt ponies */
},
),
),
Unfortunately, the issues only tell me why this error happens, but not how to resolve it.
How can I rework this and similar factories so the cache will work with them?
Rework your factory closures to factory classes.
Config
'service_manager' => array(
'factories' => array(
'auth.service' => \Fully\Qualified\NS\AuthFactory::class,
),
),
Factory
namespace Fully\Qualified\NS;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class AuthFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
// create your object and set dependencies
return $object
}
}
Besides this approach making caching possible, another advantage is that PHP will parse your config faster since it doesn't have to create a Closure class on each request for each anonymous function.
Related
ZF2 documentation says following on defult services documentation;
InputFilterManager, mapping to Zend\Mvc\Service\InputFilterManagerFactory. This creates and returns
an instance of Zend\InputFilter\InputFilterPluginManager, which can be
used to manage and persist input filter instances.
I have a custom zf2 inputfilter class and i'm adding filters and validators inside init() method like following;
namespace Application\Filter;
use Zend\InputFilter\InputFilter;
class GlassFilter extends InputFilter
{
public function init()
{
$this->add(array(
'name' => 'glassname',
'required' => true,
'filters' => array(
array('name' => 'StringToUpper'),
),
'validators' => array(
array( 'name' => 'StringLength', 'options' => array('min' => 3),
),
));
}
Also i added following key to my module.config.php
'filters' => array(
'invokables' => array(
'glassfilter' => '\Application\Filter\GlassFilter',
),
),
My question is, how can i construct my GlassFilter using InputFilterManager? Is this a correct approach? I found this thread but i want to understand relation between custom InputFilters and InputFilterManager.
Ok, after spending 3 bloody hours (thanks to incredible(!) documentation) I figured it out. I'm writing my solution as an answer, hopefully it will help others who want to write their custom inputfilters.
You should register your custom inputfilter in module.config.php by input_filters top key, not filter, filters, filter_manger, filtermanager etc..
Extend default Zend\InputFilter\InputFilter when writing your own GlassFilter.
Write your filters inside the init() method of GlassFilter, not in the __constructor(). It will be called automatically after construction.
Then get it anywhere via inputfiltermanager, not servicemanager directly.
Config example:
'input_filters' => array(
'invokables' => array(
'glassfilter' => '\Application\Filter\GlassFilter',
),
),
Usage example:
$glassfilter = $serviceLocator->get('InputFilterManager')->get('glassfilter');
Hi I am trying to write a user registration form using ZfcUser module for Zend Framwork 2 and would like some advice on best practices when adding more user fields.
So far I have created my own module called "WbxUser" and as outlined in the modules wiki pages I have added a custom field called "userlastname" to ZfcUser's registration form by using the Event Manager in my modules bootstrap function like so.
Code:
//WbxUser.Module.php
namespace WbxUser;
use Zend\Mvc\MvcEvent;
class Module {
public function onBootstrap(MvcEvent $e){
$events = $e->getApplication()->getEventManager()->getSharedManager();
$events->attach('ZfcUser\Form\Register','init', function($e) {
$form = $e->getTarget();
$form->add(array(
'name' => 'userlastname',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Last Name',
),
));
// Do what you please with the form instance ($form)
});
$events->attach('ZfcUser\Form\RegisterFilter','init', function($e) {
$filter = $e->getTarget();
$filter->add(array(
'name' => 'userlastname',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'min' => 3,
'max' => 255,
),
),
),
));
});
}
public function getConfig(){
return array();
}
public function getAutoloaderConfig(){
return array();
}
}
But after this I have got a bit lost on where/how to write the code to save the extra data that my new fields are gathering.
Is this the event where I can fire off save routines for the additional fields https://github.com/ZF-Commons/ZfcUser/wiki/How-to-perform-a-custom-action-when-a-new-user-account-is-created
Should I be writing my own WbxUser model that extends ZfcUser\Entity\User.php to add my new fields
I am a bit of a ZF and MVC noob so would be very great-full for a nudge in the write direction.
this one seems to be a bit more intuitive
http://juriansluiman.nl/en/article/117/use-3rd-party-modules-in-zend-framework-2
You can override the module configurations. The most elegant way is to use the zfcuser.global.php.dist in the autoload folder. Copy this file to your appliction autoload folder and change the filename to zfcuser.global.php. In here you're able to change whatever option you can find in here. This option can be used to override the zfcUser entity: 'user_entity_class' => 'ZfcUser\Entity\User'.
Then again, you may find yourself in a situation where you need to change things that cannot be changed in the configuration file. In this case you could create your own user module ( You may want to just clone the entire zfcuser module until you're sure about what files you want to replace.
After cloning the user module (and changed the namespaces accordingly) add your module to application.config.php. Make sure your module is loaded after zfcuser. Or alternatively you could remove zfcuser from the config file and put the following in module.php:
public function init($moduleManager)
{
$moduleManager->loadModule('ZfcUser');
}
Now you can override ZfcUser module configurations.
The following snippet could be used to override the template folder and UserController.
<?php
// Note most routes are managed in zfcUser config file.
// All settings here either overrides or extend that functionality.
return array(
'view_manager' => array(
'template_path_stack' => array(
// 'zfcuser' => __DIR__ . '/../view', Override template path
),
'template_map' => array(
// You may want to use templates from the original module without having to copy - paste all of them.
),
),
'controllers' => array(
'invokables' => array(
// 'zfcuser' => 'YourModule\Controller\IndexController', // Override ZfcUser controller.
),
),
'router' => array(
'routes' => array(
'zfcuser' => array(
'type' => 'Literal',
'priority' => 2500,
'options' => array(
'route' => '/user',
'defaults' => array(
// Use original ZfcUser controller.
// It may be a good idea to use original code where possible
'controller' => 'ZfcUser\Controller\UserController',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'authenticate' => array(
'type' => 'Literal',
'options' => array(
'route' => '/authenticate',
'defaults' => array(
// Invoke YourModule\Controller\IndexController
'controller' => 'zfcuser',
'action' => 'authenticate',
),
),
),
),
),
),
),
);
Also you can override ZfcUser services in module.php. ;-)
Altering a third party module don't really appeal to me because at times it unsettle a lot of functionality. I always try do something outside the module because if there is an update in the module i easily incorporate into my application without haven to rewrite anything.
To do something like you are trying to do I would create another table in my application and add a foreign key that extend the zfcuser table that way i can relate the information to each zf2user in my application.
It just a suggestion as i am also new in zf2.
As in title, I'm struggling to access DBAdapter inside Router. Implementing ServiceLocatorAwareInterface isn't much help (ZF2 does not inject anything). Declaring it as a service in module with custom factory is not an option either, as it extends Http/Parts router and requires configuration parameters passed depending on a route (I don't want to hard-code them)
What I've already tried:
module.config.php:
(...)
'router' => array(
'routes' => array(
'adm' => array(
'type' => 'Custom\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/admin[/:language[/:controller[/:action[/:params]]]]',
'constraints' => array(
'language' => '(pl|en)',
'controller' => "[a-zA-Z0-9_\-]*",
'action' => "[a-zA-Z0-9_\-]*",
'params' => "(.*)",
),
'defaults' => array( ... ),
),
'may_terminate' => true,
),
),
),
'service_manager' => array(
(...)
'invokables' => array(
'Custom\Mvc\Router\Http\Segment' => 'Custom\Mvc\Router\Http\Segment',
),
),
(...)
As of now, Custom\Mvc\Router\Http\Segment is just a copy of Zend\Mvc\Router\Http\Segment, with added interfaces ServiceLocatorAwareInterface, AdapterAwareInterface and respective methods in the similar fashion:
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
var_dump($serviceLocator);
exit();
}
It never enters the setServiceLocator method, only RouteInterface::factory(), which then calls constructor.
Setting up a factory didn't help either, again - the code is not executed. Same behavior after moving the 'invocables' or factory to application config.
Currently using Zend Framework 2 RC1
It would have been easier if you would have gisted us som code.. :)
My recommendation would either be to use a factory to instansiate your custom router or set it as an invokable class (Requires you to implement ServiceLocatorAwareInterface so you can set it up in the router)
I have earlier asked this question, and I got good answers there.
However, that was for beta4, and no longer works.
So where and how do I add my own view helpers to ZF2?
You should add them to your module.config.php under view_helpers like this:
'view_manager' => array(
'template_path_stack' => array(
'ModuleName' => __DIR__ . '/../view',
),
),
'view_helpers' => array(
'factories' => array(
'showmessages' => function($sm) {
$helper = new ModuleName\Helper\MessageShower();
// do stuff with $sm or the $helper
return $helper;
},
),
'invokables' => array(
'selectmenu' => 'ModuleName\Helper\SelectMenu',
'prettyurl' => 'ModuleName\Helper\PrettyUrl',
),
),
Here I show two ways of creating the helpers. If all they need to do is to be instantiated, just add their name (including namespace) as invokables. If you need to do stuff with them or the ServiceManager, create them through the factories keyword.
The beta5 had a BC regarding the servicemanager. This applies for the view helper manager as well. Have a look here - there's even an example for view helpers a bit down too.
I'm creating abstract models for managing database entities - I already have EntityAbstract, EntitySetAbstract and a ManagerAbstract models. In my ManagerAbstract model I need a Zend/Db/Adapter instance in order to create a Zend\Db\TableGateway.
How could I pull the main instance of the adapter to my ManagerAbstract? In ZF1 I could have achieved this with Zend_Registry.
If this isn't the right way of doing things in ZF2, I would love to hear the correct way to this kind of things.
Thanks!
Use the Dependency Injection Container, Zend\Di. The ZfcUser project does this if you want to poke around in some working code.
Alternatively, the basic approach is something like this (code untested!):
Firstly: configure the DI to inject the database connection information:
config/autoload/local.config.php:
<?php
return array(
'di' => array(
'instance' => array(
'Zend\Db\Adapter\Adapter' => array(
'parameters' => array(
'driver' => 'Zend\Db\Adapter\Driver\Pdo\Pdo',
),
),
'Zend\Db\Adapter\Driver\Pdo\Pdo' => array(
'parameters' => array(
'connection' => 'Zend\Db\Adapter\Driver\Pdo\Connection',
),
),
'Zend\Db\Adapter\Driver\Pdo\Connection' => array(
'parameters' => array(
'connectionInfo' => array(
'dsn' => "mysql:dbname=mydatabasename;host=localhost",
'username' => 'myusername',
'password' => 'mypassword',
'driver_options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''),
),
),
),
),
),
);
Secondly, within your module's module.config.php file, inject the adapter into the mapper:
module/My/config/module.config.php:
<?php
return array(
'di' => array(
// some config info...
'My\Model\ManagerAbstract' => array(
'parameters' => array(
'adapter' => 'Zend\Db\Adapter\Adapter',
),
),
// more config info...
)
);
Finally, ensure that your ManagerAbstract class can receive the injection:
module/My/src/My/Model/ManagerAbstract.php:
<?php
namespace My\Model;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
abstract class ManagerAbstract implements AdapterAwareInterface
{
/**
* #var Zend\Db\Adapter\Adapter
*/
protected $adapter;
// some code
public function setDbAdapter(Adapter $adapter)
{
$this->adapter = $adapter;
}
// some more code
}
Note that to use any sub-class, you need to retrieve it via the DIC or inject the mapper into the service and then inject the service into the controller (or other service) where you want to use it.