ZF2: How to get Zend\Db\Adapter inside custom router? - php

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)

Related

zf2 routing seems to ignore __NAMESPACE__

In Zend Framework 2, I tried using the following route:
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/:username[/:action]',
'defaults' => array(
'__NAMESPACE__' => 'Website\Controller',
'controller' => 'User',
'action' => 'index',
),
),
'may_terminate' => true,
),
However, when going to http://www.example.com/MyUsernameHere, I get a 404 not found error:
The requested controller could not be mapped to an existing controller class.
Controller:
User(resolves to invalid controller class or alias: User)
It's almost like the router completely ignores the 'Website\Controller' namespace and looks for User without the namespace in front it.
So, if I manually enter the namespace like so:
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/:username[/:action]',
'defaults' => array(
'controller' => 'Website\Controller\User',
'action' => 'index',
),
),
'may_terminate' => true,
),
then the page loads as expected.
What gives? Can the '__NAMESPACE__' parameter not be used for controllers? The ZF2 website clearly gives an example using '__NAMESPACE__', but I cannot get it work in practice. Is the example wrong and outdated?
For this to work as you expected you have to attach the ModuleRouteListener to the MVC event manager. You can do this in your module onBootstrap method:
public function onBootstrap(MvcEvent $event)
{
//...
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
//...
}
After you did that your code will work as expected.
They actually should have mentioned this in the page with the example that you referred to in your question. You can check for more details on the module route listener here in the Zend\Mvc documentation. They write there:
This listener determines if the module namespace should be prepended to the controller name. This is the case if the route match contains a parameter key matching the MODULE_NAMESPACE constant.

Zend Framework 2 - Cache-conform service factories

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.

How to use InputFilterManager to construct custom InputFilters in Zf2

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');

Zend2: how to add custom route type?

I have a class \Foo\BarRoute implementing the route interface (\Zend\Mvc\Router\RouteInterface).
How do I add \Foo\BarRoute as a bar route plugin and make it available in configuration (e.g. 'type' => 'bar')?
So far I got the following Module.php without any effect :(
public function onBootstrap(EventInterface $e)
{
$routePluginManager = $e->getRouter()->getRoutePluginManager();
$routePluginManager->setInvokableClass('bar', '\Foo\BarRoute');
}
Can this be done via the module configuration file only?
Thanks!
Why not set the FQCN of your custom route class in the module.config.php directly?
in case you just need to use it in your module config file.
e.g.
return array(
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Foo\BarRoute',
'options' => array(
'route' => '/',
'defaults' => array(),
),),
),),
...
);

Extending ZfcUser with Zend Framework 2

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.

Categories