I'm creating abstract models for managing database entities - I already have EntityAbstract, EntitySetAbstract and a ManagerAbstract models. In my ManagerAbstract model I need a Zend/Db/Adapter instance in order to create a Zend\Db\TableGateway.
How could I pull the main instance of the adapter to my ManagerAbstract? In ZF1 I could have achieved this with Zend_Registry.
If this isn't the right way of doing things in ZF2, I would love to hear the correct way to this kind of things.
Thanks!
Use the Dependency Injection Container, Zend\Di. The ZfcUser project does this if you want to poke around in some working code.
Alternatively, the basic approach is something like this (code untested!):
Firstly: configure the DI to inject the database connection information:
config/autoload/local.config.php:
<?php
return array(
'di' => array(
'instance' => array(
'Zend\Db\Adapter\Adapter' => array(
'parameters' => array(
'driver' => 'Zend\Db\Adapter\Driver\Pdo\Pdo',
),
),
'Zend\Db\Adapter\Driver\Pdo\Pdo' => array(
'parameters' => array(
'connection' => 'Zend\Db\Adapter\Driver\Pdo\Connection',
),
),
'Zend\Db\Adapter\Driver\Pdo\Connection' => array(
'parameters' => array(
'connectionInfo' => array(
'dsn' => "mysql:dbname=mydatabasename;host=localhost",
'username' => 'myusername',
'password' => 'mypassword',
'driver_options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''),
),
),
),
),
),
);
Secondly, within your module's module.config.php file, inject the adapter into the mapper:
module/My/config/module.config.php:
<?php
return array(
'di' => array(
// some config info...
'My\Model\ManagerAbstract' => array(
'parameters' => array(
'adapter' => 'Zend\Db\Adapter\Adapter',
),
),
// more config info...
)
);
Finally, ensure that your ManagerAbstract class can receive the injection:
module/My/src/My/Model/ManagerAbstract.php:
<?php
namespace My\Model;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
abstract class ManagerAbstract implements AdapterAwareInterface
{
/**
* #var Zend\Db\Adapter\Adapter
*/
protected $adapter;
// some code
public function setDbAdapter(Adapter $adapter)
{
$this->adapter = $adapter;
}
// some more code
}
Note that to use any sub-class, you need to retrieve it via the DIC or inject the mapper into the service and then inject the service into the controller (or other service) where you want to use it.
Related
Today I bought a book to learn the Zend Framework 2. Having started with the skeleton Application & skeleton module, I made good progress, until database interaction started. Now every time I want to do something with the database, I get the following exception:
Zend\ServiceManager\Exception\ServiceNotFoundException
File: /Applications/AMPPS/www/myproject/ZendSkeletonApplication/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:529
Message:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Zend\Db\Adapter\Adapter
Possible reasons for this could be incorrect database details, but having used the same credentials for other projects, I know they are correct (local development).
My global.php looks like this – I can't see any errors:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=mydatabasename;host=localhost',
'username' => 'root',
'password' => 'mysql',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
),
),
)
);
Following the stacktrace, the error must be in the beginning of this method:
protected function createUser(array $data)
{
$sm = $this->getServiceLocator();
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new \Zend\Db\ResultSet\ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new \Users\Model\User);
$tableGateway = new \Zend\Db\TableGateway\TableGateway('user', $dbAdapter, null, $resultSetPrototype);
$user = new User();
$user->exchangeArray($data);
$userTable = new UserTable($tableGateway);
$userTable->saveUser($user);
return true;
}
But again, I can't see any error here, which leaves me a bit puzzled. I guess the error is more than just a typo.
The class that method is located in uses the following Zend Framework 2 components (besides self written ones):
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
Do I need to add something here, maybe to be able to use the getServiceLocator() method? I don't know there that method is located.
Do you have anymore ideas what could cause this exception? I am using Zend Framework 2.3.2
You have put the service_manager key as a subset to db. This is wrong.
The service_manager key needs to be top level.
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=mydatabasename;host=localhost',
'username' => 'root',
'password' => 'mysql',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
)
);
The zend file application.config.php offers some way to cache the config, which I find very nice for a production system:
return array(
'modules' => array(
'Application',
),
'module_listener_options' => array(
'module_paths' => array(
'./module',
'./vendor'
),
'config_glob_paths' => array('config/autoload/{,*.}{global,local}.php'),
'config_cache_enabled' => true,
'config_cache_key' => md5('config'),
'module_map_cache_enabled' => true,
'module_map_cache_key' => md5('module_map'),
'cache_dir' => './data/cache',
),
);
However, activating that leads immediately to errors like
Fatal error: Call to undefined method Closure::__set_state()
This has to do with factories written as closures, like these:
'service_manager' => array(
'factories' => array(
'auth.service' => function($sm) {
/* hic sunt ponies */
},
),
),
Unfortunately, the issues only tell me why this error happens, but not how to resolve it.
How can I rework this and similar factories so the cache will work with them?
Rework your factory closures to factory classes.
Config
'service_manager' => array(
'factories' => array(
'auth.service' => \Fully\Qualified\NS\AuthFactory::class,
),
),
Factory
namespace Fully\Qualified\NS;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class AuthFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
// create your object and set dependencies
return $object
}
}
Besides this approach making caching possible, another advantage is that PHP will parse your config faster since it doesn't have to create a Closure class on each request for each anonymous function.
ZF2 documentation says following on defult services documentation;
InputFilterManager, mapping to Zend\Mvc\Service\InputFilterManagerFactory. This creates and returns
an instance of Zend\InputFilter\InputFilterPluginManager, which can be
used to manage and persist input filter instances.
I have a custom zf2 inputfilter class and i'm adding filters and validators inside init() method like following;
namespace Application\Filter;
use Zend\InputFilter\InputFilter;
class GlassFilter extends InputFilter
{
public function init()
{
$this->add(array(
'name' => 'glassname',
'required' => true,
'filters' => array(
array('name' => 'StringToUpper'),
),
'validators' => array(
array( 'name' => 'StringLength', 'options' => array('min' => 3),
),
));
}
Also i added following key to my module.config.php
'filters' => array(
'invokables' => array(
'glassfilter' => '\Application\Filter\GlassFilter',
),
),
My question is, how can i construct my GlassFilter using InputFilterManager? Is this a correct approach? I found this thread but i want to understand relation between custom InputFilters and InputFilterManager.
Ok, after spending 3 bloody hours (thanks to incredible(!) documentation) I figured it out. I'm writing my solution as an answer, hopefully it will help others who want to write their custom inputfilters.
You should register your custom inputfilter in module.config.php by input_filters top key, not filter, filters, filter_manger, filtermanager etc..
Extend default Zend\InputFilter\InputFilter when writing your own GlassFilter.
Write your filters inside the init() method of GlassFilter, not in the __constructor(). It will be called automatically after construction.
Then get it anywhere via inputfiltermanager, not servicemanager directly.
Config example:
'input_filters' => array(
'invokables' => array(
'glassfilter' => '\Application\Filter\GlassFilter',
),
),
Usage example:
$glassfilter = $serviceLocator->get('InputFilterManager')->get('glassfilter');
As in title, I'm struggling to access DBAdapter inside Router. Implementing ServiceLocatorAwareInterface isn't much help (ZF2 does not inject anything). Declaring it as a service in module with custom factory is not an option either, as it extends Http/Parts router and requires configuration parameters passed depending on a route (I don't want to hard-code them)
What I've already tried:
module.config.php:
(...)
'router' => array(
'routes' => array(
'adm' => array(
'type' => 'Custom\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/admin[/:language[/:controller[/:action[/:params]]]]',
'constraints' => array(
'language' => '(pl|en)',
'controller' => "[a-zA-Z0-9_\-]*",
'action' => "[a-zA-Z0-9_\-]*",
'params' => "(.*)",
),
'defaults' => array( ... ),
),
'may_terminate' => true,
),
),
),
'service_manager' => array(
(...)
'invokables' => array(
'Custom\Mvc\Router\Http\Segment' => 'Custom\Mvc\Router\Http\Segment',
),
),
(...)
As of now, Custom\Mvc\Router\Http\Segment is just a copy of Zend\Mvc\Router\Http\Segment, with added interfaces ServiceLocatorAwareInterface, AdapterAwareInterface and respective methods in the similar fashion:
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
var_dump($serviceLocator);
exit();
}
It never enters the setServiceLocator method, only RouteInterface::factory(), which then calls constructor.
Setting up a factory didn't help either, again - the code is not executed. Same behavior after moving the 'invocables' or factory to application config.
Currently using Zend Framework 2 RC1
It would have been easier if you would have gisted us som code.. :)
My recommendation would either be to use a factory to instansiate your custom router or set it as an invokable class (Requires you to implement ServiceLocatorAwareInterface so you can set it up in the router)
How can I easily implement queries in Zend framework?
Check this document:
Zend Framework Database Quick Start (PDF)
You can use doctrine2
Doctrine project.
There is a module compatibile with ZF3
DoctrineModule.
You can use QueryBuilder that brings creation of query to object manipulation.
You can use the Zend Db Adapter object like so:
$sql = 'SELECT * FROM bugs WHERE bug_id = ?';
$result = $db->fetchAll($sql, 2);
Use Zend_Db and just create a $db Object using Zend_Db Factory Method, and then create SQL Statements using Zend_Db_Select Class and pass the $select SQL Statement to the fetch* (fetchRow, fetchAll...) Methods.
1.Config:
config/autoload/dbAdapter.local.php
<?php
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=name;host=localhost',
'username' => 'root',
'password' => 'root',
),
'service_manager' => array(
'abstract_factories' => array(
'Zend\Db\Adapter\AdapterAbstractServiceFactory',
),
),
);
Implementation:
public function testAction()
{
$username = 'user';
$sql = "SELECT email FROM users WHERE username = ?";
$statement = $this->getDbAdapter()->createStatement($sql, array($username));
$result = $statement->execute()->current();
}
protected function getDbAdapter()
{
if($this->dbAdapter == null) {
$this->dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
}
return $this->dbAdapter;
}
Zend framework has abstract_factories ,it allows us to handle multiple DB queries :
Zend\Db\Adapter\AdapterAbstractServiceFactory
Need to set Service Manager :
'service_manager' => array(
'abstract_factories' => array(
'Zend\Db\Adapter\AdapterAbstractServiceFactory',
),
),
Configure adapters in config/autoload/local.php
db' => array(
'adapters' => array(
'database1' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=userDB;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
etc...
),
),
Configure adapters in config/autoload/global.php
return array(
'db' => array(
'adapters' => array(
'database1' => array(
'username' => 'root',
'password' => '',
),
),
),
);
Call adapters
$dbmanager->get('database1');
Use in Model
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Adapter\Adapter;
class UserTable extends AbstractTableGateway
{
protected $table ='user';
public function __invoke(Adapter $adapter)
{
$this->adapter = $adapter;
$this->initialize();
}
public function fetchAll()
{
$resultSet = $this->select();
return $resultSet->toArray();
}
}