Why does autowiring provides a different entity manager instance for event subscribers? - doctrine

I have different services in a symfony4 project, which get the entity manager injected. I found out, that doctrine event subscribers and services used by them get a different entity manager instance than the other services and when you call self::$container->get('doctrine')->getManager(). I have seen up to three different instances in my project, but I don't know under which circumstances even more instances are created.
I have added the function spl_object_id to all constructors to see which instance of the entity manager is used by the objects. The following code has two services and one event subscriber. The event subscriber uses the first service. I expected all of these to use the same entity manager instance, since the general idea of the service container is that objects of a certain type are only created once. But obviously two entity manager instances are created, one for the event subscriber and all services used by it and one for all others.
TestService1.php:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class TestService1
{
public function __construct(EntityManagerInterface $entityManager)
{
echo "\n Manager from TestService1: ".spl_object_id($entityManager);
}
}
TestService2.php
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class TestService2
{
public function __construct(EntityManagerInterface $entityManager)
{
echo "\n Manager from TestService2: ".spl_object_id($entityManager);
}
}
TestSubscriber.php:
<?php
namespace App\Service;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;
class TestSubscriber implements EventSubscriber
{
public function __construct(EntityManagerInterface $entityManager, TestService1 $testService1)
{
echo "\n Manager from TestSubscriber: ".spl_object_id($entityManager);
}
public function getSubscribedEvents()
{
}
}
TestServiceTest.php:
<?php
namespace App\Tests\Service;
use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class TestServiceTest extends KernelTestCase
{
public function testGetEntityManager()
{
self::bootKernel();
$testObject1 = self::$container->get(TestService1::class);
$testObject2 = self::$container->get(TestService2::class);
echo "\n Manager from container: ".spl_object_id(self::$container->get('doctrine')->getManager());
}
}
services.yaml:
services:
.....
App\Service\TestSubscriber:
tags:
- { name: doctrine.event_subscriber}
App\Service\TestService1:
public: true
App\Service\TestService2:
public: true
Result of phpunit test run:
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.
Testing App\Tests\Service\TestServiceTest
. 1 / 1 (100%)
Manager from TestService1: 50
Manager from TestSubscriber: 50
Manager from TestService2: 386
Manager from container: 386
Time: 200 ms, Memory: 16.00MB
OK (1 test, 1 assertion)
I would expect, that the object id of the entity manager is the same at all places, i.e. that there is only ONE object. This shows that there are two instances. Running this in Symfony 2.8 did result in only ONE instance.
Questions:
Why do container / autowiring create two or more different entity manager instances, e.g. when doctrine event subscribers are used?
How do I prevent this?
Should it be important: I use php 7.2.5, symfony 4.3.1 and doctrine orm 2.6.3.
Edit:
I just found out that not only the entity manager has multiple instances, but also some of my own services. I haven't found out yet why. The problem in tests is, that I initialise some services in tests before they are used by other services. When the initialised services are different instances, then the services using them fail.

I thought, that this behaviour was related somehow to phpunit and/or the KernelTestCase/WebTestCase, but it is only in part.
I created a controller to use the objects:
<?php
namespace App\Controller;
use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class TestController extends AbstractController
{
/**
* #Route("/test_controller")
*/
public function showAction(TestService1 $testService1, TestService2 $testService2)
{
return new Response('<br/>Testservice1 in controller: '.$testService1->getEmId().'<br/>Testservice2 in controller: '.$testService2->getEmId());
}
}
and I added a getter for the entity managers id to the service classes:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class TestService1
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
echo "<br/>Manager from TestService1: ".spl_object_id($entityManager);
$this->entityManager = $entityManager;
}
public function getEmId()
{
return spl_object_id($this->entityManager);
}
}
This results in the following output:
Manager from TestService1: 2897
Manager from TestSubscriber: 2897
Manager from TestService2: 3695
Testservice1 in controller: 2897
Testservice2 in controller: 3695
As you can see, there are here also TWO different entity manager objects, which makes passing doctrine objects between these services impossible, when they have to be changed or stored.
What I also found out, is that there IS also a relation to tests:
Other than in Symfony 2.8, the KernerTestCase and the WebTestCase have a tearDown-method, which is called after EACH test case. In this method the kernel is reset. This means, that in tests you cannot store a service in a static variable and use it in all test cases, since the kernel and with that the service objects change between each test case.
The problem can be even worse in tests using the WebTestCase. When calling bootKernel and createClient (which also boots the kernel, again!), all object are recreated, again with different entity managers. There is a bug report on this. Here is an example:
<?php
namespace App\Tests\Service;
use App\Entity\Status;
use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class TestServiceTest extends WebTestCase
{
public function testGetEntityManager()
{
self::bootKernel();
// I am unaware that createClient also boots the kernel, so let's store the EM object...
$entityManager = self::$container->get('doctrine')->getManager();
echo "\n Manager from container 1: ".spl_object_id(self::$container->get('doctrine')->getManager());
self::createClient();
echo "\n Manager from container 2: ".spl_object_id(self::$container->get('doctrine')->getManager());
$testObject1 = self::$container->get(TestService1::class);
$testObject2 = self::$container->get(TestService2::class);
echo "\n Manager from container 3: ".spl_object_id(self::$container->get('doctrine')->getManager());
// This object is managed by one of the now 4!!! entity managers, passing it so a service using a different
// EM will at best not work, but probable cause exceptions (object not under control of MY entity manager,
// cannot persist) or even crash.
$status = $entityManager->getRepository(Status::class)->findOneBy(['status' => 'member']);
}
}
This results in the following output:
Manager from TestService1: 60
Manager from TestSubscriber: 60
Manager from container 1: 399
Manager from TestService1: 434
Manager from TestSubscriber: 434
Manager from container 2: 507
Manager from TestService2: 507
Manager from container 3: 507
We now have FOUR different entity manager instances!

