in ZF2 it was possible to configurate multiple adapters like this in the module.config.php:
'db' => array(
'adapters'=>array(
'db1' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'zf2',
'password' => 'zf2test',
),
'db2' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf1;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'zf1',
'password' => 'zf1test',
),
)
),
In the controller factory I could get them via the ServiceManager:
class AlbumControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$albumTable = $serviceLocator->getServiceLocator()->get('Album\Model\AlbumTable');
$db1Adapter = $serviceLocator->getServiceLocator()->get('db1');
$db2Adapter = $serviceLocator->getServiceLocator()->get('db2');
return new AlbumController($albumTable, $db1Adapter, $db2Adapter);
}
}
Now I'm trying to do the same in Zend Framework 3 - but this nested array configuration doesn't work:
Fatal error: Uncaught exception 'Zend\Db\Adapter\Exception\InvalidArgumentException' with message 'createDriver expects a "driver" key to be present inside the parameters' in /var/www/USER/teckert/zf3/vendor/zendframework/zend-db/src/Adapter/Adapter.php:262
I think that in ZF 2 the adapters key are already handled when the dbAdapter is trying to create the driver - but this is not happening in ZF 3.
Any hints are warmly welcomed...
The manual of the zend-db with the adapters section wasn't clear enough for me
EDIT
According to this doc I've added the following snippet to the global config file:
'service_manager' => [
'abstract_factories' => [
\Zend\Db\Adapter\AdapterAbstractServiceFactory::class,
],
],
While trying to get the dbAdapter with $container->get('db1') in my AlbumTableFactory I get this error:
Unable to resolve service "db1 to a factory; are you certain you provided it during configuration?
Make sure you've added Zend\Db\Adapter\AdapterAbstractServiceFactory to the abstract_factories array of the ServiceManager configuration.
This abstract factory is responsible for instantiating the individual database adapters. Also, check whether your Album\Model\AlbumTable factory retrieves the database adapter with the correct name.
Okay I finally resolved the problem.
As mentioned by #Pieter I needed the following array content in my config.php:
'service_manager' => [
'abstract_factories' => [
\Zend\Db\Adapter\AdapterAbstractServiceFactory::class,
],
],
Additionally I had to change the process my classes and dependent factories are talking to each other.
AlbumControllerFactory is called and get's the dependent AlbumTable service ($container->get(AlbumTable::class);) which then triggers the AlbumTableFactory
AlbumTableFactory then is preparing the constructor injection for the AlbumTable with $tableGateway = $container->get(AlbumTableGateway::class);
Now I combined the logic from the AlbumTableFactory and the AlbumTableGatewayFactory into one AbstractFactoryInterface (I removed the AlbumTableGatewayFactory completely)
// AlbumTableFactory implements AbstractFactoryInterface
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$dbAdapter = $container->get('db1');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
$tableGateway = new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
return new AlbumTable($tableGateway);
}
Related
I'm trying to register a bunch of custom DBAL types. When I run the migrations:diff I get the exception:
Fatal error: Class 'App\Persistence\Models\Types\Money' not found in D:\development\projects\project\vendor\doctrine\dbal\lib\Doctrine\DBAL\Types\Type.php on line 174
I've tried to do this either by registering it after all Doctrine settings and using a event subscriber:
class DoctrineCustomTypesEventSubscriber implements Subscriber {
public function getSubscribedEvents() {
return [Events::postConnect];
}
public function postConnect(ConnectionEventArgs $args) {
Type::addType('money', "App\Persistence\Models\Types\Money");
Type::addType('geopoint', "App\Persistence\Models\Types\Point");
Type::addType('geoarea', "App\Persistence\Models\Types\Area");
}
}
$doctrineCustomTypesSubscriber = new App\Persistence\DoctrineCustomTypesEventSubscriber();
$app['db.event_manager']->addEventSubscriber($doctrineCustomTypesSubscriber);
$app->register(new Dflydev\Provider\DoctrineOrm\DoctrineOrmServiceProvider, array(
'orm.proxies_dir' => $app['APP_ROOT_DIR'].'/app/persistence/proxies',
'orm.em.options' => array(
'mappings' => array(
array(
'type' => 'annotation',
'namespace' => 'App\Persistence\Models',
'path' => $app['APP_ROOT_DIR'].'/app/persistence/models',
'use_simple_annotation_reader' => false,
),
),
),
));
Update
Placing the registration before all orm settings doesn't work either:
use Doctrine\DBAL\Types\Type;
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
'db.options' => array('url' => $app['APP_DB_CONN_URL']),
));
Type::addType('money', "App\Persistence\Models\Types\Money");
Type::addType('geopoint', "App\Persistence\Models\Types\Point");
Type::addType('geoarea', "App\Persistence\Models\Types\Area");
What am I doing wrong here?
Also can you tell me where do I put these registrations:
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping(...)?
The type classes should exist and be registered in autoloader, so that they can be instantiated by FQCN associated with type.
Here is my config within my Application module.config.php:
'service_manager' => array(
'factories' => array(
'translator' => 'Zend\I18n\Translator\TranslatorServiceFactory',
'navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
'masterSlaveFeature' => function($sm){
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$slaveAdapter = clone $dbAdapter;
$feature = new Zend\Db\TableGateway\Feature\MasterSlaveFeature($slaveAdapter);
return $feature;
},
),
),
Here is the problem. As you can see I create a new MasterSlaveFeature Db Instance every time I call the servicemanager for the masterSlaveFeature service.
I was thinking that a solution could be using the service manager to handle the MasterSlaveFeature Db Instance but how do I pass the cloneddb adapter to the service manager for loading the instance with the adapter?
I was thinking about making a new service that handles loading the MasterSlaveFeature instance.
First of all you don't need to clone adapter.
Second, service manager uses shared instances by default, meaning consecutive call will return previously created instance, unless you specify in config instance is not shared.
As for what you want to achieve, you should define alias, eg db_slave_adapter and use that alias to fetch adapter:
'service_manager' => array(
'factories' => array(
'masterSlaveFeature' => function($sm){
$dbAdapter = $sm->get('db_slave_adapter');
$feature = new Zend\Db\TableGateway\Feature\MasterSlaveFeature($dbAdapter);
return $feature;
},
),
),
Define alias in your application level configuration:
'service_manager' => array(
'aliases' => array(
'db_slave_adapter' => 'Zend\Db\Adapter\Adapter'
),
),
when you will have real slave adapter, you will replace alias with adapter factory
I am a zf1 developer. I started zf2. I am creating a Authentication module. I created a Auth class as mentioned in the doc
<?php
namespace Application\Model;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Adapter\DbTable as AuthAdapter;
class Myauth implements AdapterInterface {
/**
* Sets username and password for authentication
*
* #return void
*/
public function __construct($username, $password) {
// Configure the instance with constructor parameters...
$authAdapter = new AuthAdapter($dbAdapter,
'users',
'username',
'password'
);
$authAdapter
->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password');
$result = $authAdapter->authenticate();
if (!$result->isValid()) {
// Authentication failed; print the reasons why
foreach ($result->getMessages() as $message) {
echo "$message\n";
}
} else {
// Authentication succeeded
// $result->getIdentity() === $username
}
}
}
Issue1 : How to get $dbAdapter here?
Issue2 : Is this correct way to create auth module?
I have a couple things to say:
1. About Database Adapter
This link shows you how to configure database adapter.
In config/autoload/global.php:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2tutorial;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',
),
),
);
In config/autoload/local.php:
return array(
'db' => array(
'username' => 'YOUR USERNAME HERE',
'password' => 'YOUR PASSWORD HERE',
),
)
Now, from ServiceLocatorAware classes, you can get Database Adapter as
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
2. About Creating Authentication
Dude, why reinvent the square wheel? As mentioned here, ZfcUser is built to be a foundation for a very large percentage for Zend Framework 2 applications.
Nearly anything is customizable as mentioned here. A lot of modules are available such as ScnSocialAuth which have dependancy on ZfcUser and are really awesome.
As in ZF 2, Models are not ServiceLocatorAware classes, so you cannot use the solution in Ojjwal Ojha's answer.
You can:
1. Get dbAdapter in your Controller by calling:
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
Pass dbAdapter to your Model when you create it:
$model = new Model($dbAdapter);
Write an init function in your Model.
I do not know how to configure Zend \ Log with Doctrine2. Only allows you to write directly to the database via a connection adapter or write to a file.
May be it's too late to answer this question but better late than never.
I've found a good post which explains how to create a basic SQL Logger for ZF2 and Doctrine.
The approach is pretty simple :
1. Creating Logger class : Create the following class in your Module/Application/Log folder :
<?php
namespace Application\Log;
use Zend\Log\Logger;
use Doctrine\DBAL\Logging\DebugStack;
class SqlLogger extends DebugStack
{
protected $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function stopQuery()
{
parent::stopQuery();
$q = $this->queries[$this->currentQuery];
$message = "Executed Query: " . print_r($q, true);
$this->logger->info($message);
}
}
The stopQuery() function which is called by Doctrine when it finiches sending the query to the database server, is
overrided so that it could write the current query to the Logger object.
2. Configuring the Logger : Add the following code in your config/autoload/global.php file, to make
the Logger accessible to the Service Manager using the name my_sql_logger :
'service_manager' => array(
'factories' => array(
'my_sql_logger' => function($sm) {
$log = new \Zend\Log\Logger();
$writer = new \Zend\Log\Writer\Stream('./data/logs/sql.log');
$log->addWriter($writer);
$sqllog = new \Application\Log\SqlLogger($log);
return $sqllog;
},
)
),
The Logger will write data to the data/logs/sql.log file. So, make sure that data/logs folder exists in your
application root directory.
3. Configuring Doctrine : Now you need to tell Doctrine to use the created Logger. Just add the following code
to your Doctrine configuration :
return array(
'doctrine' => array(
/*--------Add this code------------*/
'sql_logger_collector' => array(
'orm_default' => array(
'sql_logger' => 'my_sql_logger',
),
),
/*---------------------------------*/
'connection' => array(
'orm_default' => array(
'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
'params' => array(
'host' => 'localhost',
'port' => '3306',
'user' => 'username',
'password' => 'password',
'dbname' => 'dbname',
),
),
),
),
);
With the above configuration of Zend\Log and Doctrine2, you'll get all the query data logged in the
data/log/sql.log file.
Please see this Sql Logger for ZF2 and Doctrine for more details.
First ZF2 application, getting there, but I think still missing a think or two when it comes to dependency injection and the ServiceManager.
I have a particular problem at the moment with a new database gateway class I'm writing. I won't to inject a database adapter, so I've implemented AdapterAwareInterface. But the setDbAdapter method is never called in my class. I'm wondering if someone would be so kind as to look at my code and suggest what might be going wrong (or what I'm missing!).
So, here is the class in which I implement AdapterAwareInterface.
<?php
namespace Foo\Database;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
use Zend\Log\LoggerAwareInterface;
use Zend\Log\LoggerInterface;
class Gateway implements AdapterAwareInterface, LoggerAwareInterface
{
protected $logger = NULL;
protected $db = NULL;
public function setDbAdapter(Adapter $adapter)
{
$this->db = $adapter;
}
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
This is an extract from my module file showing how I configure my service manager:
public function getServiceConfig()
{
return array(
'factories' => array(
....
),
'invokables' => array(
'FooDatabaseGateway' => 'Foo\Database\Gateway',
),
'abstract_factories' => array(
'AbstractFeedParserFactory' => 'Bookmakers\Odds\Feeds\AbstractFeedParserFactory',
),
);
}
This is how I'm testing:
gateway = $this->getServiceLocator()->get('FooDatabaseGateway');
And this is part of my global config:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=kickoff_manager;host=localhost',
'username' => '****',
'password' => '****',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
Many thanks for any help you can provide.
:wq
OK a fresh pair of eyes on this problem this morning. I think this is the write answer.. At least that is to say its working for me. If anyone wants to suggest an entirely different of better approach, then please do so :-).
So the bit is was missing was to use an initializer in my service manager config to call the setDbAdapter function on any class instances that implement AdapterAwareInterface. So in the array I return from getServiceConfig in my Module.php file, I have added the following entry:
public function getServiceConfig() {
return array(
'initializers' => array(
'db' => function($service, $sm)
{
if ($service instanceof AdapterAwareInterface)
{
$service->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
}
}....
I think what I'm missing while learning ZF2 is that there are a lot of building blocks to work with, but you've got to put a lot of them together yourself.
Things are looking good and I'm enjoying the Framework, but there is a lot to learn, and I'm still not convinced by using Server Manager injection rather than good old constructor injection!
:wq