Code Monkey home page Code Monkey logo

laravel-transactional-events's Introduction

Transaction-aware Event Dispatcher for Laravel

Latest Stable Version Total Downloads

This Laravel package introduces Transaction-aware Event Dispatcher.
It ensures the events dispatched within a database transaction are dispatched only if the outer transaction successfully commits. Otherwise, the events are discarded and never dispatched.

Note: Laravel 8.17 introduced a new method DB::afterCommit that allows one to achieve the same of this package. Yet, it lacks transaction-aware behavior support for Eloquent events.

Table of Contents

Motivation

Consider the following example of ordering tickets that involves changes to the database.
The orderTickets dispatches the custom OrderCreated event. In turn, its listener sends an email to the user with the order details.

DB::transaction(function() {
    ...
    $order = $concert->orderTickets($user, 3); // internally dispatches 'OrderCreated' event
    PaymentService::registerOrder($order);
});

In the case of transaction failure, due to an exception in the orderTickets method or even a deadlock, the database changes are completely discarded.

Unfortunately, this is not true for the already dispatched OrderCreated event. This results in sending the order confirmation email to the user, even after the order failure.

The purpose of this package is thus to hold events dispatched within a database transaction until it successfully commits. In the above example the OrderCreated event would never be dispatched in the case of transaction failure.

Installation

Laravel Package Notes
5.8.x-7.x 1.8.x
8.x-11.x 2.x >2.1.x requires PHP 8+

Laravel

  • Install this package via composer:
composer require fntneves/laravel-transactional-events
  • Publish the provided transactional-events.php configuration file:
php artisan vendor:publish --provider="Neves\Events\EventServiceProvider"

Lumen

  • Install this package via composer:
composer require fntneves/laravel-transactional-events
  • Manually copy the provided transactional-events.php configuration file to the config folder:
cp vendor/fntneves/laravel-transactional-events/src/config/transactional-events.php config/transactional-events.php
  • Register the configuration file and the service provider in bootstrap/app.php:
// Ensure the original EventServiceProvider is registered first, otherwise your event listeners are overriden.
$app->register(App\Providers\EventServiceProvider::class);

$app->configure('transactional-events');
$app->register(Neves\Events\EventServiceProvider::class);

Usage

The transaction-aware layer is enabled out of the box for the events under the App\Events namespace.

This package offers three distinct ways to dispatch transaction-aware events:

  • Implement the Neves\Events\Contracts\TransactionalEvent contract;
  • Use the generic TransactionalClosureEvent event;
  • Use the Neves\Events\transactional helper;
  • Change the configuration file.

Use the contract, Luke:

The simplest way to mark events as transaction-aware events is implementing the Neves\Events\Contracts\TransactionalEvent contract:

namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
...
use Neves\Events\Contracts\TransactionalEvent;

class TicketsOrdered implements TransactionalEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    ...
}

And that's it. There are no further changes required.

What about Jobs?

This package provides a generic TransactionalClosureEvent event for bringing the transaction-aware behavior to custom behavior without requiring specific events.

One relevant use case is to ensure that Jobs are dispatched only after the transaction successfully commits:

