Apigility: Send Error code 404 module.php in bootstrap - php

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() ...
}

Related

Access models from other modules zf3

I'm trying to configure ZF3 after a few projects with ZF2, but cannot access a model which is in another module.
I have 3 tables in my database, and I defined the gateways as follow in my Application\src\Module.php
public function getServiceConfig()
{
return [
'factories' => [
Model\ChatTable::class => function($container) {
$tableGateway = $container->get(Model\ChatTableGateway::class);
return new Model\ChatTable($tableGateway);
},
Model\ChatTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Chat());
return new TableGateway('chat', $dbAdapter, null, $resultSetPrototype);
},
Model\OperadorTable::class => function($container) {
$tableGateway = $container->get(Model\OperadorTableGateway::class);
return new Model\OperadorTable($tableGateway);
},
Model\OperadorTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Operador());
return new TableGateway('operador', $dbAdapter, null, $resultSetPrototype);
},
Model\ConversacionTable::class => function($container) {
$tableGateway = $container->get(Model\ConversacionTableGateway::class);
return new Model\ConversacionTable($tableGateway);
},
Model\ConversacionTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Conversacion());
return new TableGateway('conversacion', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
public function getControllerConfig()
{
return [
'factories' => [
Controller\IndexController::class => function($container) {
return new Controller\IndexController(
$container->get(Model\ChatTable::class),
$container->get(Model\OperadorTable::class),
$container->get(Model\ConversacionTable::class)
);
},
],
];
}
Then, I can use them in my Application\Controller\IndexController as follow:
namespace Application\Controller;
use Application\Model\ChatTable;
use Application\Model\OperadorTable;
use Application\Model\ConversacionTable;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
private $chatTable;
private $operadorTable;
private $conversacionTable;
//TABLAS
public function __construct(
ChatTable $chatTable,
OperadorTable $operadorTable,
ConversacionTable $conversacionTable
){
$this->chatTable = $chatTable;
$this->operadorTable = $operadorTable;
$this->conversacionTable = $conversacionTable;
}
//VIEW ACTIONS
public function indexAction()
{
return new ViewModel([
'chats' => $this->chatTable->fetchAll(),
'operadores' => $this->operadorTable->fetchAll(),
'conversaciones' => $this->conversacionTable->fetchAll(),
]);
}
}
This works perfectly. My question is ¿what if, for example, I prefer to put the Chat and ChatTable model in another module, for example, under Panel\Model\ChatTable and acces them from my Application module? ¿what changes should I make?
In ZF2 this was easy using Service Locator. I have found a question suggesting the use of service factories, but, at least in my case, does not solve the idea of using at the same time models within the module and from outside the module.
Thanks in advance. Bye!
Well, I found the answer after a few attempts.
For example, if you prefer to use Panel\Model\Chat and Panel\Model\ChatTable instead of Application\Model\Chat and Application\Model\ChatTable, the configuration should be the following.
In your Application\src\Module.php:
public function getServiceConfig()
{
return [
'factories' => [
\Panel\Model\ChatTable::class => function($container) {
$tableGateway = $container->get(\Panel\Model\ChatTableGateway::class);
return new \Panel\Model\ChatTable($tableGateway);
},
\Panel\Model\ChatTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new \Panel\Model\Chat());
return new TableGateway('chat', $dbAdapter, null, $resultSetPrototype);
},
],
//rest of stuff
];
}
public function getControllerConfig()
{
return [
'factories' => [
Controller\IndexController::class => function($container) {
return new Controller\IndexController(
$container->get(\Panel\Model\ChatTable::class),
//rest of stuff
);
},
],
];
}
Then, in your Application\Controller\IndexController:
use Panel\Model\ChatTable;
//rest of stuff
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
private $chatTable;
//rest of stuff
//TABLAS
public function __construct(
ChatTable $chatTable,
//rest of stuff
){
$this->chatTable = $chatTable;
//rest of stuff
}
//VIEW ACTIONS
public function indexAction()
{
return new ViewModel([
'chats' => $this->chatTable->fetchAll(),
//rest of stuff
]);
}
}
I just started my first zf3 app weeks ago, and I had the same problem with you.
What did I do was, define the driver of the origin module inside the current module.config.php
'doctrine' => [
'driver' => [
__NAMESPACE__ . '_driver' => [
'class' => AnnotationDriver::class,
'cache' => 'array',
'paths' => [__DIR__ . '/../src/Model']
],
'Admin_driver' => [
'class' => AnnotationDriver::class,
'cache' => 'array',
'paths' => [__DIR__ . '/../../Admin/src/Model']
],
'orm_default' => [
'drivers' => [
__NAMESPACE__ . '\Model' => __NAMESPACE__ . '_driver',
'Admin\Model' => 'Admin_driver'
]
]
]
],
As you can see, I defined the Admin_driver and loaded it into the orm_default .

How to use Zend 2 save handler DbTableGateway?

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

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.

Populate selectable field from DB in Zend 2 Album example

I'm starting programming with Zend and I am using the default Album example they provide as a "getting started" guide.
I would like to have a select drop down field in the form, but I can't find an easy way of doing it like the code is right now
This is how I'm doing it without consulting DB from my UsersForm.php
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'name' => 'level',
'options' => array(
'label' => 'User level',
'value_options' => array(
'1' => 'admin',
'2' => 'boss',
'3' => 'assistent',
'4' => 'client',
),
),
));
UPDATE
Ok, so following that tutorial using TableGateway I managed to have a selectable but is grabbing data from the 'project' table because the rest of my fields need that table, but I need that selectable to get from 'user' table.
My Module.php looks like this:
public function getServiceConfig()
{
return array(
'invokables' => array(),
'factories' => array(
'Project\Model\ProjectTable' => function($sm) {
$projectTableGateway = $sm->get('ProjectTableGateway');
$usersTableGateway = $sm->get('UsersTableGateway');
$table = new ProjectTable($projectTableGateway, $usersTableGateway);
return $table;
},
'project-model-selectable' => function($sm) {
$tableGateway = $sm->get('selecttable-gateway');
$table = new SelectTable($projectTableGateway, $usersTableGateway);
return $table;
},
'ProjectTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Project());
return new TableGateway('project', $dbAdapter, null, $resultSetPrototype);
},
'UsersTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Users());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
'selecttable-gateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new SelectOption());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
And my getProject function in ProjectTable.php:
public function getProject($id)
{
$id = (int) $id;
$rowset = $this->projectTableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
My addAction in ProjectController.php
public function addAction()
{
$tableGateway = $this->getServiceLocator()->get('Project\Model\ProjectTable');
$form = new ProjectForm($tableGateway);
$form->get('submit')->setValue('Nuevo');
$request = $this->getRequest();
if ($request->isPost()) {
$project = new ProjectForm($tableGateway);
$form->setInputFilter($project->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$project->exchangeArray($form->getData());
$this->getProjectTable()->saveProject($project);
// Redirect to list of projects
return $this->redirect()->toRoute('project');
}
}
return array('form' => $form);
}
Thanks
public function getProject($id)
{
$id = (int) $id;
$rowset = $this->projectTableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}

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