0

I try to migrate from ZF2 to ZF3 but many viewHelpers and validators do not work. But only the ones who overwrite ZendFrameworks viewhelpers / validators do not work...

I want f.e. to overwrite the CSRF validator to allow a higher timeout by default.

I have the following application configuration:

$config = array(
// This should be an array of module namespaces used in the application.
'modules' => array(
    'Zend\Cache',
    'Zend\Db',
    'Zend\Log',
    'Zend\Mail',
    'Zend\Mvc\Console',
    'Zend\Mvc\I18n',
    'Zend\I18n',
    'Zend\Mvc\Plugin\FilePrg',
    'Zend\Form',
    'Zend\Hydrator',
    'Zend\InputFilter',
    'zend\Form',
    'Zend\Filter',
    'Zend\Mvc\Plugin\FlashMessenger',
    'Zend\Mvc\Plugin\Identity',
    'Zend\Mvc\Plugin\Prg',
    'Zend\Navigation',
    'Zend\Paginator',
    'Zend\Serializer',
    'Zend\ServiceManager\Di',
    'Zend\Session',
    'Zend\Router',
    'Zend\Validator',
    'DoctrineModule',
    'DoctrineORMModule',
    'TwbBundle',
    'AssetManager',
    #'Reliv\ElFinder',
    'ZfcUser', //https://github.com/ZF-Commons/ZfcUser
    'ZfcUserDoctrineORM',
    'BjyAuthorize', // https://github.com/bjyoungblood/BjyAuthorize
    'Base',
    'Product',
    'Blog',
    'Admin'
),

// These are various options for the listeners attached to the ModuleManager
'module_listener_options' => array(
    // This should be an array of paths in which modules reside.
    // If a string key is provided, the listener will consider that a module
    // namespace, the value of that key the specific path to that module's
    // Module class.
    'module_paths' => array(
        './module',
        './vendor',
    ),

    // An array of paths from which to glob configuration files after
    // modules are loaded. These effectively override configuration
    // provided by modules themselves. Paths may use GLOB_BRACE notation.
    'config_glob_paths' => array(
        'config/autoload/{,*.}{global,local}.php',
    ),

    // Whether or not to enable a configuration cache.
    // If enabled, the merged configuration will be cached and used in
    // subsequent requests.
    // 'config_cache_enabled' => true,

    // The key used to create the configuration cache file name.
    //'config_cache_key' => $stringKey,

    // Whether or not to enable a module class map cache.
    // If enabled, creates a module class map cache which will be used
    // by in future requests, to reduce the autoloading process.
    // 'module_map_cache_enabled' => true,

    // The key used to create the class map cache file name.
    #'module_map_cache_key' => $stringKey,

    // The path in which to cache merged configuration.
    'cache_dir' => "data/cache/",

    // Whether or not to enable modules dependency checking.
    // Enabled by default, prevents usage of modules that depend on other modules
    // that weren't loaded.
    // 'check_dependencies' => true,
),

// Used to create an own service manager. May contain one or more child arrays.
//'service_listener_options' => array(
//     array(
//         'service_manager' => $stringServiceManagerName,
//         'config_key'      => $stringConfigKey,
//         'interface'       => $stringOptionalInterface,
//         'method'          => $stringRequiredMethodName,
//     ),
// )

// Initial configuration with which to seed the ServiceManager.
// Should be compatible with Zend\ServiceManager\Config.
// 'service_manager' => array(),
);

Module config of Base module:

namespace Base;
...
return [
    ...
    'validators' => array(
        'invokables' => [
            \Zend\Validator\Csrf::class => Validator\Csrf::class
        ]
    )
    ...
]

Base\Validator\Csrf:

<?php

namespace Base\Validator;


class Csrf extends \Zend\Validator\Csrf
{
    protected $timeout = 1;

    public function __construct($options = [])
    {
        parent::__construct($options);

        die("THIS DOES NOT GETTING PRINTED! NOR DOES THE BREAKPOINT HIT.");
    }
}

EDIT: Added autoload config

composer.json:

"autoload": {
    "psr-4": {
      "Base\\": "module/Base/src/"
    }
  }

EDIT 2: Possible bug in implementation of \Zend\Form\Element\Csrf ?

Interesting, the CsrfValidator just gets directly instantiated here...

/**
 * Get CSRF validator
 *
 * @return CsrfValidator
 */