DB::transaction(function () {
    ...
    Event::dispatch(new TransactionalClosureEvent(function () {
        // Job will be dispatched only if the transaction commits.
        ProcessOrderShippingJob::dispatch($order);
    });
    ...
});

And that's it. There are no further changes required.

Configuration

The configuration file includes the following parameters:

Enable or disable the transaction-aware behavior:

'enable' => true

By default, the transaction-aware behavior will be applied to all events under the App\Events namespace.
Feel free to use patterns and namespaces.

'transactional' => [
    'App\Events'
]

Choose the events that should always bypass the transaction-aware layer, i.e., should be handled by the original event dispatcher. By default, all *ed Eloquent events are excluded. The main reason for this default value is to avoid interference with your already existing event listeners for Eloquent events.

'excluded' => [
    // 'eloquent.*',
    'eloquent.booted',
    'eloquent.retrieved',
    'eloquent.saved',
    'eloquent.updated',
    'eloquent.created',
    'eloquent.deleted',
    'eloquent.restored',
],

Frequently Asked Questions

Can I use it for Jobs?

Yes. As mentioned in Usage, you can use the generic TransactionalClosureEvent(Closure $callable) event to trigger jobs only after the transaction commits.

License

This package is open-sourced software licensed under the MIT license.

laravel-transactional-events's People

Contributors

adduc avatar bytestream avatar digistorm-developer avatar duxthefux avatar erikgaal avatar fntneves avatar hanson avatar lokielse avatar mfn avatar owenvoke avatar rtroost avatar stylecibot avatar szasza avatar woodspire avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-transactional-events's Issues

Error after installation: Auth driver [jwt] for guard [api] is not defined.

Package version: 1.8.2
Laravel version: 5.8.32

Installation is performed but as soon as any framework code is executed (./artisan is enough), it 'splodes:

./composer.phar require fntneves/laravel-transactional-events
Using version ^1.8 for fntneves/laravel-transactional-events
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing drupol/phptree (2.4.4): Loading from cache
  - Installing fntneves/laravel-transactional-events (1.8.2): Loading from cache
Writing lock file
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi

In AuthManager.php line 97:

  Auth driver [jwt] for guard [api] is not defined.


PHP Fatal error:  Uncaught InvalidArgumentException: Auth driver [jwt] for guard [api] is not defined. in /vagrant/project/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php:97
Stack trace:
#0 /vagrant/project/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php(68): Illuminate\Auth\AuthManager->resolve('api')
#1 /vagrant/project/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php(297): Illuminate\Auth\AuthManager->guard()
#2 /vagrant/project/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(237): Illuminate\Auth\AuthManager->__call('check', Array)
#3 /vagrant/project/app/Services/Sentry.php(36): Illuminate\Support\Facades\Facade::__callStatic('check', Array)
#4 /vagrant/project/vendor/sentry/sentry/src/State/Hub.php(106): App\Services\Sentry->App\Services\{closure}(Object(Sentry\State\Scope))
#5 /vagrant/project/app/Services/Sentry.php(41): Sentry\State\Hub->configureScope(Object(Closure))
#6 /vagrant/project/app/Exceptions/Handler.php in /vagrant/project/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php on line 97

In AuthManager.php line 97:

  Auth driver [jwt] for guard [api] is not defined.


Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 255

I can work around the installation problem by disabling auto-discovery in the composer.json:

  "extra": {
    "laravel": {
      "dont-discover": [
        "fntneves/laravel-transactional-events"
      ]
    }

But as son as I add \Neves\Events\EventServiceProvider to config/app into providers I'm were I started.

I'm also using https://github.com/tymondesigns/jwt-auth .

When I debug the above exception I end up in \Illuminate\Auth\AuthManager::resolve where said exception is thrown.

The last thing it tried was to look for a method called createJwtDriver:

โ€ฆ
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );

It's somehow connected to this code in \Illuminate\Auth\AuthServiceProvider::registerEventRebindHandler:

        $this->app->rebinding('events', function ($app, $dispatcher) {
            if (! $app->resolved('auth')) {
                return;
            }

            if (method_exists($guard = $app['auth']->guard(), 'setDispatcher')) {
                $guard->setDispatcher($dispatcher);
            }
        });

It talks about event and rebindingโ€ฆ ๐Ÿคทโ€โ™€๏ธ

The JWT serviceprovider has this:

    /**
     * Extend Laravel's Auth.
     *
     * @return void
     */
    protected function extendAuthGuard()
    {
        $this->app['auth']->extend('jwt', function ($app, $name, array $config) {
            $guard = new JWTGuard(
                $app['tymon.jwt'],
                $app['auth']->createUserProvider($config['provider']),
                $app['request']
            );

            $app->refresh('request', $guard, 'setRequest');

            return $guard;
        });
    }

This "extend" should not require the createJwtDriver to be called etc. but it seems maybe this is triggered before the JWT driver is fully initialized; hard to say.

I also tried moving this package ServiceProvider before the JWT but no avail.

I temporarily tried to disable the 'api' auth driver (requiring jwt) but the fun thing is that other 'web' driver is also custom and I suffer the same problem.

It seems the auth guard is requested in a scope when the auth stuff is not fully booted?

When I go to \Illuminate\Auth\AuthServiceProvider::register and comment out this line, at least artisan does not crash anymore:

    public function register()
    {
        $this->registerAuthenticator();
        $this->registerUserResolver();
        $this->registerAccessGate();
        $this->registerRequestRebindHandler();
//        $this->registerEventRebindHandler();
    }

Not sure if it makes sense to try to proceed with this package at this point though.

Any idea? ๐Ÿ˜„

Why are Eloquent events excluded?

This is probably a silly question, but why are Eloquent events excluded by default in the config file? I'm calling save on an eloquent model within a transaction, as well as performing some other functions. Sometimes the transaction doesn't commit because an Exception is throw, which means the model is not saved. However, since I am binding an even to the created event of that model, the event is being triggered even if the transaction doesn't commit.

Googling around for solutions led me to this package, which seems great. However, I guess I will have to alter the config so that Eloquent events are not excluded, right?

Is there any reason not to do this? Is there a reason Eloquent events are being excluded? If events dispatched outside of a transaction are not effected, why not have this package work on all events?

Question: seeking clarification regarding Laravels native events

Upfront, thanks for the package!

Re-reading the README and looking at config/transactional-events.php I'm not entirely in the clear what events by default would be handled transactional.

I've also looked at \Neves\Events\TransactionalDispatcher::shouldHandle to get a better picture.

What I want to achieve is a total "opt-in" for transactional events

  • No event without explicit consent should be handled transactionally.
  • Only application specific events should be handled transactionally

My understand is, to achieve that, the following approach would be correct

  • enable in config (doh!)
  • do not listen any events in 'transactional'
  • the value of 'excluded' actual does not matter

Config (condensed a bit):

<?php declare(strict_types = 1);

return [

    'enable' => true,

    'transactional' => [
        // 'App\Events',
    ],

    'excluded' => [
        // 'eloquent.*',
        'eloquent.booted',
        'eloquent.retrieved',
        'eloquent.saved',
        'eloquent.updated',
        'eloquent.created',
        'eloquent.deleted',
        'eloquent.restored',
    ],
];

I think my assumptions are right, however I'm not sure why the excluded lists the eloquent event. Based on the code in the dispatcher, I would not understand why Eloquent events would be considered for transaction in the first place anyway. That would require them to listed in 'transactional', e.g. like eloquent.*, no?

Thanks for clarification!

Lumen compatibility

Hi @fntneves ,

First of all, thank you for providing the package.

I would like to ask if you'd accept a PR which requires individual Illuminate packages instead of the Laravel framework, therefore it would be compatible with Lumen as well.

If so, I am happy to make the changes.

Cheers,
Szasza

drupol/phptree is abandoned

I noticed that you are using drupol/phptree which is no longer maintained in packagist.

This package is abandoned and no longer maintained. The author suggests using the loophp/phptree package instead.

What is the best way to apply to all observers?

Hello, I use observers for every Model, like UserObserver, OrderObserver, etc...

If I modify the excluded setting to 'excluded' => [], all the eloquent events in the Observers will be transactional?

Would you consider approaching Laravel to include this in core?

I'm aware the history of the project originated in trying to have this in Laravel.

Personally the approach used feels very clean and it definitely is production ready.

Also by using the explicit interface to mark events, the addition to Laravel core could be done in almost backwards compatible way (i.e. it could start without the white/blacklist thing), i.e. changing at least no runtime behaviour (as long as users didn't replace it with their own event bus, etc.).

What's your take on this thought?

Wrong parameter to TransactionalDispatcher constructor

Sometimes I'm getting this error:

[27-Mar-2019 00:36:04 UTC] PHP Fatal error: Uncaught TypeError: Argument 2 passed to Neves\Events\TransactionalDispatcher::__construct() must be an instance of Illuminate\Events\Dispatcher, instance of Neves\Events\TransactionalDispatcher given, called in /app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/EventServiceProvider.php on line 29 and defined in /app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:69

If I remove the EventDispatcher type on constructor, it works, so I print a log to test:

/**
 * Create a new transactional event dispatcher instance.
 *
 * @param  \Illuminate\Database\ConnectionResolverInterface $connectionResolver
 * @param  \Illuminate\Contracts\Events\Dispatcher          $eventDispatcher
 */
public function __construct(ConnectionResolverInterface $connectionResolver, $eventDispatcher)
{
    Log::debug(get_class($eventDispatcher));

    $this->connectionResolver = $connectionResolver;
    $this->dispatcher = $eventDispatcher;
    $this->setUpListeners();
}

And got this:

[2019-03-27 11:33:03] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:08] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:33] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:33] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:40] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:42] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:42] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:33:44] local.DEBUG: Neves\Events\TransactionalDispatcher  
[2019-03-27 11:34:00] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:34:00] local.DEBUG: Illuminate\Events\Dispatcher  
[2019-03-27 11:34:00] local.DEBUG: Illuminate\Events\Dispatcher  

