I have a new module for which I'm writing tests.
The module contains a class which implements ServiceLocatorAwareInterface because it needs to create other objects using the DI container. Everything works fine when running in the skeleton app, but when running module tests i get the following:
Zend\Di\Exception\RuntimeException: Invalid instantiator of type "NULL" for "Zend\ServiceManager\ServiceLocatorInterface"
Researching a little bit I find that the DI container tries to create a new object of type "ServiceLocatorAwareInterface", which is of course wrong.
Digging a little more in the tests bootstrap, I find that adding the following line solves the problem, as in the DI now knows what class to instantiate for that interface.
$di->instanceManager()->addTypePreference('Zend\ServiceManager\ServiceLocatorInterface', new \Zend\ServiceManager\ServiceManager());
I'm not sure whether this is the best solution to the problem, as the ServiceManager passed by me is a dummy one.
Does anyone have any other ideas?
Yes, you are going in the right direction. (See the preferences documentation)
Not many people are using DI these days in favor of the ServiceManager (myself included), but if the config for DI remains similar to how it was during the ZF2 betas, you should be able to add a "preferences" section to your DI config like so:
'di' => array(
'instance' => array(
'preferences' => array(
'My_Interface' => 'My_Implementation_Or_Alias',
)
)
)
This configuration block can replace your call to $di->instanceManager()->addTypePreference()
Looking through the current docs, and mimicking the example here, you may have success defining the DI config as shown below using the ZF2 official release:
$di = new Zend\Di\Di;
$di->configure(new Zend\Di\Config(array(
'instance' => array(
'preferences' => array(
'My_Interface' => 'My_Implementation_Or_Alias',
)
)
)));
What you can do in this case is the following.
In your bootstrap for the module unit tests create a dummy application that is configured with a configuration that will only load the module you're testing.
...//other code before this for autoloading stuff
// DON'T RUN THE application in your tests, just init it
$application = Zend\Mvc\Application::init(include 'config/test.application.config.for.module.php');
$fullyConfigedManager = $application->getServiceManager();
TestCases::setServiceManager( $fullyConfigedManager );
After the application has been boostrapped you can pull the ServiceManager from the application directly. This service manager should be fully configured with any factories, invokables, and configuration from your module.
Related
I want to have a list of services and class names in my web application. I can use this command in console:
php bin/console debug:container
And I get something like this:
Symfony Container Public Services
=================================
-------------------------------------------------------------------- --------------------------------------------------------------------------------------------
Service ID Class name
-------------------------------------------------------------------- --------------------------------------------------------------------------------------------
annotation_reader Doctrine\Common\Annotations\CachedReader
app.annotations.softdelete.driver AppBundle\Doctrine\SoftDelete\Mapping\Driver\Annotation
app.annotations.translate.driver AppBundle\Doctrine\Mapping\Driver\TranslateDriver
app.be_auth_controller.listener AppBundle\EventListener\BeAuthControllerListener
I want to have this information on a web page using Symfony 3.
I created a service and I used:
$this->container->getServiceIds();
which returns something like:
[
0 => "service_container"
1 => "annotation_reader"
2 => "annotations.reader"
3 => "app.annotations.softdelete.driver"
4 => "app.annotations.translate.driver"
...
]
I don't know, how to get the class names.
In any cases works this:
get_class($this->container->get($this->container->getServiceIds()[1]))
But in some other cases it throws different exceptions.
To get full definition of given service you can use ContainerBuilder and Symfony cache file.
first create instance of ContainerBuilder:
$container = new ContainerBuilder();
then load cache file:
$cachedFile = $this->container->getParameter('debug.container.dump');
$loader = new XmlFileLoader($container, new FileLocator());
$loader->load($cachedFile);
now you can get full definition of your service like this:
$definition = $container->getDefinition('service_name')
$definition->getClass();
Your attempt with get_class is what came to mind as I was reading it, but whatever errors you are getting will come from improper fetching of those services. After all when you call $container->get(...), its at that moment instantiating those classes.
To be honest the output you are looking to replicate can be reproduced based on the method used by that command.
https://github.com/symfony/framework-bundle/blob/master/Command/ContainerDebugCommand.php
You'll just need to adapt it to work for you.
I've been working on converting an application of mine from CodeIgniter to Phalcon. I've noticed that [query heavy] requests that only took a maximum of 3 or 4 seconds using CI are taking up to 30 seconds to complete using Phalcon!
I've spent days trying to find a solution. I've tried using all the different means of access offered by the framework including submitting raw query strings directly to Phalcon's MySql PDO adapter.
I'm adding my database connection to the service container exactly like it is shown in Phalcon's INVO tutorial:
$di->set('db', function() use ($config) {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => $config->database->host,
"username" => $config->database->username,
"password" => $config->database->password,
"dbname" => $config->database->name
));
});
Using webgrind output I've been able to narrow the bottleneck down to the constructor in Phalcon's PDO adapter class (cost is in milliseconds):
I've already profiled and manually tested the relevant SQL to make sure the bottleneck isn't in the database (or my poorly constructed SQL!)
I've discovered the problem, which to me wasn't immediately apparent, so hopefully others will find this useful as well.
Every time a new query was started, the application was getting a new instance of the database adapter. The request which produced the webgrind output above had a total of 20 queries.
While re-reading Phalcon's documentation section on dependency injection I saw that services can optionally be added to the service container as a "shared" service, which effectively forces the object to act as a singleton, meaning that once one instance of the class is created, the application will simply pass that instance to any request instead of creating a new instance.
There are several methods to force a service to be added as a shared service, details of which can be found here in Phalcon's Documentation:
http://docs.phalconphp.com/en/latest/reference/di.html#shared-services
Changing the code posted above to be added as a shared service looks like this:
$di->setShared('db', function() use ($config) {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => $config->database->host,
"username" => $config->database->username,
"password" => $config->database->password,
"dbname" => $config->database->name
));
});
Here's what the webgrind output looks like for the same query referenced above, but after setting the database service to be added as a shared service (cost in milliseconds):
Notice that the invocation count is now 1 instead of 20, and the invocation cost dropped from 20 seconds down to 1 second!
I hope someone else finds this useful!
In most examples services are shared as de facto, not in the most apparent way though, but via:
$di->set('service', …, true);
The last bool argument passed to the set makes it shared and in 99.9% you'd want your DI services to be that way, otherwise similar things would happen as described by #the-notable, but because they are likely to be not as "impactful", they would be hard to trace down.
I am using Zend Framework 2.2.2 and Doctrine2 Module 0.7.0.
My goal is to have my functions related to a task in a standalone php-class. My current workflow is between two different programms: get data -> modify and store data -> send data.
This workflow needs functions from 3 ZF2 modules:
1. source software module
2. internal storage mechanism module
3. destination software module
The first task is successfull but when I move my data to the second module like this (shrinked to the main code):
use MTNewsletterEngine\Controller\NewsletterEngineController;
/** #var \MTNewsletterEngine\Controller\NewsletterEngineController */
private $_newsletterEngine;
$this->_newsletterEngine = new NewsletterEngineController();
[...]
$this->_newsletterEngine->addNewNewsletterRecipient($emailAddresses,1);
The second Controller has problems getting the service locator:
Fatal error: Call to a member function get() on a non-object in C:\xampp\htdocs\app\trunk\module\MTNewsletterEngine\src\MTNewsletterEngine\Controller\NewsletterEngineController.php on line 51
Line 51:
$em_mtnewsletterengine = $this->getServiceLocator()->get('doctrine.entitymanager.orm_mtnewsletterengine');
NewsletterEngineController is the Main Controller from Module MTNewsletterEngine.
I am confused as I don't know how to get this solved. Thanks.
Do not create a new instance of NewsletterEngineController by using the new keyword. The ServiceLocator will not be injected to the created object this way. Use Zend\ServiceManager to retrieve an instance of Zend\Mvc\Controller\ControllerManager (alias: "ControllerLoader" (ci)) and use the get method, to load the target controller. Zend\Mvc\Controller\ControllerManager extends the ServiceManager itself (because it is a plugin manager).
Check your module.config.php. The controller should be listed as an invokable controller.
Example:
'controllers' => array(
'invokables' => array(
'MTNewsletterEngine\Controller\NewsletterEngine' => 'MTNewsletterEngine\Controller\NewsletterEngineController'
),
),
$this->_newsletterEngine = $this->getServiceLocator()
->get('ControllerLoader')
->get('MTNewsletterEngine\Controller\NewsletterEngine');
For more information read the manual and try to understand the way the ServiceManager / ServiceLocator (which is part of Zend\Di) works.
Maybe you should also think about the structure of your application. I am not sure what you are trying to do there but it seems like you are mixing up different application layers.
Docs
http://framework.zend.com/manual/2.2/en/index.html#zend-di
http://framework.zend.com/manual/2.2/en/index.html#zend-servicemanager
I have zf2 DoctrineORMModule and DoctrienModule installed. I am trying to use the command tool to create mapping files and generate entities from these mapping files. (I know this isn't the preferred method, but this is how I'm going to do it. I have my reasons.)
I have a restful module configured and here is my Doctrine Configuration for this module.
// Doctrine config
'doctrine' => array(
'driver' => array(
'Restful_driver' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Restful/Entities')
),
'orm_default' => array(
'drivers' => array(
'Restful\Entities' => 'Restful_driver'
)
)
)
)
I first run
doctrine orm:convert-mapping xml /to/my/dest/path --from-database --force
This will create my xml file with all the table info. This part works fine and I can view the xml that it created. Next I try to run
doctrine orm:generate-entities /to/my/dest/path --generate-annotations
--generate-methods
I don't get any errors but also I don't get any results either. The output from the previous command is.
No Metadata Classes to process.
I have tried to read around but havn't found any articles that really solve my problem. Most say something about not having my annotations/mappings not configured correctly. But I can dump the entity manager through a controller.
var_dump($this->getServiceLocator()->get('doctrine.entitymanager.orm_default'));
What do I need to do to get this to generate entities from xml mappings? Any help is appreciated.
I had a similar problem with YAML files and posted my solution here. I'm sure this will work with xml files as well. Just try to add
$driverImpl = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array("YOUR_PATH_TO_XML_FILES"));
/* #var $em \Doctrine\ORM\EntityManager */
$em = $application->getServiceManager()->get('doctrine.entitymanager.orm_default');
$em->getConfiguration()->setMetadataDriverImpl($driverImpl);
to the doctrine-module.php.
Did you try using the doctrine-module script in vendor/bin? It should be all set up already to read your app's configs.
./doctrine-module orm:generate-entities ~/doctrine-entities
This "error" occured because you use Annotation Driver for generating. This driver uses your current exist entities and doesnt look at xml. If you want to create Entities from XML - you should send to DI in doctrine configuration section XML driver with need path.
I use another zf2 doctrine module and my DI config has another format, so i cant to send you properly DI example.
Can someone explain what a compilerpass is?
CompilerPass implementations are some kind of listeners that are executed after dependency injection container is built from configuration files and before it is saved as plain PHP in cache. They are used to build some structures that requires access to definitions from outer resources or need some programming that is not available in XML/YAML configuration. You can consider them as "final filters" that can modify entire DIC.
Let's consider a TwigBundle and its TwigEnvironmentPass. What it does is quite simple:
Fetch a reference to twig service (defined as <service id="twig" class="..." ...>)
Find all services that has been tagged with twig.extension tag. To do that you have work on complete DIC (built from XML configuration files) as those services might be defined in any bundle.
Build a custom code for service creation method.
As a final result the following code will be generated:
protected function getTwigService()
{
$this->services['twig'] = $instance = new \Twig_Environment($this->get('twig.loader'), ...);
// THIS HAS BEEN ADDED THANKS TO THE TwigEnvironmentPass:
$instance->addExtension(new \Symfony\Bundle\SecurityBundle\Twig\Extension\SecurityExtension($this->get('security.context')));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\TransExtension($this->get('translator')));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\TemplatingExtension($this));
$instance->addExtension(new \Symfony\Bundle\TwigBundle\Extension\FormExtension(array(0 => 'TwigBundle::form.html.twig', 1 => 'SiteBundle::widgets.html.twig')));
$instance->addExtension(new \MyProject\SiteBundle\Twig\Extension\MyVeryOwnExtensionToTwig($this));
return $instance;
}