Load Symfony (5.2) config from database - symfony

I am a newbie in Symfony but I know how to use OOP in PHP.
I try (with frustration) to couple custom parameters with Symfony configs by using Doctrine entities.
To solve the problem I used for e.g. the answer from Michael Sivolobov: https://stackoverflow.com/a/28726681/2114615 and other sources.
My solution:
Step 1: Create new package in config folder
-> config
-> packages
-> project
-> services.yaml
-> project
-> src
-> ParameterLoaderBundle.php
-> DependencyInjection
-> Compiler
-> ParameterLoaderPass.php
Step 2: Import the new resource
# config/services.yaml
...
imports:
- { resource: 'packages/project/config/services.yaml' }
...
Step 3: Package coding
# packages/project/config/services.yaml
services:
Project\:
resource: "../src"
<?php
namespace Project;
use Project\DependencyInjection\Compiler\ParameterLoaderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ParameterLoaderBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterLoaderPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
<?php
namespace Project\DependencyInjection\Compiler;
use App\Entity\SettingCategory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParameterLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$setting = $em->getRepository(SettingCategory::class)->findAll();
$container->setParameter('test', $setting);
}
}
After at all I test the new Parameter in my API controller:
$this->getParameter('Test');
But the following error message appears:
The parameter \"test\" must be defined.

Couple of things going on here. First off, loading config from a database is very unusual in Symfony so it is not surprising that you are having difficulty. Secondly, your process code is never getting called. Part of debugging is making sure that code that you expect to be called is in fact being called. Third, you really got off on a tangent with attempting to add a bundle under config. Way back in Symfony 2 there used to be more bundle related stuff under app/config and it may be that you discovered some old articles and misunderstood them.
But, the big problem here is that Symfony has what is known as a 'compile' phase which basically processes all the configuration and caches it. Hence the CompilerPassInterface. Unfortunately, services themselves are not available during the compile phase. They simply don't exist yet so no entity manager. You need to open your own database connection if you really want to load config from a database. You will want to use just a database connection object and not the entity manager since part of the compile phase is to process the entities themselves.
So get rid of all your code and just adjust your Kernel class:
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$url = $_ENV['DATABASE_URL'];
$conn = DriverManager::getConnection(['url' => $url]);
$settings = $conn->executeQuery('SELECT * FROM settings')->fetchAllAssociative();
$container->setParameter('test',$settings);
}
And be aware that even if you get all this working, you will need to manually rebuild the Symfony cache after updating your settings table. It is not going to be automatic. You really might consider taking a completely different approach.

Related

Install Symfony without symfony/runtime and with old index.php

I am trying to add Symfony 5.4 to my legacy project. There is a pretty nice documentation on how to do this, but there's a big problem - the documentation assumes "normal" Symfony, but each time I try to install Symfony using their recommended way of composer create-project, I get a Symfony version with symfony/runtime - the big problem here, is that this version has a completely different index.php:
<?php
use App\Kernel;
require_once dirname(_DIR_).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
The documentation found here is based on a completely different index file.
I did find that I can remove the runtime package, and just copy old index, and it works for the most part, but then you also have problems with console.php and I worry that if I go this route there will be more and more problems caused by my installation expecting symfony/runtime and me manually removing it's
I tried installing Symfony 5.3 as well as different patches of 5.4, all came with this installed, even though I did work on some 5.3 / 5.4 projects and had the old school index.php file.
Does anyone know how to currently install Symfony with the "old" index.php, console.php etc.?
Thanks!
So the task is to migrate from a non-Symfony legacy app to a Symfony app. The basic idea is to allow the Symfony app to process a request and then hand it off to the legacy app if necessary. The Symfony docs show how to do this but but relies on the older style index.php file. The newer runtime based approach is a bit different.
But in the end all it really takes is a couple of fairly simple classes. A runner class takes care of creating a request object and turning it into a response. This is where you can add the bridge to your legacy app. It's a clone of Symfony's HttpKernelRunner class:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;
class LegacyRunner implements RunnerInterface
{
private $kernel;
private $request;
public function __construct(HttpKernelInterface $kernel, Request $request)
{
$this->kernel = $kernel;
$this->request = $request;
}
public function run(): int
{
$response = $this->kernel->handle($this->request);
// check the response to see if it should be handed off to legacy app
dd('Response Code ' . $response->getStatusCode());
$response->send();
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($this->request, $response);
}
return 0;
}
}
Next you need to wire up runner by extending the SymfonyRuntime::getRunner method:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Runtime\RunnerInterface;
use Symfony\Component\Runtime\SymfonyRuntime;
class LegacyRuntime extends SymfonyRuntime
{
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof HttpKernelInterface) {
return new LegacyRunner($application, Request::createFromGlobals());
}
return parent::getRunner($application);
}
}
Finally, update composer.json to use your legacy runtime class:
"extra": {
...
"runtime": {
"class": "App\\Legacy\\LegacyRuntime"
}
}
After updating composer.json do a composer update for the changes to take effect and start your server. Navigate to a route and you should hit the dd statement.