public function getCsrfValidator()
{
    if (null === $this->csrfValidator) {
        $csrfOptions = $this->getCsrfValidatorOptions();
        $csrfOptions = array_merge($csrfOptions, ['name' => $this->getName()]);
        $this->setCsrfValidator(new CsrfValidator($csrfOptions));
    }
    return $this->csrfValidator;
} 

Stacktrace (breakpoint in \Zend\Validator\Csrf __construct()) Stacktrace

StaticPage is another module of mine.

I also debugged with xdebug and set a break point in the CsrfFactory (return statement) to see, if it is used (but it isn't). I thought I can overwrite services / validators etc. easily in ZF3... Did I miss something?

koseduhemak
  • 523
  • 2
  • 4
  • 19
  • First of you do not need a factory only to make a `new Csrf()`, you should make it an invokable instead. Have you tried genetaring the classmap again or refreshing the cache ? – Unex Dec 01 '17 at 11:15
  • Thank you for your answer. I replaced invokables with factories as per migration guide to zend framework 3... But I cannot use InvokableFactory here because it will return the zend-validator version of csrf instead of mine. But I will look into that further... My autoload is done via composer (edited question with autoload config). I reran ´composer dump-autoload -o´ but that changed nothing. Cache is disabled in application.config (edited in question aswell). – koseduhemak Dec 01 '17 at 11:23
  • It should be like this : `'invokables' => [ \Zend\Validator\Csrf::class => Validator\Csrf::class ]`. And you can delete your factory and the 'factories' key . This won't solve your issue, but will remove useless code, and maybe help you find the issue faster. – Unex Dec 01 '17 at 11:25
  • Thanks, edited it in my module.config.php and in the question. Debugged again / throw "debug" exception in Base\Validator\Csrf __construct() to test if it gets loaded - but no luck yet. – koseduhemak Dec 01 '17 at 11:32
  • This is weird, it looks like your module configuration is not loaded. Are you sure your module is properly loaded ? could you try with another pair key => class to make sure it's loaded ? – Unex Dec 01 '17 at 11:39
  • Could you try changing the 'validators' key with the 'service_manager' key ? – Unex Dec 01 '17 at 11:43
  • I tried to add it to the service_manager invokable key but with no luck, I tried also to add it to both, the service_manager invokable key and the validators invokable key at the same time - no luck. I dumped the included module.config.php in Module.php of `Base` module and the resulting config array is correct (as defined in the module.config.php file). I am not sure what do you mean with `could you try with another pair key => class to make sure it's loaded`? – koseduhemak Dec 01 '17 at 11:54
  • Okay, then maybe you could try adding a breakpoint in the Zend\Validator\Csrf and checking in the stacktrace where things happens to load this validator instead of yours. You'll find where it's loaded, with which key and you will be able to check if the service managers registered properly your key. looks like your config is not loaded. – Unex Dec 01 '17 at 11:57
  • Ok I edited my question with image of stacktrace and found a weird implementation in `\Zend\Form\Element\Csrf`. The CsrfValidator gets directly instantiated there without using service_manager mechanism... – koseduhemak Dec 01 '17 at 12:08
  • Well then i'm not sure if it should be like that in the zend module. But as of now, you should probably instantiate your own csrf either directly or injecting it where you need with the service manager :). – Unex Dec 01 '17 at 12:27

1 Answers1

2

You can use a delegator to change the validator attached to the form element when the element is instantiated. Essentially a delegator allows you (in this case) to modify the form element after it has been constructed - the idea is explained well here.

In your case you would create a class:

<?php

namespace Base\Delegator;

use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\Validator\Csrf as CsrfValidator;

class CsrfDelegatorFactory implements DelegatorFactoryInterface
{
    public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
    {
        // construct the Csrf form element
        $element = call_user_func($callback);

        // set the validator with chosen timeout and other options
        $element->setCsrfValidator(new CsrfValidator(
            [
                // ...
                'timeout' => 10000
            ]
        ));

        return $element;
    }
}

Then map the delegator to Zend\Form\Element\Csrf in your application module.config.php:

'form_elements' => [
    // ...
    'delegators' => [
        \Zend\Form\Element\Csrf::class => [
            0 => \Base\Delegator\CsrfDelegatorFactory::class
        ],
    ]
],

Note that this is only changing the validator assigned by default to a Zend\Form\Element\Csrf and a csrf validator obtained by other means will not be affected.

avy
  • 644
  • 6
  • 16