How to use Zend 2 save handler DbTableGateway? - php

The Zend\Session Save Handler tutorial gives an example for DbTableGateway in which they create a TableGateway with an undefined $adapter variable. I want to use the handler to tie the Session Manager (from the previous page of the tutorial) to my session storage table in my database. How can I do this?
I guess the code should look something like this?
class Module implements AutoloaderProviderInterface, ConfigProviderInterface
{
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
// create the session manager
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$this->bootstrapSession($e);
}
public function bootstrapSession($e)
{
$session = $e->getApplication()
->getServiceManager()
->get('Zend\Session\SessionManager');
$tableGateway = new TableGateway('session', $adapter); // somehow define this somewhere?
$saveHandler = new DbTableGateway($tableGateway, new DbTableGatewayOptions());
$session->setSaveHandler($saveHandler);
$session->start();
$container = new Container('initialized');
if (!isset($container->init)) {
$serviceManager = $e->getApplication()->getServiceManager();
$request = $serviceManager->get('Request');
$session->regenerateId(true);
$container->init = 1;
$container->remoteAddr = $request->getServer()->get('REMOTE_ADDR');
$container->httpUserAgent = $request->getServer()->get('HTTP_USER_AGENT');
$config = $serviceManager->get('Config');
if (!isset($config['session'])) {
return;
}
$sessionConfig = $config['session'];
if (isset($sessionConfig['validators'])) {
$chain = $session->getValidatorChain();
foreach ($sessionConfig['validators'] as $validator) {
switch ($validator) {
case 'Zend\Session\Validator\HttpUserAgent':
$validator = new $validator($container->httpUserAgent);
break;
case 'Zend\Session\Validator\RemoteAddr':
$validator = new $validator($container->remoteAddr);
break;
default:
$validator = new $validator();
}
$chain->attach('session.validate', array($validator, 'isValid'));
}
}
}
}
public function getServiceConfig()
{
return array(
'factories' => array(
'Zend\Session\SessionManager' => function ($sm) {
$config = $sm->get('config');
if (isset($config['session'])) {
$session = $config['session'];
$sessionConfig = null;
if (isset($session['config'])) {
$class = isset($session['config']['class']) ? $session['config']['class'] : 'Zend\Session\Config\SessionConfig';
$options = isset($session['config']['options']) ? $session['config']['options'] : array();
$sessionConfig = new $class();
$sessionConfig->setOptions($options);
}
$sessionStorage = null;
if (isset($session['storage'])) {
$class = $session['storage'];
$sessionStorage = new $class();
}
$sessionSaveHandler = null;
if (isset($session['save_handler'])) {
// class should be fetched from service manager since it will require constructor arguments
$sessionSaveHandler = $sm->get($session['save_handler']);
}
$sessionManager = new SessionManager($sessionConfig, $sessionStorage, $sessionSaveHandler);
} else {
$sessionManager = new SessionManager();
}
Container::setDefaultManager($sessionManager);
return $sessionManager;
},
),
);
}
/***************************************************************************************************
* Returns the location of the module.config.php file. This function is used by the Zend Framework
* underneath the hood.
***************************************************************************************************/
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
/***************************************************************************************************
* Returns the Zend StandardAutoLoader which contains the directory structure of the module source
* folder.
***************************************************************************************************/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}

I have this code for save a session to a db table.
'service_manager' => array(
'factories' => array(
'Zend\Session\SessionManager' => function (\Zend\ServiceManager\ServiceManager $sm) {
$sessionConfig = new \Zend\Session\Config\SessionConfig();
$sessionConfig->setOptions(
array(
'use_cookies' => true,
'name' => 'ed2',
'gc_maxlifetime' => 1728000
)
);
/* #var $adapter \Zend\Db\Adapter\Adapter */
$adapter = $sm->get('Zend\Db\Adapter\Adapter');
$tableGateway = new \Zend\Db\TableGateway\TableGateway('session', $adapter);
$saveHandler = new \Common\Session\SaveHandler\DbTableGateway(
$tableGateway,
new \Zend\Session\SaveHandler\DbTableGatewayOptions()
);
$sessionManager = new \Zend\Session\SessionManager($sessionConfig);
$sessionManager->setSaveHandler($saveHandler);
$sessionManager->start();
return $sessionManager;
},
)
)
Config for db
'db' => array(
'driver' => 'Pdo_Mysql',
'database' => 'release',
'username' => 'username',
'password' => 'password',
'hostname' => '127.0.0.1',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
)
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => function (\Zend\ServiceManager\ServiceManager $serviceManager) {
$adapterFactory = new Zend\Db\Adapter\AdapterServiceFactory();
$adapter = $adapterFactory->createService($serviceManager);
Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter);
return $adapter;
}
)
)