symfony 3.1 Check if a bundle is installed

I'm developing a bundle who has a dependency on another one.
In order to handle the case that the base bundle has not been installed I'll like to perform a "bundle_exists()" function inside a controller.
The question is: How can I have a list of installed bundles or How can I check for the name (eventually also the version) of a bundle.
Thanks.
In addition to #Rooneyl's answer:
The best place to do such a check is inside your DI extension (e.g. AcmeDemoExtension). This is executed once the container is build and dumped to cache. There is no need to check such thing on each request (the container doesn't change while it's cached anyway), it'll only slow down your cache.
// ...
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$bundles = $container->getParameter('bundles');
if (!isset($bundles['YourDependentBundle'])) {
throw new \InvalidArgumentException(
'The bundle ... needs to be registered in order to use AcmeDemoBundle.'
);
}
}
}
Your class needs to have access to the container object (either by extending or DI).
Then you can do;
$this->container->getParameter('kernel.bundles');
This will give you a list of bundles installed.
Update;
If you are in a controller that extends the Symfony\Bundle\FrameworkBundle\Controller\Controller or in a command class that extends Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand, you can just get the parameter.
$this->getParameter('kernel.bundles').
Else #Wouter J's answer is your best answer.
You can get a list of all Bundles from the Kernel like this:
public function indexAction ()
{
$arrBundles = $this->get("kernel")->getBundles();
if (!array_key_exists("MyBundle", $arrBundles))
{
// bundle not found
}
}
From Andrey at this question: How do I get a list of bundles in symfony2?
If you want to call a non static method of registered bundle object (not class) then you can do the following:
$kernel = $this->container->get('kernel');
$bundles = $kernel->getBundles();
$bundles['YourBundleName']->someMethod();
Where 'YourBundleName' is the name of your bundle, which you can get by calling from console:
php app/console config:dump-reference

Symfony 2.5.6 error InvalidArgumentException: The service definition "event_dispatcher" does not exist

I'm trying to build my first Compiler Pass in Symfony 2. For now, I'm just trying to get the core event_dispatcher service from FrameWorkBundle inside a SampleBundle, but I get this error :
error InvalidArgumentException: The service definition "event_dispatcher" does not exist.
Here is the code for my compiler :
<?php
namespace Me\SampleBunlde\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class RegisterListenersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('event_dispatcher');
}
}
?>
I'm a bit surprised since I'm following step by step a professionnal Symfony book who assures me that I will find this service with that id.
I've done some researches about that, and I discovered that only the debug.event_dispatcher service was avaible. Then I checked for aliases and saw that there was a private Alias named 'event_dispatcher' pointing to debug.event_dispatcher. So I'm really confused about all that. And I'm wondering :
Why is the Alias private ? Do I need to set him Public or is it the wrong way ?
Why Symfony does not automatically interprets my event_dispatcher call ?
Thank you for your help !
Use findDefinition() instead of getDefinition(). findDefinition also looks for aliases.

Symfony2 Adding 3rd party Bundles to a Service Controller

