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.
Related
I setup a Unit Test in a Shopware custom (static) Plugin following this guide:
Shopware documentation
Everything runs fine and I'm able to run a unit test
class ProductReturnsTest extends TestCase
{
use IntegrationTestBehaviour;
use StorefrontPageTestBehaviour;
public function testConfirmPageSubscriber(): void
{
$container = $this->getKernel()->getContainer();
$dd = $container->get(CustomDataService::class); <== IT BREAKS HERE ServiceNotFoundException: You have requested a non-existent service
$dd = $container->get('event_dispatcher'); // WORKS WITH SHOPWARE ALIASES NOT WITH PLUGINS
}
}
I can make container->get on any shopware alias but as soon I try to recall and get from the container any service decleared in any xml of any 3th party plugin, i get
ServiceNotFoundException: You have requested a non-existent service "blabla"
What is wrong ?
Take a look at the answer given here: https://stackoverflow.com/a/70171394/10064036.
Probably your plugin is not marked as active in the DB your tests run against.
The test environment has a mostly unpopulated database to allow tests to to run unaffected with their own fixtures only. Therefore after each test there should be a rollback to all transactions made within the test. This principle also includes plugin installations and database transactions they may execute in their lifecycle events.
You may want to install your plugin properly before your tests, so you get a representative state of the environment with the plugins lifecycle events getting dispatched and thereby caused possible changes.
public function setUp(): void
{
$this->installPlugin();
}
private function installPlugin(): void
{
$application = new Application($this->getKernel());
$installCommand = $application->find('plugin:install');
$args = [
'--activate' => true,
'--reinstall' => false,
'plugins' => ['YourPluginName'],
];
$installCommand->run(new ArrayInput($args, $installCommand->getDefinition()), new NullOutput());
}
I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.
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 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.
I'm converting an old PHP project to the Symfony2 framework. Some of the pages are now handled by my Symfony2 front controller (index.php), but many pages have not yet been converted.
The problem is that, within Symfony, all of my Doctrine entity annotations must begin with the ORM\ prefix, but outside of Symfony, that prefix does not appear to be enabled, and so I get the following error:
Class MyProject\MyBundle\Entity\MyClass is not a valid entity or mapped super class.
I've tried to duplicate whatever magic Symfony does to set this up, including following these instructions [doctrine-project.org], and actually including app/autoload.php entirely into my legacy bootstrap process. But nothing works.
Does anyone know how I can manually replicate whatever it is that Symfony does to enable the ORM\ prefix for my Doctrine annotations?
I got the answer from the Symfony2 Google group. The problem is that the Doctrine configuration shown in the documentation uses SimpleAnnotationReader behind the scenes, but you need regular AnnotationReader to use the ORM\ namespace prefix. I got it to work by replacing this:
$config = new Doctrine\ORM\Configuration();
$driver = $config->newDefaultAnnotationDriver('/path/to/my/entities');
with this:
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
// ...
$config = new Doctrine\ORM\Configuration();
$reader = new AnnotationReader();
$driver = new AnnotationDriver($reader, '/path/to/my/entities');
I ended up with:
Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration($paths, $devMode, null, null, false);`
The 3rd and 4th null arguments are default. The 5th false argument tells it to make a standard AnnotationReader rather than a basic one.
I'm using Doctrine 2.5.6.
Explanation
I found I couldn't get Ian's solution working without calling Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration before making my own config. I was getting this error:
'[Semantical Error] The annotation "#Doctrine\ORM\Mapping\Entity" in class My\Class does not exist, or could not be auto-loaded.'
I was really confused so I took a look at the source code.
It turns out createAnnotationMetadataConfiguration calls Doctrine\ORM\Configuration::newDefaultAnnotationDriver rather than creating the annotation driver directly. This calls AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php'); which seems to be critical. After that, newDefaultAnnotationDriver just creates a new AnnotationDriver().