How to use the global.php/local.php configs in the getConfig() of a module in a Zend Framework 2 application? - configuration

In a ZF2 application I have some cofigs, that: 1. need to be different dependening on the environment; 2. are specific for a concrete module. I'm curently using it like here described:
global.php & local.php
return array(
...
'modules' => array(
'Cache' => array(
'ttl' => 1, // 1 second
)
)
...
);
Module class
Module {
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $this->getConfig()['modules']['Cache']['ttl'],
...
));
},
...
)
);
}
...
}
...
}
It's working fine, but I believe, that the module specific settings should be accessed over one central place in the module -- the getConfig() method of the Module class. Like this:
class Module {
public function getConfig() {
$moduleConfig = include __DIR__ . '/config/module.config.php';
$application = $this->getApplicationSomehow(); // <-- how?
$applicationModuleConfig = $application->getConfig()['modules'][__NAMESPACE__];
$config = array_merge($moduleConfig, $applicationModuleConfig);
return $config;
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $serviceManager->get('Config')['modules']['Cache']['ttl'],
...
));
},
...
)
);
}
...
}
...
}
The problem is, that I don't get, how to access the global.php/local.php configs in the getConfig() of a module. How can I do it?

Every single configuration of every single loaded Module will be merged into one single config. Namely this would be:
$serviceManager->get('config');
The reason behind (global|local).config.php is merely for usage purpose. Global configuration files should always be deployed. Local configuration files however should only be deployed as distributionables, alias local.config.php.dist.
Distributionals will not be loaded, no matter where they are places. However common notion of ZF2 is to copy the distributionables into the /config/autoload-directory of the ZF2 Application and rename them to local.config.php
One example:
// YourModule/config/module.config.php
return array(
'key' => 1337
);
// YourModule/config/local.yourmodule.php.dist
return array(
'key' => 7331
);
Now when you publish / deploy your application, only module.config.php will be used. If someone wants to change the configuration of your Module, they would never touch module.config.php, as this file would constantly be overwritten when your module will be updated.
However what people can do is to copy:
YourModule/config/local.yourmodule.php.dist
to
/config/autoload/local.yourmodule.php
And change the config values inside this local configuration.
To understand:
You should always configure your module as best as possible for a LIVE-Scenario.
If you have environment-specific needs, overwrite this config using a local-config
local configs are never deployed automatically, this is a manual task needing to be done from inside the environment itself
Hope this got a little more clear
Ultimately:
configure your module for a LIVE-Scenario
On your development machine create a /config/autoload/mymodule.local.php and overwrite your ttl with it's development value
LoadOrder:
The last interesting part, which i have completely forgotten about, would be the load order of the configuration files. As all files are merged, this is important to note!
First to load is /config/application.config.php
Second to load would be each Modules /modules/{module}/config/module.config.php *
Last but not least the autoloadable files will be loaded /config/autoload/{filename}.php
asterix It is actually NOT module.config.php which is called, but rather the Module-classes configuration functions. Mainly these are:
getConfig()
getServiceConfig()
getViewHelperConfig()
ultimately everything under Zend\ModuleManager\Feature\{feature}ProviderInterface
If i understand this part of the ConfigListener correctly, then getConfig() will be called first and all of the specialiced {feature}ProviderInterfaces will overwrite the data of getConfig(), but don't take this for granted, it would need a check!

You're not supposed to access other Modules setting in your Module#getConfig(). If you rely on other configuration, that CAN ONLY BE for service purposes. Ergo you'd rely on Module#getServiceConfig() and inside the factories you do have access to the ServiceManagerand access your configs with $serviceManager->get('config');. (see Sam's comment)
The loading order of the configs is by default:
/config/application.config.php, that is the initial config file; not for module configs; here is the filename pattern for the config files to load defined ('config_glob_paths' => array('config/autoload/{,*.}{global,local}.php')).
{ModuleNamespace}\Module#getConfig() (e.g. Cache\Module#getConfig()), that by convention should load its /module/{ModuleNamespace}/config/module.config.php;
/config/autoload/global.php, that should not contain any module specific configs (see below);
/config/autoload/local.php, that contains environment specific settings also should not contain any module specific configs (see below); it should not versioned/deployed;
/config/autoload/{ModuleNamespaceLowerCased}.local.php (e.g. cache.local.php), that contains only the module AND environment specific settings and should not be versioned/;
For the Cache module above there can be following config files:
/module/Cache/config/module.config.php -- a complete set of module configs; loaded by Cache\Module#getConfig()
/module/Cache/config/cache.local.php.dist -- an example for /config/autoload/cache.local.php
/config/autoload/cache.local.php -- environment specific module configs
The setting ttl can be accessed from any place, where one has access to the Service Locator. For example in factory methods of Cache\Module#getServiceConfig()
class Module {
public function getConfig() {
$moduleConfig = include __DIR__ . '/config/module.config.php';
$application = $this->getApplicationSomehow(); // <-- how?
$applicationModuleConfig = $application->getConfig()['modules'][__NAMESPACE__];
$config = array_merge($moduleConfig, $applicationModuleConfig);
return $config;
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $serviceManager->get('Config')['ttl'],
...
));
},
...
)
);
}
...
}
...
}
For futher information about how configs are managed in ZF2 see Sam's answer and blog article.