Can we do something about this? The most quickly solution is remove the EventDispatcher type from constructor, but I don't know if is the best solution.

Neves\Testing\DatabaseTransactions causes red squiggly in PHPStorm

Versions:

  • PHPStorm 2020.1.2
  • "fntneves/laravel-transactional-events": "^1.8" resolved to 1.8.9
  • "laravel/framework": "^7.0" resolved to 7.11.0
  • "phpunit/phpunit": "^8.5" resolved to 8.5.4

Sample code:

<?php

namespace Tests\Unit;

use App\User;
use Neves\Testing\DatabaseTransactions;
use Tests\TestCase;

class UserTest extends TestCase
{
    use DatabaseTransactions; // squiggly here

    public function testSquiggly()
    {
        $this->assertInstanceOf(
            User::class,
            factory(User::class)->create(),
            'this is not a real test'
        );
    }
}

Screenshot of sample code in PHPStorm:

image

Message shown on squiggly hover:

Trait method 'beginDatabaseTransaction' will not be applied, because it collides with 'DatabaseTransactions'

image

Perhaps this is more of a PHPStorm bug? The method does seem to be applied. Adding dd('foo') to the beginning of it:

    use BaseDatabaseTransactions;

    public function beginDatabaseTransaction()
    {
        dd('foo');
        $emptyDispatcher = new \Illuminate\Events\Dispatcher;

Successfully prints 'foo':

application@68261664050d:/app$ vendor/bin/phpunit --filter UserTest
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.

.."foo"

And no fatal errors are emitted by PHP itself.

Call to a member function getParent() on null

Call to a member function getParent() on null {"userId":3,"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Call to a member function getParent() on null at /var/www/app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:229)
            Bus::pipeThrough([UseDatabaseTransactions::class]);
//            dispatch_now($Job->chain([$Job2, $Job3, $Job4, $Job5]) );
            dispatch_now($Job);
            dispatch_now($Job2);
            dispatch_now($Job3);
            dispatch_now($Job4);
            dispatch_now($Job5);
            Bus::pipeThrough([]);

Failed on line dispatch_now($Job2);

class UseDatabaseTransactions {

    public function handle($command, $next) {
        return DB::transaction(function() use ($command, $next) {
            return $next($command);
        });
    }

}
#0 /var/www/app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php(161): Neves\\Events\\TransactionalDispatcher->finishTransaction()
#1 /var/www/app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php(299): Neves\\Events\\TransactionalDispatcher->onTransactionCommit()
#2 /var/www/app/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(347): Neves\\Events\\TransactionalDispatcher->Neves\\Events\\{closure}(Object(Illuminate\\Database\\Events\\TransactionCommitted))
#3 /var/www/app/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(196): Illuminate\\Events\\Dispatcher->Illuminate\\Events\\{closure}('Illuminate\\\\Data...', Array)
#4 /var/www/app/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php(114): Illuminate\\Events\\Dispatcher->dispatch('Illuminate\\\\Data...', Array, false)
#5 /var/www/app/vendor/laravel/framework/src/Illuminate/Database/Connection.php(813): Neves\\Events\\TransactionalDispatcher->dispatch(Object(Illuminate\\Database\\Events\\TransactionCommitted))
#6 /var/www/app/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php(162): Illuminate\\Database\\Connection->fireConnectionEvent('committed')
#7 /var/www/app/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php(30): Illuminate\\Database\\Connection->commit()
#8 /var/www/app/vendor/laravel/framework/src/Illuminate/Support/helpers.php(1124): Illuminate\\Database\\Connection->Illuminate\\Database\\Concerns\\{closure}(NULL)
#9 /var/www/app/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php(31): tap(NULL, Object(Closure))
#10 /var/www/app/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php(349): Illuminate\\Database\\Connection->transaction(Object(Closure))
#11 /var/www/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(237): Illuminate\\Database\\DatabaseManager->__call('transaction', Array)
#12 /var/www/app/app/Jobs/Pipes/UseDatabaseTransactions.php(12): Illuminate\\Support\\Facades\\Facade::__callStatic('transaction', Array)
#13 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(163): App\\Jobs\\Pipes\\UseDatabaseTransactions->handle(Object(App\\Jobs\\Sender), Object(Closure))
#14 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(App\\Jobs\\Sender))
#15 /var/www/app/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(98): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#16 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(413): Illuminate\\Bus\\Dispatcher->dispatchNow(Object(App\\Jobs\\Sender), false)
#17 /var/www/app/app/Nova/Actions/Signer.php(85): dispatch_now(Object(App\\Jobs\\Sender))

Serialization of 'Closure' is not allowed

I don't have time to get more information right now, had to remove the package. Will want it back in soon so will be back to debug and give more information.

Will also get better stack tracers with just the relevant information later.

Laravel 5.7.

Exception Serialization of 'Closure' is not allowed 
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:138 serialize
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:138 Illuminate\Queue\Queue::createObjectPayload
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:110 Illuminate\Queue\Queue::createPayloadArray
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php:150 Illuminate\Queue\RedisQueue::createPayloadArray
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:88 Illuminate\Queue\Queue::createPayload
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php:91 Illuminate\Queue\RedisQueue::push
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/horizon/src/RedisQueue.php:46 Laravel\Horizon\RedisQueue::push
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:44 Illuminate\Queue\Queue::pushOn
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php:128 Illuminate\Broadcasting\BroadcastManager::queue
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:280 Illuminate\Events\Dispatcher::broadcastEvent
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:203 Illuminate\Events\Dispatcher::dispatch
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:171 Neves\Events\TransactionalDispatcher::dispatchPendingEvents
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:114 Neves\Events\TransactionalDispatcher::commit
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:409 Neves\Events\TransactionalDispatcher::Neves\Events\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:360 Illuminate\Events\Dispatcher::Illuminate\Events\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php:209 Illuminate\Events\Dispatcher::dispatch
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:92 Neves\Events\TransactionalDispatcher::dispatch
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Database/Connection.php:813 Illuminate\Database\Connection::fireConnectionEvent
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:162 Illuminate\Database\Connection::commit
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:30 Illuminate\Database\Connection::Illuminate\Database\Concerns\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Support/helpers.php:1027 tap
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:31 Illuminate\Database\Connection::transaction
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:327 Illuminate\Database\DatabaseManager::__call
    helpers.php:105 db_transaction
    Http/Controllers/ContentArticleRevision/ApprovedController.php:43 App\Http\Controllers\ContentArticleRevision\ApprovedController::store
    [internal] call_user_func_array
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Controller.php:54 Illuminate\Routing\Controller::callAction
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php:45 Illuminate\Routing\ControllerDispatcher::dispatch
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Route.php:219 Illuminate\Routing\Route::runController
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Route.php:176 Illuminate\Routing\Route::run
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Router.php:682 Illuminate\Routing\Router::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:30 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    Http/Middleware/Visitor.php:50 App\Http\Middleware\Visitor::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    Http/Middleware/Authenticate.php:80 App\Http\Middleware\Authenticate::handleMetaData
    Http/Middleware/Authenticate.php:45 App\Http\Middleware\Authenticate::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/barryvdh/laravel-cors/src/HandleCors.php:36 Barryvdh\Cors\HandleCors::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php:41 Illuminate\Routing\Middleware\SubstituteBindings::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:104 Illuminate\Pipeline\Pipeline::then
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Router.php:684 Illuminate\Routing\Router::runRouteWithinStack
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Router.php:659 Illuminate\Routing\Router::runRoute
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Router.php:625 Illuminate\Routing\Router::dispatchToRoute
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Router.php:614 Illuminate\Routing\Router::dispatch
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:176 Illuminate\Foundation\Http\Kernel::Illuminate\Foundation\Http\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:30 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/nova/src/Http/Middleware/ServeNova.php:26 Laravel\Nova\Http\Middleware\ServeNova::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/fideloper/proxy/src/TrustProxies.php:57 Fideloper\Proxy\TrustProxies::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    Http/Middleware/LogRequest.php:61 App\Http\Middleware\LogRequest::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:31 Illuminate\Foundation\Http\Middleware\TransformsRequest::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php:27 Illuminate\Foundation\Http\Middleware\ValidatePostSize::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php:62 Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/barryvdh/laravel-cors/src/HandlePreflight.php:29 Barryvdh\Cors\HandlePreflight::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:163 Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53 Illuminate\Routing\Pipeline::Illuminate\Routing\{closure}
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:104 Illuminate\Pipeline\Pipeline::then
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:151 Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter
    /var/www/partner.postedin.com/releases/20190228205803/api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:116 Illuminate\Foundation\Http\Kernel::handle
    /var/www/partner.postedin.com/releases/20190228205803/api/public/index.php:55 [main]

Issue when transaction done on non-default connection

Brief

When inside a transaction on non-default database connection, events dispatched are not actually dispatched after transaction commit.


Debug

Reproducible with following sample snippet.

DB::connection('zone-b')->transaction(function() { event(new \App\Events\SampleEvent); });

In below line we use ->connection() method of resolver class which returns default connection i.e. connection against default connection name (say zone-a, refer config/database.php). And hence from the implementation it never is pushed in $pendingEvents.

public function dispatch($event, $payload = [], $halt = false)
{
$connection = $this->connectionResolver->connection();


Proposed Fix

Update:

  1. Using a stack of connection names/ids. Every transaction start, push it over, every commit/rollback dispatch/discard pending events of popped txn. How is it?

Please remove the global function introduced with 1.8.8

See https://github.com/fntneves/laravel-transactional-events/releases/tag/1.8.8

This is a very sad move. The Laravel community suffers and tries really hard to get rid of all the global helper functions. There are countless occurrences of this laravel/framework issues and PRs introducing global helper function are closed (almost?) all cases. That's also the reason why some of the helpers were moved into a dedicated package ( https://github.com/laravel/helpers )

It wouldn't be such a problem if it were optional, but it isn't: it's forced upon users on installation, the helpers.php file through composer is forcefully loaded.

It uses function_exists but you will find it's actually not easy to truly override this.

I would like to ask to reconsider this addition, thank you!

Update location of drupol/phptree package

Hi @fntneves,

The package drupol/phptree has moved from drupol/phptree to loophp/phptree.

drupol/phptree has been deprecated on Packagist and the new package should be suggested already.
Don't forget to update your composer.json file.

Let me know if I can be of any help and sorry for the disturbance.

Thanks for using my package!

Code seems to be bugged

Hi, I didn't tested if it works properly but I found something weird, which seems to bu a bug.
Neves\Events\TransactionalDispatcher::128, theres a code like follows:

$this->currentTransaction = $this->isTransactionRunning()
            ? $this->currentTransaction->add($transactionNode)
            : $transactionNode;

        $this->currentTransaction = $transactionNode;

First statements assigns value to class field conditionally while statement below immediately overwrites it.

Hello Help needed On using it on Model Observers, not custom events

Hi thanks for this package
It looks like exactly what I want, the only problem is I am using model observers not events,
so how can I use it in an Observer Class for a given model.

the idea is created method in the observer is only fired when a commit is successful, same with updated and others

Thanks

Is this package still needed on Laravel 10.x

I am using this amazing package for a long time, but I don't know if it is still needed on Laravel 10.x.

The laravel docs says:

Setting the after_commit configuration option to true will also cause any queued event listeners, mailables, notifications, and broadcast events to be dispatched after all open database transactions have been committed.

@fntneves could you please help with this question?

Nested multiple connections problems.

Hi! I have many databases in one app. But there's something wrong with laravel-transactional-events when nested multiple connections. I report these cases.

OK (Single connection)

\DB::connection('logdb')->transaction(function() {
    event(new \App\Events\EventA);
    event(new \App\Events\EventB);
});

Of course it is no problems. I got both events.

NG (Exception when nested multiple connections)

\DB::connection('logdb')->transaction(function() {
    \DB::transaction(function() {
        event(new \App\Events\EventA);
    });
    event(new \App\Events\EventB);
});

I got exception below.

ErrorException: count(): Parameter must be an array or an object that implements Countable in /vagrant/server/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php:152
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(2, 'count(): Parame...', '/vagrant/server...', 152, Array)
#1 /vagrant/server/vendor/fntneves/laravel-transactional-events/src/Neves/Events/TransactionalDispatcher.php(152): count(NULL)

NG (Inner event not fired when nested multiple connections)

\DB::connection('logdb')->transaction(function() {
    event(new \App\Events\EventB);
    \DB::transaction(function() {
        event(new \App\Events\EventA);
    });
});

I got EventB only. EventA is not fired.

My Environment

  • laravel 5.8.10
  • laravel-transactional-events 1.8.0
  • CentOS 7
  • MySQL 8.0

Dingo Api

Hello.
After installing the package, I get a conflict:
Argument 1 passed to Dingo\Api\Http\Response::setEventDispatcher() must be an instance of Illuminate\Events\Dispatcher, instance of Neves\Events\TransactionalDispatcher given, called in ../vendor/dingo/api/src/Provider/DingoServiceProvider.php on line 37

Problem with DatabaseTransactions

Hello,

In my tests I am using DatabaseTransactions trait. Because of that I still have a problem that you resolved with Neves\RefreshDatabase trait. All events are fired after the outer transaction which happens after the end of the test.

Could you create a fix for DatabaseTransactions trait as well?

Using DatabaseTransactions

Hello,

I use DatabaseTransactions trait which does not work with TransactionalEvent. Seems like it's solved if I use Neves\RefreshDatabase trait but I am trying to avoid using RefreshDatabase since I have a lot of migration files & rerunning migrations for each test takes a lot of time. Is there a fix for DatabaseTransactions?

Keys in transactional-events.php are not up-to-date

Hey,
When setting laravel-transactional-events up, I found that the keys in the config-file where not up-to-date.
See patch. I'll create a pull request in a second. :-)

Index: src/config/transactional-events.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/config/transactional-events.php	(date 1507679746000)
+++ src/config/transactional-events.php	(date 1514360704000)
@@ -16,11 +16,11 @@
 
     'enable' => true,
 
-    'events' => [
+    'transactional' => [
         'App\Events',
     ],
 
-    'exclude' => [
+    'excluded' => [
         //
     ],
 ];

Kind regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.