The answer turned out to be like newage's answer:
Since newage edited his answer to include the db adapter, I've accepted it as the right answer. The rest of this is just my implementation:
You can remove all the TableGateway and savehandler logic from the bootstrapSession method and put it in the getServiceConfig method.
Add the definition for Adapter to the 'factories' array in getServiceConfig, then modify the 'Zend\Session\SessionManager' function to include the Adapter, TableGateway, and save handler. This is what the new getServiceConfig would look like:
public function getServiceConfig()
{
return array(
'factories' => array(
// New code here
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
// New code here
'Zend\Session\SessionManager' => function ($sm) {
$config = $sm->get('config');
if (isset($config['session'])) {
$session = $config['session'];
$sessionConfig = null;
if (isset($session['config'])) {
$class = isset($session['config']['class']) ? $session['config']['class'] : 'Zend\Session\Config\SessionConfig';
$options = isset($session['config']['options']) ? $session['config']['options'] : array();
$sessionConfig = new $class();
$sessionConfig->setOptions($options);
}
$sessionStorage = null;
if (isset($session['storage'])) {
$class = $session['storage'];
$sessionStorage = new $class();
}
$sessionSaveHandler = null;
if (isset($session['save_handler'])) {
// class should be fetched from service manager since it will require constructor arguments
$sessionSaveHandler = $sm->get($session['save_handler']);
}
$sessionManager = new SessionManager();
}
// New code here
/* #var $adapter \Zend\Db\Adapter\Adapter */
$adapter = $sm->get('Zend\Db\Adapter\Adapter');
$tableGateway = new TableGateway('mytablename', $adapter);
$saveHandler = new DbTableGateway($tableGateway, new DbTableGatewayOptions());
$sessionManager->setSaveHandler($saveHandler);
// New code here
Container::setDefaultManager($sessionManager);
return $sessionManager;
},
),
);
}
Then add the database connection info to the module's config file:
return array(
// ...
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=mydbname;host=mydbhost;port=xxxx',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'dbusername',
'password' => 'dbpassword',
),
);

Related

Apigility: Send Error code 404 module.php in bootstrap