Related

How do I call `grabFixture()` method from within `ApiTester instance in Yii2?

I am building an API in Yii2 2.0.14 and running tests with Codeception. Examples in the tutorial* show that I can call fixtures like so:
$profile = $I->grabFixture('profiles', 'user1');
However this doesn't seem to be available in my test class here:
<?php
namespace frontend\tests\api;
use frontend\tests\ApiTester;
class DemoCest
{
public function _fixtures()
{
return [
'users' => [
'class' => UserFixture::className(),
// fixture data located in tests/_data/user.php
'dataFile' => codecept_data_dir() . 'user.php'
],
];
}
public function demo(ApiTester $I)
{
$users = $I->grabFixture('users');
$I->wantTo('perform actions and see result');
$I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded');
$I->sendPOST('/user/test', ['name' => 'davert', 'email' => 'davert#codeception.com']);
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
$I->seeResponseIsJson();
$I->seeResponseContainsJson(['message' => 'test OK']);
}
}
I have seen that you need to add fixtures to frontend/tests/api-suite.yml but Codeception is throwing an exception when I do that
PHP Notice: Undefined index: tests in
/var/www/vendor/codeception/base/src/Codeception/Command/Run.php on
line 389
I am completely lost.
actor: ApiTester
modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
- \frontend\tests\Helper\Api
- REST:
url: http://securedata.test/api/v1
depends: PhpBrowser
part: Json
Can anyone lead me in the right direction?
tutorial - http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html
Add to your suite configuration, you haven't defined the tests path in the codeception.yml under paths.
If you look into the line the error points to it will take you to the line inside the matchSingleTest function
in your case
list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']);
in which it gives tests as undefined index for the $config['paths'] array, which if you backtrack parses the codeception.yml to the $config,
You are using an older version of Yii 2.0.14 which may have the missing section from the execute() see this ISSUE
So add tests directory as . and make sure your codeception.yml is in the /tests root.
see the following
paths:
tests: .

The default remember_me token in Laravel is too long

TL;DR How can I use my own way of generating the remember_me token?
I have an old site, written without any framework, and I have been given the job to rewrite it in Laravel (5.4.23). The DB is untouchable, cannot be refactored, cannot be modified in any way.
I was able to customise the Laravel authentication process using a different User model, one that reflect the old DB. But when it comes to the "Remember me" functionality, I have an issue with the length of the token.
The old site already uses the "Remember me" functionality but its DB field has been defined as BINARY(25). The token generated by the SessionGuard class is 60 characters long.
My first attempt was to try and find a way to shorten the token before writing it into the DB, and expand it again after reading it from the DB. I couldn't find such a way (and I'm not even sure there is such a way).
Then I looked into writing my own guard to override the cycleRememberToken (where the token is generated). I couldn't make it work, I think because the SessionGuard class is actually instantiated in a couple of places (as opposed to instantiate a class based on configuration).
So, I am stuck. I need a shorten token and I don't know how to get it.
Well, I was on the right track at one point.
I had to create my own guard, register it and use it. My problem, when I tried the first time, was that I did not register it in the right way. Anyway, this is what I did.
I put the following in AuthServiceProvides
Auth::extend('mysession', function ($app, $name, array $config) {
$provider = Auth::createUserProvider($config['provider']);
$guard = new MyGuard('lrb', $provider, app()->make('session.store'));
$guard->setCookieJar($this->app['cookie']);
$guard->setDispatcher($this->app['events']);
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
return $guard;
});
I change the guard in config/auth.php as
'guards' => [
'web' => [
'driver' => 'mysession',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
and finally my new guard
class MyGuard extends SessionGuard implements StatefulGuard, SupportsBasicAuth
{
/**
* #inheritdoc
*/
protected function cycleRememberToken(AuthenticatableContract $user)
{
$user->setRememberToken($token = Str::random(25));
$this->provider->updateRememberToken($user, $token);
}
}

zf2 Zend\ServiceManager\Exception\ServiceNotCreatedException

I spent half day to resolve this issue with no success.
I'm doing a setup in EC2, centos 6/64 bit. LAMP installed. On another hosting, my zf2 solution it work fine, so I've searched issue in php modules installed also (list at the end).
This error happen when zf2 try to get an instance of my custom service, also with wasabi mail.
\zend\config\application.config.php
'config_glob_paths' => array(
'./config/autoload/{,*.}{global,local}.php',
),
\zend\config\autoload\global.php
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
'Zend\CustomLogger' => function ($sm) {
$auth = $sm->get('zfcuser_auth_service');
$customLogger = new \Application\Service\CustomLogger(
$sm->get('Request'),
$sm->get('ZendLog'),
new \Zend\Session\SessionManager(),
$auth->getIdentity(), // $user
$sm->get('Mail'));
return $customLogger;
},
controller
<?php
namespace Foo\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Doctrine\ORM\EntityManager;
use MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__\stdClass;
class FooController extends AbstractActionController
{
protected $customLogger;
private function getCustomLogger()
{
if (null === $this->customLogger) {
$this->customLogger = $this->getServiceLocator()->get('Zend\CustomLogger');
}
return $this->customLogger;
}
public function indexAction()
{
$this->getCustomLogger();
$this->customLogger->controllerLog("ENTER IN Foo\Controller\FooController\index", "info");
// .... other code
}
}
Error
Zend\ServiceManager\Exception\ServiceNotCreatedException
File:
/var/www/solutions/mysolution/zend/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:930
Message:
An exception was raised while creating "Zend\CustomLogger"; no instance returned
PHP modules installed
bz2.so curl.so fileinfo.so iconv.so mbstring.so mysqlnd.so pdo_sqlite.so shmop.so sqlite3.so sysvshm.so xmlreader.so xsl.so
calendar.so dom.so ftp.so intl.so mysqlnd_mysqli.so pdo_mysqlnd.so phar.so simplexml.so sysvmsg.so tokenizer.so xml.so zip.so
ctype.so exif.so gettext.so json.so mysqlnd_mysql.so pdo.so posix.so sockets.so sysvsem.so wddx.so xmlwriter.so
Extension enabled in PHP ini
extension=/usr/lib64/php/5.5/modules/php_bz2.so
extension=/usr/lib64/php/5.5/modules/php_curl.so
extension=/usr/lib64/php/5.5/modules/php_fileinfo.so
extension=/usr/lib64/php/5.5/modules/php_gd2.so
extension=/usr/lib64/php/5.5/modules/php_intl.so
extension=/usr/lib64/php/5.5/modules/php_mbstring.so
extension=/usr/lib64/php/5.5/modules/php_mysql.so
extension=/usr/lib64/php/5.5/modules/php_mysqli.so
extension=/usr/lib64/php/5.5/modules/php_openssl.so
extension=/usr/lib64/php/5.5/modules/php_pdo_mysql.so
extension=/usr/lib64/php/5.5/modules/php_soap.so
extension=/usr/lib64/php/5.5/modules/php_xmlrpc.so
extension=/usr/lib64/php/5.5/modules/php_xsl.so
So simple solution... permission on log directory and some others directory are wrong. Restore the correct permissione, user and group on directory (e.g. data/logs, data/cache...)

How to do Kohana Validation of $_serialize_column inside ORM

The validation on Kohana ORM is done using rules
function rules()
{
return array(
'username' => array(
array('not_empty'),
array(array($this, 'availability')),
)
);
}
I'm struggling to validate a JSON encoded column using $_serialize_columns.
class Model_Admin extends ORM {
protected $_belongs_to = array();
protected $_has_many = array(
'plans' => array(),
'groups' => array(),
'transactions' => array(),
'logins' => array()
);
protected $_serialize_columns = array('data');
/**
* #param array $data
* #param Validation $validation
*
* #return bool
*/
public function data($data, $validation)
{
return
Validation::factory(json_decode($data, TRUE))
// ... rules ...
->check();
}
public function rules()
{
return array(
'data' => array(
array(array($this, 'data'), array(':value',':validation')
)
);
}
}
the array that gets encoded is:
array(
'name' => '',
'address' => '',
'phone' => '',
'postalcode' => ''
);
the data method receives the json encoded data, because the ORM runs the filters before doing the validation, so I need to convert it back to an associative array, then create a new validation object to check specifically for the content of that array. Because I can't merge Validation rules from another Validation instance
Updated Answer
The use of a second validation object is necessary since save() causes the internal model validation object to be checked. This means that rules added to the validation object being checked from a validation rule will be ignored (Validation->check() imports the rules into local scope before looping).
Since the data itself is technically another object (in the sense of object relationships, it has its own dataset that needs validation) the ideal solution would be to find a way to create a real model that saves the data.
There are numerous other benefits to saving data with proper database column definitions, not least if you need to perform data property lookups, make in-situ changes etc. (which would otherwise require unserializing the data column, potetnailly in all rows).
There are some alternatives, but they feel like kludges to me:
Create a model that represents the data object and add rules to it, using check() to validate the data (problem: will require a lot of maintenance, no real-world table means columns must be manually defined).
Set the data as real columns in the Admin model, and use a filter that will convert it into the data column on set (problem: again, must manually define the columns and exclude the additional columns from the save operation).
I hope this is of some use.
Original Answer
The Kohana ORM save() method permits the inclusion of an "extra" validation object, which is merged into the main ORM validation object namespace.
This is documented briefly here.
If I have understood correctly, I think you are looking to do something like this:
// another script, e.g., a controller
// Create the model
$admin = ORM::factory('Admin');
// $data = the data as an array, before serialization ...
$extra_validation = Validation::factory($data)
// add ->rule() calls here, but DO NOT chain ->check()
;
// Set $data in the model if it is going to be saved, e.g., $admin->data = $data;
// Set other data... e.g., $admin->foo = 'bar';
// Save the model
try {
$admin->save($extra_validation);
}
catch (ORM_Validation_Exception $e)
{
// Manipulate the exception result
}
While in this example you must still create another validation object, you are now able to catch all exceptions in a single block. I would recommend using var_dump() or similar on $e->errors() to check the namespace if you are using i18n messages to provide a human-readable error message. You should find that a namespace called "_external" has been created in the response.

An easy way to load ACL in Zend Framework 2?

I have been following this guide to load my menu configuration and i think it is very nice and clean way to load the menu.
My question is simple, is there a way to load your ACL configuration on the same way with a config array and some kinda of factory?
If there isn't, how do i load a ACL configuration and use with that menu in a easy way?
Thanks!
Edit:
This is a very good blog post on why use modules that is already done and not make your own, http://hounddog.github.com/blog/there-is-a-module-for-that/
ZF2 contains ACL and also RBAC (role based ACL - might be in ZF2.1), but to put it in place, easier is to use module which you can plug into your application. BjyAuthorize seems to me a bit bloated, you have to use ZfcUser module. I prefer ZfcRbac, the ACL rules are based on user roles (group) and their access to controller, action or route. Configuration stored in one config file, really easy to implement.
Most likely there are several ways to do it, but I prefer to do it in getViewHelperConfig() of application's Module.php (here I use BjyAuthorize module to simplify work with ACL, and in particular it allows to set ACL rules in configuration file module.bjyauthorize.global.php)
public function getViewHelperConfig()
{
return array(
'factories' => array(
'navigation' => function($sm) {
$auth = $sm->getServiceLocator()->get('BjyAuthorize\Service\Authorize');
$role = $auth->getIdentityProvider()->getIdentityRoles();
if (is_array($role))
$role = $role[0];
$navigation = $sm->get('Zend\View\Helper\Navigation');
$navigation->setAcl($auth->getAcl())->setRole($role);
return $navigation;
}
)
);
}
Play with This structure . get role and resource from database and save this in session for or any caching .
You are right, there is no out-of-the-box-all-in-one solution. You have to build some bridges between the modules.
Another easy way to integrate BjyAuthorize is using **Zend Navigation**s default methods as described by Rob Allen:
Integrating BjyAuthorize with ZendNavigation
$sm = $e->getApplication()->getServiceManager();
// Add ACL information to the Navigation view helper
$authorize = $sm->get('BjyAuthorizeServiceAuthorize');
$acl = $authorize->getAcl();
$role = $authorize->getIdentity();
ZendViewHelperNavigation::setDefaultAcl($acl);
ZendViewHelperNavigation::setDefaultRole($role);
You can also use ZfcRbac and use a listener to make it work with Zend Navigation.
Since this is a lot of code I simply post the link here:
Check Zend Navigation page permissions with ZfcRbac – Webdevilopers Blog
I've just created an ACL module that creates an ACL Service parsing the routes.
To manage your access control to your application you only need to define roles and add a new key 'roles' in every route. If you do not define that key or its array is empty, then the route becomes public. It also works with child routes.
As an example:
array(
'router' => array(
'routes' => array(
'user\users\view' => array(
'type' => 'Segment',
'options' => array(
'route' => '/admin/users/view/id/:id/',
'constraints' => array(
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'User\Controller\Users',
'action' => 'view',
'roles' => ['admin', 'user'],
),
),
),
),
),
);
The module can be installed via composer and it is now listed in the zend modules repository: http://zfmodules.com/itrascastro/TrascastroACL
You can get more detailed info about use and installation from my blog: http://www.ismaeltrascastro.com/acl-module-zend-framework/