laravel-doctrine / orm

An integration library for Laravel and Doctrine ORM
MIT License
830 stars 178 forks source link

[QUESTION] Laravel Octane support #489

Closed filakhtov closed 3 weeks ago

filakhtov commented 3 years ago

Hey team πŸ‘‹πŸ» ,

As the title indicates, I would like to check what are the plans (if any) to support Laravel Octane and a concept of long running web worker processes.

As things stand today, Doctrine ORM v.2.x closes an entity manager any time an SQL error occurs. It was designed that way to keep internal state consistent avoid data corruption. It has been a known issue forever and almost five years ago Symfony maintainers suggested to make the entity manager resettable in doctrine/orm#5933.

Unfortunately, it is not possible to achieve what was proposed in a backwards compatible way, so it was scheduled for Doctrine 3.x, and looking at the milestone/roadmap for Doctrine 3.x, I would say it is unlikely coming any time soon, meaning we are stuck with 2.x for years to come.

This problem further amplified with Laravel Octane and using PSR-7 based web server implementation, Swoole or RoadRunner, because they start a long running worker process that will process multiple requests and aim to reuse as many dependencies as possible to improve performance and reduce request bootstrap time.

As an example, Symfony has solved this by introducing a proxy (see symfony/symfony#19203) for entity manager. I know that a partial solution exists for this library as well, in a form of IlluminateRegistry::resetManager(), however it has issues, such as losing extensions.

With the above context, my question are:

Thanks in advance for reading through this issue and providing any feedback!

eigan commented 3 years ago

Hi @filakhtov πŸ‘‹πŸ»

As of now laravel-doctrine do not have any maintainer that moves the project forward. Almost all contributions are provided from non team members. We are just making sure that pull requests are merged and released. It might appear that we do not even use laravel-doctrine but the reason (for me at least) is that we do not keep up with the laravel packages and features.

So, no there is no plans, however I am active and will answer questions. Sadly I have no time to implement or look for solutions.

I would like to invite you to our slack channel, but seems like our invite-page is down.

aftabnaveed commented 3 years ago

@filakhtov why not create a pull request? We are using https://github.com/swooletw/laravel-swoole in our project and the way we deal with the ORM Exceptions is that we just reload the swoole worker using Server::reload() that just re-initialises the worker and thus the EntityManager another workaround we did was that on each request we clean it using EntityManager::clean method.

filakhtov commented 3 years ago

@aftabnaveed I would have done this for active project, but given the project does not have any committed maintainers and visionaries who is moving this library forward I am very hesitant to use it in production systems.

aftabnaveed commented 3 years ago

@filakhtov We heavily rely on this for our e-commerce websites and has a team of devs using it. I am happy to help maintain where appropriate. @eigan seems to be very active and helping where possible.

josenicomaia commented 3 years ago

The slack invite page is my fault. I am fixing it tomorrow.

seifane-wise commented 2 years ago

@filakhtov Have you tried putting a listener on Octane ?

I'm thinking something like this :

class ResetEntityManager
{
    public function handle(RequestReceived $event): void
    {
        if (!App::getFacadeApplication()->get('em')->isOpen()) {
            $service = new DoctrineServiceProvider(App::getFacadeApplication());
            $service->register();
        }
        EntityManager::clear();
    }
}

And calling it in octane.php.

...
    'listeners' => [
        WorkerStarting::class => [
            EnsureUploadedFilesAreValid::class,
            EnsureUploadedFilesCanBeMoved::class,
        ],

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            Support\Doctrine\Octane\Listeners\ResetEntityManager::class
            //
        ],

        RequestHandled::class => [
...

I am still experimenting with octane but that should solve most of the issues regarding resetting and flushing of the EntityManager (That I can think of).

superbiche commented 2 years ago

Tried @seifane-wise 's approach but changed the listeners just a bit, so clearing the EntityManager happens after the request is handled:

class EnsureEntityManagerIsOpen
{
    public function handle(RequestReceived|RequestTerminated $event): void
    {
        if (!$event->app->get('em')->isOpen()) {
            $service = new DoctrineServiceProvider($event->app);
            $service->register();
        }
    }
}
class ClearEntityManager
{
    public function handle(): void
    {
        EntityManager::clear();
    }
}
'listeners' => [
    RequestReceived::class => [
        ...Octane::prepareApplicationForNextOperation(),
        ...Octane::prepareApplicationForNextRequest(),
        \App\Doctrine\Octane\Listeners\EnsureEntityManagerIsOpen::class,
        //
    ],
    RequestTerminated::class => [
        // FlushUploadedFiles::class,
        \App\Doctrine\Octane\Listeners\ClearEntityManager::class,
     ],
     TaskReceived::class => [
         ...Octane::prepareApplicationForNextOperation(),
         \App\Doctrine\Octane\Listeners\EnsureEntityManagerIsOpen::class,
     ],
     TaskTerminated::class => [
        \App\Doctrine\Octane\Listeners\ClearEntityManager::class,
     ],

This seems to do the trick when closing manually the EntityManager in RequestTerminated. I didn't make a lot of tests for now but this looks promising.

I guess Swoole users also need to add EnsureEntityManagerIsOpen listener to TickReceived and ClearEntityManager to TickTerminated.

filakhtov commented 2 years ago

I just wanted to loop back and say that we have achieved what we wanted by creating a wrapper on top of the EntityManager and provided convenience methods to replace wrapped instance after each request. Just to mention, this has its own implications with losing Doctrine extensions, because those are loaded in the service provider as a separate step.

We have, however, completely abandoned the idea using long running processes with PHP, because the ecosystem isn't just there yet. We have encountered numerous issues with various third party libraries that are written with the assumption of running under traditional PHP model and leaving residual state that later bleeds between requests. As such we deemed that this model is not feasible at scale and will require significant investments in auditing every single dependency we have in the project today, any time its updated or every new dependency introduced for any residual state that could manifest in plethora of different possible ways.

I do believe that PHP could achieve maturity in this area in years to come, but at its current state it's just not ready.