I use APIGILITY to create a Web-service, I’d like to resolve a problem. I don’t have a specific database linked to my app ,the database is specified by a parameter in the URL.
For example http://sk.localhost/users-service/1, where 1 is the database’s paramaters “projet” you can see below.
It’s my database .conf:
return array(
'db' => array(
'1' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=projet;host=localhost',
'driver_option' => array(
1002 => 'SET NAMES \'UTF8\''
),
),
'2' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=cgm;host=localhost',
'driver_option' => array(
1002 => 'SET NAMES \'UTF8\''
),
));
And it’s my code to specify the database with the parameter:
class Module
{
private $id_base;
private $conf_Users_tables;
private $conf_Droits_tables;
private $data;
public function init(ModuleManager $moduleManager)
{
$events = $moduleManager->getEventManager();
$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this, 'initDBSConfig'));
}
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$eventManager->attach('route', array($this, 'checkRoute'));
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function checkRoute(MvcEvent $e)
{
$route = $e->getRouteMatch()->getParams();
$this->id_base = $route['id_base'];
}
public function initDBSConfig(ModuleEvent $e)
{
$conf = include __DIR__ . '/../../config/Dbs.conf.php';
$this ->data = $conf['db'];
$this->initConfTables();
}
function initConfTables()
{
$confUsers = include __DIR__ . '/../../config/Users.conf.php';
$confDroits = include __DIR__ . '/../../config/Droits.conf.php';
$this->conf_Users_tables = $confUsers;
$this->conf_Droits_tables = $confDroits;
}
public function getServiceConfig()
{
return array(
'factories' => array(
'Application\Model\UsersTable' => function($sm) {
if(isset($this->data[$this->id_base]))
{
$tableGateway = $sm->get('UsersTableGateway');
$table = new UsersTable($tableGateway);
return $table;
}
else
{
return;
}
},
'UsersTableGateway' => function ($sm) {
$dbAdapter = $sm->get('SwitchDbAdapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype($sm->get('UsersModel'));
return new TableGateway($this ->data['t'.$this->id_base]['users'], $dbAdapter, null, $resultSetPrototype);
},
'UsersModel' => function($sm) {
$usersModel = new UsersModel();
$usersModel->setConfig($this->conf_Users_tables[$this->id_base]);
return $usersModel;
},
'Application\Model\DroitsTable' => function($sm) {
$tableGateway = $sm->get('DroitsTableGateway');
$table = new DroitsTable($tableGateway);
return $table;
},
'DroitsTableGateway' => function ($sm) {
$dbAdapter = $sm->get('SwitchDbAdapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype($sm->get('DroitsModel'));
return new TableGateway($this ->data['t'.$this->id_base]['droits'], $dbAdapter, null, $resultSetPrototype);
},
'DroitsModel' => function($sm) {
$droitsModel = new DroitsModel();
$droitsModel->setConfig($this->$conf_Droits_tables[$this->id_base]);
return $droitsModel;
},
'SwitchDbAdapter' => function ($sm) {
$dbAdapter = new \Zend\Db\Adapter\Adapter($this->data[$this->id_base]);
return $dbAdapter;
},
),
);
}
}
So I’d like the application send HTTP ERROR code 404 if the parameter in the URL doesn’t match to any databases in my config.
public function getServiceConfig()
{
if (empty($this->data[$this->id_base])){
header('HTTP/1.0 404 Not Found');
die('<h1>404 Not Found</h1>Incorrect Database request.');
}
// Rest of you code in method getServiceConfig() ...
}

EDIT: Class Zend\Db\Adapter\Adapter not found

I have a simple form which after submitting redirects to processAction inside AuthController and in this action I want to create a simple table bar.
EDITED:
Referring to Zend framerwork DB DDL update, I made a little modification in below code
AuthController.php
<?php
namespace Blog\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Debug\Debug;
use Blog\Form\LoginForm;
use Zend\Authentication\AuthenticationService;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Ddl;
use Zend\Db\Sql\Ddl\Column;
use Zend\Db\Sql\Insert;
use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter;
class AuthController extends AbstractActionController
{
protected $adapter;
public function getAdapter()
{
if (!$this->adapter) {
$sm = $this->getServiceLocator();
$this->adapter = $sm->get('Zend\Db\Adapter\Adapter');
}
return $this->adapter;
}
public function indexAction()
{
return new ViewModel();
}
public function processAction()
{
$DB = new \Zend\Db\Adapter\Adapter(array(
'driver' => 'Pdo',
'database' => 'blog',
'username' => 'root',
'password' => 'mysql'
));
$this->adapter = $this->getAdapter();
$sql = new Sql($this->adapter);
$table = new Ddl\CreateTable('bar');
$table->addColumn(new Column\Integer('id'));
$table->addColumn(new Column\Varchar('name', 255));
$table->setTable('bar');
$results = $this->adapter->query($sql->getSqlStringForSqlObject($table), $this->adapter ::QUERY_MODE_EXECUTE);
return new ViewModel();
}
}
global.php:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=blog;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
Module.php
<?php
namespace Blog;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements AutoloaderProviderInterface,ConfigProviderInterface
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getServiceConfig()
{
return array(
'factories' =>array(
'Zend\Db\Adapter\Adapter' => function ($sm) {
$config = $sm->get('Config');
return new \Zend\Db\Adapter\Adapter($config['db']);
}
)
);
}
}
Problem:(Updated)
table bar is not created and shows error like
Fatal error: Class 'Blog\Controller\Zend\Db\Adapter\Adapter' not found
in /var/www/zend2/module/Blog/src/Blog/Controller/AuthController.php
on line 110
if I print
echo $sql->getSqlStringForSqlObject($table);
The query prints like this
CREATE TABLE `bar` ( `id` INTEGER NOT NULL, `name` VARCHAR(255) NOT NULL )
but table was not there.
Error occurs in below line of code as it is not able to identify adapter :
$results = $this->adapter->query($sql->getSqlStringForSqlObject($table), $this->adapter ::QUERY_MODE_EXECUTE);
But works in this way:
$results = $this->adapter->query($sql->getSqlStringForSqlObject($table), $DB ::QUERY_MODE_EXECUTE);
I am using Zend 2.4
I think you are trying to access non existing service. You can try to create a service factory:
config.php
<?php
return [
'db' => [
'driver' => 'Pdo',
'database' => 'blog',
'username' => 'root',
'password' => 'mysql'
]
];
Module.php
<?php
class Module
{
public function getServiceConfig()
{
return [
'factories' => [
'Zend\Db\Adapter\Adapter' => => function ($sm) {
$config = $sm->get('Config');
return new \Zend\Db\Adapter\Adapter($config['db']);
},
],
];
}
}
Then, you can access the service with the service manager:
AuthController.php
class AuthController extends AbstractActionController
{
public function getAdapter()
{
if (!$this->adapter) {
$sm = $this->getServiceLocator();
$this->adapter = $sm->get('Zend\Db\Adapter\Adapter');
}
return $this->adapter;
}
public function processAction()
{
$this->adapter = $this->getAdapter();
$sql = new Sql($this->adapter);
// other stuff here
}
}
You can find more examples here and here.
For beginners in ZF2 ,the query execute won't work if you don't instantiate class Zend\Db\Adapter\Adapter correctly.
I corrected like
$DB = new \Zend\Db\Adapter\Adapter(array(
'driver' => 'Pdo',
'database' => 'blog',
'username' => 'root',
'password' => 'mysql'
));
Also below line of code :
$results = $this->adapter->query($sql->getSqlStringForSqlObject($table), $DB ::QUERY_MODE_EXECUTE);
Ref Fatal error : class not found in Zend framework 2
Note: I still fail to understand why $this->adapter = $this->getAdapter(); not working instead of $DB.Any hint will be appreciated.

