Using ZF2 ServiceManager to handle the MasterSlaveFeature - php

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

Related

Zend2 Dependency injection factory to service

Im trying to implement Dependency injection Using the Zend2 service manager. I want to inject a PDO instance into Service (Im not using the Zend Db).
Im following the tutorial here: https://framework.zend.com/manual/2.4/en/in-depth-guide/services-and-servicemanager.html
I have it working for another service, but the when injecting the PDO instance Im getting this error:
Catchable fatal error: Argument 1 passed to Application\Service\DataService::__construct() must be an instance of Application\Service\DbConnectorService, none given, called in /srv/www/shared-apps/approot/apps-dev/ktrist/SBSDash/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php on line 1077 and defined in /srv/www/shared-apps/approot/apps-dev/ktrist/SBSDash/module/Application/src/Application/Service/DataService.php on line 24
From the tutorial this seems to be related to my invokeables in the module.config. But I cannot work out what the issue is.
Any advice is appreciated.
Here is my code:
DataService:
class DataService {
protected $dbConnectorService;
public function __construct(DbConnectorService $dbConnectorService) {
$this->dbConnectorService = $dbConnectorService;
}
......
DataServiceFactory:
namespace Application\Factory;
use Application\Service\DataService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class DataServiceFactory implements FactoryInterface {
function createService(ServiceLocatorInterface $serviceLocator) {
$realServiceLocator = $serviceLocator->getServiceLocator();
$dbService = $realServiceLocator->get('Application\Service\DbConnectorService');
return new DataService($dbService);
}
}
Module.Config:
'controllers' => array(
'factories' => array(
'Application\Controller\Index' => 'Application\Factory\IndexControllerFactory',
'Application\Service\DataService' => 'Application\Factory\DataServiceFactory',
)
),
'service_manager' => array(
'invokables' => array(
'Application\Service\DataServiceInterface' => 'Application\Service\DataService',
'Application\Service\DbConnectorService' => 'Application\Service\DbConnectorService',
)
),
You are trying to create the service as an 'invokable' class. ZF2 will treat this service as a class without dependencies (and not create it using the factory).
You should update your service configuration to register under the 'factories' key, pointing to the factory class name.
'service_manager' => [
'invokables' => [
'Application\\Service\\DbConnectorService'
=> 'Application\\Service\\DbConnectorService',
],
'factories' => [
'Application\\Service\\DataServiceInterface'
=> 'Application\\Factory\\DataServiceFactory',
],
],
You will need to make the same change for DbConnectorService if that also has a factory.

Can't register custom DBAL types

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.

ZF3 multiple database adapters

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);
}

ZF2 Zend\Log + Doctrine2

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.

zf2 ServiceManagerAwareInterface in Fieldset

In my Zend\Form\Fieldset AddressFieldset it needs a Zend\Db\TableGateway\AbstractTableGateway BundeslandTable for a \Zend\Form\Element\Select().
So i implement \Zend\ServiceManager\ServiceManagerAwareInterface in this AddressFieldset and use the init() instead __construct().
And in module.config.php (not only in 'form_elements' tested, also in 'service_manager')
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($sm) {
$addressFieldset = new MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($sm);
return $addressFieldset;
}
),
),
In a \Zend\Form\Form's init():
$this->add(array(
'type' => 'MyFormway\Form\Fieldset\Address',
'name' => 'address',
));
this throws an error:
Zend\Form\FormElementManager::get was unable to fetch or create an instance for MyFormway\Form\Fieldset\Address
Why is zend unable to fetch an instance of this Fieldset?
edit-----------------------
'form_elements' => array(
'factories' => array(
'MyFormway\Form\Fieldset\Address' => function($formElementManager) {
die('inna form_elements config');
$addressFieldset = new \MyFormway\Form\Fieldset\AddressFieldset();
$addressFieldset->setServiceManager($formElementManager->getServiceLocator());
return $addressFieldset;
}
),
),
Because i have the Zend\Form\FormElementManager i fetch the ServiceLocator ...perhaps dont needed, because all XxxManager extends the Zend\ServiceManager\AbstractPluginManager and this extends ServiceManager.
In FormElementManager and also in AbstractPluginManager are no method getServiceManager().
But my problem: the die() is not called plus the error above. Is it a bug? ...i stand for a big wall :(
edit-----------------------
It works for a Form but not for a Fieldset!!!
Can you do a quick check if the \Invokable is called at all? Some professional die()-debugging will suffice.
Other than that a potential error source would be your injection of the ServiceManager. In the code you provide you're not actually injecting the ServiceLocator but rather the FormElementManager.
$addressFieldset->setServiceManager($sm->getServiceManager());
Doing it this way is considered Bad-Practice tho. You should only inject the stuff that you actually do need. Given you're injecting the whole manager i assume you're either working with Doctrine or you'll need access to some DB-Data. Do it like this:
'Foo' => function ($formElementManager) {
$sl = $formElementManager->getServiceManager();
$fs = new FooFieldset();
$fs->setDbDependency(
$sl->get('MyDbDependency')
);
return $fs;
}
Last little note: when you're adding a Fieldset, you don't need to add 'name' => 'foo' within the $this->add(), since the name of the fieldset will be defined via the Fieldset __construct('name').

Categories