I am looking to get some help on how to add a 3rd party bundle to an existing service controller, specifically KnpSnappyBundle https://github.com/KnpLabs/KnpSnappyBundle.
The bundle was installed with composer, and registerBundles() function in app/AppKernel.php was updated.
I am having trouble with passing the bundle to a service controller, via its constructor.
1: Unsure how to figure out the path to use
--SomeController.php file--
...
use Symfony\Component\???\???
class FormDataController
{
...
private $pdf;
2: Also unsure how to figure out what the object type is named.
public function __construct(..., KnpSnappyBundle? $pdf )
{
...
$this->pdf= $pdf;
return $this;
}
3: Then in the servives.yml file i add the following argument to the correct controller
- "#knp_snappy.pdf"
After doing the above, the controller should be able to do the following to access the bundles functions, correct?
$this->pdf->...
Why are you doing this?
Quote:
The bundle registers two services:
the knp_snappy.image service allows you to generate images;
the knp_snappy.pdf service allows you to generate pdf files.
Just access them like this (if you use standard Symfony controller):
$this->get('knp_snappy.pdf')->...;
$this->get('knp_snappy.image')->...;
UPDATE:
If you are injecting knp_snappy.pdf service into your custom service, you should use Knp\Bundle\SnappyBundle\Snappy\LoggableGenerator class.
--SomeController.php file--
...
use Knp\Bundle\SnappyBundle\Snappy\LoggableGenerator;
class FormDataController
{
// ... //
private $pdf;
public function __construct(..., LoggableGenerator $pdf )
{
...
$this->pdf = $pdf;
return $this;
}
// ... //
}
When you run ./app/console container:debug command, it lists all available services and corresponding classes. Very helpful.

Doctrine PHPCR-ODM under Symfony not detecting mapped Document class

I am attempting to integrate PHPCR-ODM with an existing Symfony project, and am having trouble getting it to (presumably) detect my mapped Document class. Specifically, I get an error like this when attempting to persist a Document of my class MyDocument:
[Doctrine\Common\Persistence\Mapping\MappingException]
The class 'Example\Common\ORM\Document\MyDocument' was not found in the chain configured namespaces Doctrine\ODM\PHPCR\Document
My class is in a potentially strange namespace because this project uses Doctrine ORM as well, and thus far I've just added a new space for mapped Documents off of that, but I can't imagine the choice of namespace name affects the functionality.
Per the docs, I have added to my app/autoload.php:
AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php');
My app/config/config.yml includes the following (with parameters set in parameters.yml):
doctrine_phpcr:
session:
backend:
type: jackrabbit
url: %jackrabbit_url%
workspace: %jackrabbit_workspace%
username: %jackrabbit_user%
password: %jackrabbit_password%
odm:
auto_mapping: true
My document class lives in src/Example/Common/ORM/Document/MyDocument.php and looks like:
<?php
namespace Example\Common\ORM\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
/**
* #PHPCRODM\Document
*/
class MyDocument
{
/**
* #PHPCRODM\Id
*/
private $id;
/**
* #PHPCRODM\ParentDocument
*/
private $parent;
/**
* #PHPCRODM\Nodename
*/
private $name;
// .. etc
Finally, the code I am using to test the integration is inside a simple console command, and looks like:
use Example\Common\ORM\Document\MyDocument;
// ...
$documentManager = $this->container->get('doctrine_phpcr.odm.default_document_manager');
$document = new MyDocument();
$document->setParent($documentManager->find(null, '/'));
$document->setName('ExampleName');
$documentManager->persist($document);
$documentManager->flush();
I have verified that my MyDocument class is being correctly loaded, but it seems that the annotations are not being processed in a way that is making the DocumentManager aware that it is a mapped Document class.
My guess is that I have overlooked some simple configuration step, but from looking repeatedly and thoroughly at the docs for PHPCR, PHPCR-ODM, and even Symfony CMF, I can't seem to find anything. Most of the examples out there involve using PHPCR via Symfony CMF, and I wasn't able to find many (any?) real world examples of PHPCR-ODM being integrated in a regular Symfony project.
edit: The Eventual Solution
I followed the advice that #WouterJ gave below and it fixed my problem, and I further followed his suggestion of adding a compiler pass to my Symfony bundle to make this work with a non-standard namespace (i.e., something other than YourBundle\Document). In my case, this is going into a library that will be re-used elsewhere rather than a bundle, so it was appropriate.
To do this, I added a method to the src/Example/Bundle/ExampleBundle/ExampleBundle.php file like so:
<?php
namespace Example\Bundle\ExampleBundle;
use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ExampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$mappedDirectories = array(
realpath(__DIR__ . '/../../Common/ODM/Document')
);
$mappedNamespaces = array(
'Example\Common\ODM\Document'
);
$phpcrCompilerClass = 'Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass';
if (class_exists($phpcrCompilerClass)) {
$container->addCompilerPass(
DoctrinePhpcrMappingsPass::createAnnotationMappingDriver(
$mappedNamespaces,
$mappedDirectories
));
}
}
}
That code allows any mapped document classes to be placed in the Example\Common\ODM\Document namespace and it will pick them up. This example uses annotations but the same pattern can be used for XML or YAML mappings (see the Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass class for method signatures).
I found that I also needed to define the doctrine_phpcr.odm.metadata.annotation_reader service for this to work, which I did in app/config.yml:
services:
doctrine_phpcr.odm.metadata.annotation_reader:
class: Doctrine\Common\Annotations\AnnotationReader
There may be a better way to do that, but that was enough to make it work for me.
The document should be placed in the Document namespace of the bundle, not the ORM\Document namespace.
If you really want to put it in the ORM\Document namespace (which is very strange, because we are talking about an ODM not an ORM), you can use the doctrine mapping compiler pass: http://symfony.com/doc/current/cookbook/doctrine/mapping_model_classes.html

Resources