Database Translations Zend Framework 2

I have problem with creating custom translator from database in ZF2. I have a DB like this
and files:
1)Application/module.config.php
'service_manager' => array(
'abstract_factories' => array(),
'factories' => array(
'translator' => function (\Zend\ServiceManager\ServiceManager $serviceManager)
{
$pluginManager = new \Zend\I18n\Translator\LoaderPluginManager();
$pluginManager->setServiceLocator($serviceManager);
$pluginManager->setFactory('DatabaseTranslationLoaderFactory', function($serviceManager)
{
$translator = new \Zend\I18n\Translator\DatabaseTranslationLoaderFactory();
return $translator->createService($serviceManager);
});
$translator = new \Zend\I18n\Translator\Translator(array());
$translator->setFallbackLocale('en_US');
$translator->setPluginManager($pluginManager);
$translator->addRemoteTranslations('DatabaseTranslationLoaderFactory');
return $translator;
},
),
),
'translator' => array(
'locale' => 'en_US',
'translation_file_patterns' => array(
array(
'type' => 'Zend\I18n\Translator\Loader\Database',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
),
),
),
2) Zend/I18n/Translator/Loader/Database.php
<?php
namespace Zend\I18n\Translator\Loader;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\I18n\Translator\Plural\Rule as PluralRule;
use Zend\I18n\Translator\TextDomain;
class Database implements RemoteLoaderInterface {
protected $dbAdapter;
public $dbAdapter;
public function __construct(Adapter $dbAdapter = null)
{
if ($dbAdapter === null)
{
$configArray = array('driver' => 'Pdo_Mysql',
'database' => 'dbname',
'username' => 'username',
'password' => 'pswd',
'hostname' => 'localhost',
'charset' => 'utf-8',
);
$dbAdapter = new Adapter($configArray);
}
$this->dbAdapter = $dbAdapter;
}
public function load($locale, $textDomain)
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('ic_var')->columns(array('value'))
->where(array('language' => $locale, 'name' => $textDomain));
$messages = $this->dbAdapter->query(
$sql->getSqlStringForSqlObject($select),
Adapter::QUERY_MODE_EXECUTE
);
$textDomain = new TextDomain();
foreach ($messages as $message) {
if (isset($textDomain[$message['name']])) {
if (!is_array($textDomain[$message['name']])) {
$textDomain[$message['name']] = array(
$message['plural_index'] => $textDomain[$message['name']]
);
}
$textDomain[$message['name']][$message['plural_index']] = $message['value'];
} else {
$textDomain[$message['name']] = $message['value'];
}
}
return $textDomain;
}
}
3) Zend/I18n/Translator/DatabaseTranslationLoaderFactory.php
<?php
namespace Zend\I18n\Translator;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\I18n\Translator\Loader\Database;
class DatabaseTranslationLoaderFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new Database($serviceLocator->get('Zend\Db\Adapter\Adapter'));
}
}
4) Application/Module.php
public function onBootstrap(MvcEvent $e)
{
$translator = $e->getApplication()->getServiceManager()->get('translator');
$translator->addTranslationFile(
'DatabaseTranslationLoader',
'text-domain',
'text-domain'
);
}
But translation doesn`t work, because db adapter not find in loader:
Fatal error: Uncaught exception 'Zend\I18n\Exception\RuntimeException' with message 'Specified loader is not a file loader'
Thanks for your answers!
First of all you shouldn't define your custom classes in the Zend namespace as this is reserved a namespace for the ZF2 library and you don't want to touch (or add) files in the vendor directory.
Just put the custom classes in your own namespace outside the vendor folder. i.e. MyI18n
You can register you custom remote loader to the pluginManager in module.config.php.
return array(
'translator' => array(
'loaderpluginmanager' => array(
'factories' => array(
'database' => 'MyI18n\Translator\DatabaseTranslationLoaderFactory',
)
),
'remote_translation' => array(
array(
'type' => 'database' //This sets the database loader for the default textDomain
),
),
)
);
You don't have to overwrite the Translator factory if you want to add a custom loader, so just remove that code in your Module.php.
Als remove the configuration under translation_file_patterns as this is only needed for file loaders.
EDIT
For the above to work you need to overwrite the TranslatorServiceFactory because ZF has no support to register custom loaders on the plugin manager.
namespace MyNamespace\Translator;
use Zend\Mvc\Service\TranslatorServiceFactory as BaseTranslatorFactory;
class TranslatorServiceFactory extends BaseTranslatorFactory
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return MvcTranslator
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$translator = parent::createService($serviceLocator);
$config = $serviceLocator->get('Config');
$pluginManagerConfig = isset($config['translator']['loaderpluginmanager']) ? $config['translator']['loaderpluginmanager'] : array();
$pluginManager = new LoaderPluginManager(new Config($pluginManagerConfig));
$pluginManager->setServiceLocator($serviceLocator);
$translator->setPluginManager($pluginManager);
return $translator;
}
}
Now register your custom factory in the service configuration:
class Module
{
public function getServiceConfig()
{
return array(
'factories' => array(
'MvcTranslator' => 'MyNamespace\Translator\TranslatorServiceFactory',
)
)
}
}
I register custom remote loader to the pluginManager in module.config.php like this
'translator' => [
'loaderpluginmanager' => [
'factories' => [
'database' => function($lpm){
$sm = $lpm->getServiceLocator();
$loader = new Zf2Translation\Loader\DatabaseTranslationLoader($sm);
return $loader;
},
],
],
'remote_translation' => [
[
'type' => 'database',
],
],
]
Next in Database Loader class
use Zend\I18n\Translator\Loader\RemoteLoaderInterface;
class DatabaseTranslationLoader implements RemoteLoaderInterface
{
protected $dbAdapter;
protected $sm;
public function __construct(ServiceManager $sm)
{
$this->sm = $sm;
$this->dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
}
}
I hope it helps.

ZF2 SessionManager usage

I am new to ZF2 and not quite used to how to do stuff. I want to use session to keep track of an user (remember me). I had this code in one part of my class:
$sessionManager = new \Zend\Session\SessionManager();
$sessionManager->rememberMe($time);
// i want to keep track of my user id too
$populateStorage = array('user_id' => $user->getId());
$storage = new ArrayStorage($populateStorage);
$sessionManager->setStorage($storage);
Ok, so far so good. When i try:
var_dump($sessionManager->getStorage());
I get the expected data.
In another part of my program, i want to retreive my data again (a bit like containers):
$sessionManager = new \Zend\Session\SessionManager();
var_dump($sessionManager->getStorage());
This only returns an empty object.
I guess this is due to the "new" and i think i have to implement SessionManager in a different way, but how? I do not know. This is what i came up with:
In my Module i now have:
public function onBootstrap(\Zend\Mvc\MvcEvent $e)
{
$config = $e->getApplication()
->getServiceManager()
->get('Configuration');
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions($config['session']);
$sessionManager = new SessionManager($sessionConfig);
$sessionManager->start();
In my module.config:
'session' => array(
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
),
But how to procede? How do i get the instance for my sessionManager?
There is not well documented SessionManagerFactory (zf2 api doc) and SessionConfigFactory (zf2 api doc). With those is instantiate SessionManager very easy, just put these factories to your ServiceManager configuration:
'service_manager' => [
'factories' => [
'Zend\Session\SessionManager' => 'Zend\Session\Service\SessionManagerFactory',
'Zend\Session\Config\ConfigInterface' => 'Zend\Session\Service\SessionConfigFactory',
],
],
and to module configuration put your session options, under the session_config key:
'session_config' => [
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
],
and that's it, now you can grab SessionManager from service locator anywhere, for example in controller:
/** #var Zend\Session\SessionManager $sm */
$sessionManager = $this->serviceLocator->get('Zend\Session\SessionManager');
This is available as of 2.2 version of Zend Framework (related pull request).
In my project i use example from other source (book or official documentation, forget).
Main Module.php file:
namespace Application;
class Module
{
public function getServiceConfig()
{
return array(
'factories' => array(
'Zend\Session\SessionManager' => function ($sm){
$config = $sm->get('config');
if (isset($config['session'])){
$session_config = $config['session'];
$sessionConfig = null;
if (isset($session_config['config'])){
$class = isset($session_config['config']['class'])
? $session_config['config']['class']
: 'Zend\Session\Config\SessionConfig';
$options = isset($session_config['config']['options'])
? $session_config['config']['options']
: array();
$sessionConfig = new $class();
$sessionConfig->setOptions($options);
}
$sessionStorage = null;
if (isset($session_config['storage'])){
$class = $session_config['storage'];
$sessionStorage = new $class();
}
$sessionSaveHandler = null;
if (isset($session_config['save_handler'])){
// class should be fetched from service manager
// since it will require constructor arguments
$sessionSaveHandler = $sm->get($session_config['save_handler']);
}
$sessionManager = new SessionManager(
$sessionConfig,
$sessionStorage,
$sessionSaveHandler
);
if (isset($session_config['validator'])){
$chain = $sessionManager->getValidatorChain();
foreach ($session_config['validator'] as $validator){
$validator = new $validator();
$chain->attach('session.validate', array(
$validator,
'isValid'
));
}
}
} else {
$sessionManager = new SessionManager();
}
Container::setDefaultManager($sessionManager);
return $sessionManager;
},
),
);
}
}
My config/autoload/global.php config file:
return array(
'session' => array(
'config' => array(
'class' => 'Zend\Session\Config\SessionConfig',
'options' => array(
'use_cookies' => true,
),
),
'storage' => 'Zend\Session\Storage\SessionArrayStorage',
'validators' => array(
array(
'Zend\Session\Validator\RemoteAddr',
'Zend\Session\Validator\HttpUserAgent',
),
),
),
);
To get user_id from session:
$sessionStorage = $sm->get('Zend\Session\SessionManager')
->getStorage();
$user_id = $sessionStorage->user_id;

ZF2 tutorial Album PHPUnit test failing with Undefined property: AlbumTest\Model\AlbumTableTest::$controller

I am building the ZF2 Album tutorial application and when I unit test I am consistently receiving the same error even though I've rebuilt the application again. Can someone tell me what is going on here? I am dumping all relevant information here to assist. The error is:
PHPUnit 3.7.10 by Sebastian Bergmann.
Configuration read from D:\PHP\zf2-tutorial\module\Album\test\phpunit.xml.dist
......E
Time: 0 seconds, Memory: 6.25Mb
There was 1 error:
1) AlbumTest\Model\AlbumTableTest::testGetAlbumTableReturnsAnInstanceOfAlbumTable
Undefined property: AlbumTest\Model\AlbumTableTest::$controller
D:\PHP\zf2-tutorial\module\Album\test\AlbumTest\Model\AlbumTableTest.php:116
FAILURES!
Tests: 7, Assertions: 9, Errors: 1.
AlbumTableTest.php is as follows and the error is received on the final assertion:
<?php
namespace AlbumTest\Model;
use Album\Model\AlbumTable;
use Album\Model\Album;
use Zend\Db\ResultSet\ResultSet;
use PHPUnit_Framework_TestCase;
class AlbumTableTest extends PHPUnit_Framework_TestCase {
public function testFetchAllReturnsAllAlbums() {
$resultSet = new ResultSet();
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('select')
->with()
->will($this->returnValue($resultSet));
$albumTable = new AlbumTable($mockTableGateway);
$this->assertSame($resultSet, $albumTable->fetchAll());
}
public function testCanRetrieveAnAlbumByItsId() {
$album = new Album();
$album->exchangeArray(array('id' => 123,
'artist' => 'The Military Wives',
'title' => 'In My Dreams'));
$resultSet = new ResultSet();
$resultSet->setArrayObjectPrototype(new Album());
$resultSet->initialize(array($album));
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('select')
->with(array('id' => 123))
->will($this->returnValue($resultSet));
$albumTable = new AlbumTable($mockTableGateway);
$this->assertSame($album, $albumTable->getAlbum(123));
}
public function testCanDeleteAnAlbumByItsId() {
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('delete'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('delete')
->with(array('id' => 123));
$albumTable = new AlbumTable($mockTableGateway);
$albumTable->deleteAlbum(123);
}
public function testSaveAlbumWillInsertNewAlbumsIfTheyDontAlreadyHaveAnId() {
$albumData = array('artist' => 'The Military Wives', 'title' => 'In My Dreams');
$album = new Album();
$album->exchangeArray($albumData);
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('insert'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('insert')
->with($albumData);
$albumTable = new AlbumTable($mockTableGateway);
$albumTable->saveAlbum($album);
}
public function testSaveAlbumWillUpdateExistingAlbumsIfTheyAlreadyHaveAnId() {
$albumData = array('id' => 123, 'artist' => 'The Military Wives', 'title' => 'In My Dreams');
$album = new Album();
$album->exchangeArray($albumData);
$resultSet = new ResultSet();
$resultSet->setArrayObjectPrototype(new Album());
$resultSet->initialize(array($album));
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('select', 'update'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('select')
->with(array('id' => 123))
->will($this->returnValue($resultSet));
$mockTableGateway->expects($this->once())
->method('update')
->with(array('artist' => 'The Military Wives', 'title' => 'In My Dreams'), array('id' => 123));
$albumTable = new AlbumTable($mockTableGateway);
$albumTable->saveAlbum($album);
}
public function testExceptionIsThrownWhenGettingNonexistentAlbum() {
$resultSet = new ResultSet();
$resultSet->setArrayObjectPrototype(new Album());
$resultSet->initialize(array());
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
$mockTableGateway->expects($this->once())
->method('select')
->with(array('id' => 123))
->will($this->returnValue($resultSet));
$albumTable = new AlbumTable($mockTableGateway);
try {
$albumTable->getAlbum(123);
} catch (\Exception $e) {
$this->assertSame('Could not find row 123', $e->getMessage());
return;
}
$this->fail('Expected exception was not thrown');
}
public function testGetAlbumTableReturnsAnInstanceOfAlbumTable() {
$this->assertInstanceOf('Album\Model\AlbumTable', $this->controller->getAlbumTable());
}
}
?>
getAlbumTable is in AlbumController as follows:
<?php
namespace Album\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class AlbumController extends AbstractActionController {
protected $albumTable;
public function indexAction() {
return new ViewModel(array(
'albums' => $this->getAlbumTable()->fetchAll(),
));
}
public function addAction() {
}
public function editAction() {
}
public function deleteAction() {
}
public function getAlbumTable() {
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable');
}
return $this->albumTable;
}
}
?>
And AlbumTable is:
<?php
namespace Album\Model;
use Zend\Db\TableGateway\TableGateway;
class AlbumTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getAlbum($id)
{
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function saveAlbum(Album $album)
{
$data = array(
'artist' => $album->artist,
'title' => $album->title,
);
$id = (int)$album->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getAlbum($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
public function deleteAlbum($id)
{
$this->tableGateway->delete(array('id' => $id));
}
}
?>
Module.php is:
<?php
namespace Album;
class Module
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
// Add this method:
public function getServiceConfig()
{
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) {
$tableGateway = $sm->get('AlbumTableGateway');
$table = new AlbumTable($tableGateway);
return $table;
},
'AlbumTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
?>
module.config.php is:
<?php
return array(
'controllers' => array(
'invokables' => array(
'Album\Controller\Album' => 'Album\Controller\AlbumController',
),
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'album' => array(
'type' => 'segment',
'options' => array(
'route' => '/album[/:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Album\Controller\Album',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
?>
application.config.php is:
<?php
return array(
'modules' => array(
'Application',
'Album', // <-- Add this line
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'./module',
'./vendor',
),
),
);
?>
Bootstrap.php:
<?php
namespace AlbumTest;//Change this namespace for your test
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\Stdlib\ArrayUtils;
use RuntimeException;
error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);
class Bootstrap
{
protected static $serviceManager;
protected static $config;
protected static $bootstrap;
public static function init()
{
// Load the user-defined test configuration file, if it exists; otherwise, load
if (is_readable(__DIR__ . '/TestConfig.php')) {
$testConfig = include __DIR__ . '/TestConfig.php';
} else {
$testConfig = include __DIR__ . '/TestConfig.php.dist';
}
$zf2ModulePaths = array();
if (isset($testConfig['module_listener_options']['module_paths'])) {
$modulePaths = $testConfig['module_listener_options']['module_paths'];
foreach ($modulePaths as $modulePath) {
if (($path = static::findParentPath($modulePath)) ) {
$zf2ModulePaths[] = $path;
}
}
}
$zf2ModulePaths = implode(PATH_SEPARATOR, $zf2ModulePaths) . PATH_SEPARATOR;
$zf2ModulePaths .= getenv('ZF2_MODULES_TEST_PATHS') ?: (defined('ZF2_MODULES_TEST_PATHS') ? ZF2_MODULES_TEST_PATHS : '');
static::initAutoloader();
// use ModuleManager to load this module and it's dependencies
$baseConfig = array(
'module_listener_options' => array(
'module_paths' => explode(PATH_SEPARATOR, $zf2ModulePaths),
),
);
$config = ArrayUtils::merge($baseConfig, $testConfig);
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $config);
$serviceManager->get('ModuleManager')->loadModules();
static::$serviceManager = $serviceManager;
static::$config = $config;
}
public static function getServiceManager()
{
return static::$serviceManager;
}
public static function getConfig()
{
return static::$config;
}
protected static function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (is_readable($vendorPath . '/autoload.php')) {
$loader = include $vendorPath . '/autoload.php';
} else {
$zf2Path = getenv('ZF2_PATH') ?: (defined('ZF2_PATH') ? ZF2_PATH : (is_dir($vendorPath . '/ZF2/library') ? $vendorPath . '/ZF2/library' : false));
if (!$zf2Path) {
throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.');
}
include $zf2Path . '/Zend/Loader/AutoloaderFactory.php';
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
),
),
));
}
protected static function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) return false;
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
Bootstrap::init();
?>
TestConfig.php.dist:
<?php
return array(
'modules' => array(
'Album',
),
'module_listener_options' => array(
'config_glob_paths' => array(
'../../../config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'module',
'vendor',
),
),
);
?>
And, finally, phpunit.xml.dist:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="Bootstrap.php">
<testsuites>
<testsuite name="zf2tutorial">
<directory>./AlbumTest</directory>
</testsuite>
</testsuites>
</phpunit>
You are correct to assume that you need to add some setup code and comparing it with AlbumControllerTest.php is a good idea.
As the error message states, the issue is that the AlbumTableTest object does not have controller property. We therefore need to add the property using:
$protected controller
and initialise it using:
$this->controller = new AlbumController()
In addition, we need to initialise the serviceManager property and set its serviceLocator property so that the following calls make sense in the getAlbumTable method of the controller:
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable');
To summarise, you did the right thing but you didn't need all the additional code. You can get away with:
use AlbumTest\Bootstrap;
use Album\Controller\AlbumController;
use Album\Model\AlbumTable;
use Album\Model\Album;
use Zend\Db\ResultSet\ResultSet;
use PHPUnit_Framework_TestCase;
class AlbumTableTest extends PHPUnit_Framework_TestCase
{
protected $controller;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new AlbumController();
$this->controller->setServiceLocator($serviceManager);
}
....
at the start of AlbumTableTest.php
While I am not certain that this is the appropriate fix at this point, I resolved the error. I looked into AlbumControllerTest as opposed to AlbumTableTest. It has a setup method that creates the controller class. I copied the setup code along with the appropriate use statements and variable declarations and, for the moment, I am on to the next issue...
I'm still interested in a better answer!
What I copied (added) includes:
use AlbumTest\Bootstrap;
use Album\Controller\AlbumController;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter;
use PHPUnit_Framework_TestCase;
class AlbumTableTest extends PHPUnit_Framework_TestCase {
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new AlbumController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'index'));
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
You forgot to add the new import statements in Module.php. The start of the file should look like this:
namespace Album;
use Album\Model\Album;
use Album\Model\AlbumTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module
{
//...
Easy to skip, hard to find, I've spent good 15 minutes on the same thing...

Categories