Related

Laravel: How to register an own implementation of `DatabaseManager` and `Connection`

Due to debugging reasons (I am hunting down a very weird bug), I need to extend the Laravel classes Illuminate\Database\DatabaseManager, Illuminate\Database\Connection, and Illuminate\Database\Query\Builder.
Hence, I created the following three classes in my application.
namespace App\Database;
use Illuminate\Database\DatabaseManager as BaseDatabaseManager;
class DatabaseManager extends BaseDatabaseManager {
public static bool $isDebugEnabled = false;
// ...
// Overwrites some methods of the base class which prints additional diagnostic information
// if `self::$isDebugEnabled === true`.
// ...
}
and
namespace App\Database;
use Illuminate\Database\Connection as BaseConnection;
class Connection extends BaseConnection {
public static bool $isDebugEnabled = false;
/**
* Get a new query builder instance.
*
* This returns our own extended query builder with instrumentation.
*
* #return Query\Builder
*/
public function query(): Query\Builder {
return new Query\Builder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
// ...
// Overwrites some methods of the base class which prints additional diagnostic information
// if `self::$isDebugEnabled === true`.
// ...
}
and
namespace App\Database\Query;
use Illuminate\Database\Query\Builder as BaseBuilder;
class Builder extends BaseBuilder {
public static bool $isDebugEnabled = false;
// ...
// Overwrites some methods of the base class which prints additional diagnostic information
// if `self::$isDebugEnabled === true`.
// ...
}
How do I register my custom DatabaseManager and Connection with the Laravel Service Container instead of the original parent classes? The original classes are registered in \Illuminate\Foundation\Application::registerCoreContainerAliases() which is called by the constructor of the Application object, i.e. these classes are registered at a very early stage.
I thought that I could "re-register" my classes in \App\Providers\AppServiceProvider::register like this
namespace App\Providers;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register() {
// Overwrite core container services with our own classes
// for debugging purposes
// See \Illuminate\Foundation\Application::registerCoreContainerAliases()
$this->app->alias('db', \App\Database\DatabaseManager::class);
$this->app->alias('db.connection', \App\Database\Connection::class);
}
}
but it did not work. The original classes are still being used. I used app->alias, because this is what registerCoreContainerAliases originally uses, too. But I also tried ->bind with no success.
After some debugging I found out that ->alias and ->bind do not overwrite a previously registered binding, but simply add a new binding to the end of the list. Hence, the previously registered services by the Laravel core application are still preferential.
What is the correct solution?

IoC resolving issue after upgrading to 5.8

Hoping someone can help me,
I am updating from version 5.7 to 5.8 and I am now having issues with our repository setup.
I am using the repository pattern, for each repository we have a contract. I have a service provider (this is setup to be deferred) to bind the contracts to the repositories so when type hinted the interfaces / contracts in the controllers the repository is injected in.
We also have a service provider (also setup to be deferred) that when the repository is resolved it gets some config data from a config file and calls methods on the repository to setup attributes.
This all worked great on 5.7 but after upgrading to 5.8 it doesn't. If I switch the controllers to type hint the repository instead of the contract it works, but obviously if / when we change the repository we would have to amend all places this is referenced, completely against purpose of coding to an interface and injecting this.
Hopefully I've explained that well enough!
Has anyone come up against this or similar? I've checked the upgrade notes and 5.8 no longer uses the defer property as it implements the interface which I am doing but I can't see anything else and I am struggling to debug it any further.
https://laravel.com/docs/5.8/upgrade#deferred-service-providers
Any help any one can offer would be great.
If I switch back to 5.7 there is no issue, or if use the actual repository and typehint this in the controller there is no issue, it is happening only when using the contract / interface
Here is the service provider for the Repository to set the properties of it
<?php
namespace App\Providers\Repositories\Erp;
use App\Models\ERP_OLTP\WorkOrderStatus as WorkOrderStatusModel;
use App\Repositories\Erp\EloquentMaterialRequirements;
use Illuminate\Contracts\Config\Repository as ConfigRepositoryInterface;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class MaterialRequirementsServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register()
{
$this->app->resolving(
EloquentMaterialRequirements::class,
function (EloquentMaterialRequirements $repo, $app) {
$config = $app[ConfigRepositoryInterface::class]->get('materialRequirements');
$statusIds = array_reduce(
$config['workOrderStatuses'],
function ($acc, $statusCode) {
$acc[] = WorkOrderStatusModel::STATUS[$statusCode];
return $acc;
},
[]
);
$repo->setDemandStatusIds($statusIds);
$repo->setDemandWeekCount($config['weekCount']);
$repo->setAverageUsageWeekCount($config['averageWeeklyUsageMaxAgeWeeks']);
}
);
}
public function provides()
{
return [
EloquentMaterialRequirements::class,
];
}
}
Here is the service provider for repositories to be binded to contracts
<?php
namespace App\Providers;
use Illuminate\Contracts\Config\Repository as ConfigRepositoryInterface;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class ErpRepositoriesServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register()
{
foreach ($this->getBindings() as $contract => $implementation) {
$this->app->bind($contract, $implementation);
}
}
public function provides()
{
$services = [];
foreach (array_keys($this->getBindings()) as $contract) {
$services[] = $contract;
}
return $services;
}
private function getBindings(): array
{
return $this->app[ConfigRepositoryInterface::class]->get('repositories.bindings');
}
}
here is the config.repositories, i've removed other repositories to minify it
<?php
use App\Contracts\Erp\Repositories as Contracts;
use App\Managers\Erp\Repositories as Managers;
use App\Repositories\Erp as Repositories;
return [
'bindings' => [
Contracts\MaterialRequirements::class => Repositories\EloquentMaterialRequirements::class,
],
];
It simply never calls the MaterialRequirementsServiceProvider or if it does it isn't resolving the EloquentMaterialRequirements::class and therefore setting properties. I get no errors, no exceptions, nothing to go off

How to extend a laravel package within laravel 5.8 application?

I am new to Laravel and would appreciate any help on describing how a Laravel package residing in vendor folder can be extended and wont get affected if I update the package.
I'll try to create a brief guide, and we'll expand if need be.
I suggest putting all of your files inside a separate directory / namespace. You'll benefit from this should you decide to create your own composer package afterwards.
As an example I'll extend bumbummen99/shoppingcart package which forks the great gloudemans/shoppingcart, adding support for Laravel 5.8 and some minor functionality. You will of course first need to install that package:
composer require bumbummen99/shoppingcart
Start by making a couple of folders. You can use any name for folders / classes, this is what I used, relative to the project root:
app/Repositories/ExtendedCart
app/Repositories/ExtendedCart/Facades
Create the file
app/Repositories/ExtendedCart/ExtendedCart.php
This class will extend the package's main class:
namespace App\Repositories\ExtendedCart;
use Gloudemans\Shoppingcart\Cart;
class ExtendedCart extends Cart
{
public function myMethod(){
return 'myMethod';
}
}
Then make your Service Provider. Create the file:
app/Repositories/ExtendedCart/ExtendedCartServiceProvider.php
(I'm not using artisan as generating / moving the provider will produce wrong namespace)
This is your Service Provider content, here you reference the class that extends the package's class. Note you overwrite the binding from the original package.
namespace App\Repositories\ExtendedCart;
use Gloudemans\Shoppingcart\ShoppingcartServiceProvider;
class ExtendedCartServiceProvider extends ShoppingcartServiceProvider
{
public function register()
{
$this->app->bind('cart', 'App\Repositories\ExtendedCart\ExtendedCart');
}
}
Then register your service provider in config/app.php
'providers' => [
...
//Add this line to the end of providers array
App\Repositories\ExtendedCart\ExtendedCartServiceProvider::class,
]
Lastly create a Facade, which will instantiate the class (otherwise you will get non-static method exceptions). Create this file:
app/Repositories/ExtendedCart/Facades/ExtendedCart.php
This is the contents of the file:
namespace App\Repositories\ExtendedCart\Facades;
use Illuminate\Support\Facades\Facade;
class ExtendedCart extends Facade {
protected static function getFacadeAccessor() { return 'cart'; }
}
You're all set to use your extended methods. You can safely upgrade the original package, and you can even use the default facade:
namespace App\Http\Controllers;
use Cart;
class SomeController extends Controller{
public function someFunction(){
Cart::instance('default')->myMethod();
//This should return 'myMethod'
}
}
I hope this helps, good luck!

Why won't this Laravel 5.4 service provider register?

I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});

Action parameters & Doctrine entities in Symfony 2

We're running a project built on top of Zend Framework 1.x, and are considering moving to Symfony 2. We have a domain model mapped with Doctrine 2.
Our (custom built) base controller class extends Zend_Controller_Action to provide a very convenient feature, inspired from Flow3:
Let's say I have this controller:
class UserController extends BaseController
{
public function editAction(User $user)
{
// ...
}
}
If I load this URL:
/user/edit?user=123
The base controller will automatically load the User entity with identity 123, and pass it as a parameter to the editAction() method.
If the user parameter is omitted, or if no User with this identity exists, an exception is thrown.
Is there such an implementation for Symfony 2, or is it possible to implement it, and how?
The #ParamConverter annotation from SensioFrameworkExtraBundle does exactly that. If you're using the Symfony Standard distribution, you get it out of the box.

Resources