Skip to content

Commit

Permalink
~ Fixed configured events
Browse files Browse the repository at this point in the history
  • Loading branch information
tylernathanreed committed Jun 21, 2021
1 parent 960689c commit 107b968
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 37 deletions.
4 changes: 2 additions & 2 deletions config/events.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@
// App\Subscribers\UserEventSubscriber::class => [
// Illuminate\Auth\Events\Login::class, // Implicit
// Illuminate\Auth\Events\Logout::class // Implicit
// ]
// ],

// App\Subscribers\UserEventSubscriber::class => [
// [Illuminate\Auth\Events\Login::class::class, 'handleUserLogin'] // Explicit
// [Illuminate\Auth\Events\Login::class, 'handleUserLogin'] // Explicit
// [Illuminate\Auth\Events\Logout::class, 'handleUserLogout'] // Explicit
// ]

Expand Down
36 changes: 35 additions & 1 deletion src/ConfiguredEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@

trait ConfiguredEvents
{
/**
* Register the application's event listeners.
*
* @return void
*/
public function register()
{
$this->booting(function () {
$this->registerEvents();
});
}

/**
* Register the application's event listeners.
*
* @return void
*/
protected function registerEvents()
{
$dispatcher = $this->app->make('events');

$events = $this->getEvents();

foreach ($events as $event => $listeners) {
foreach ($listeners as $listener) {
$dispatcher->listen($event, $listener);
}
}

foreach ($this->subscribe as $subscriber) {
$dispatcher->subscribe($subscriber);
}
}

/**
* Returns the events and handlers.
*
Expand All @@ -19,7 +53,7 @@ public function listens()
*
* @return array
*/
protected function configuredEvents()
public function configuredEvents()
{
// Determine the event configuration
$config = $this->app->config->get('events');
Expand Down
19 changes: 15 additions & 4 deletions src/NormalizeEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,22 @@ public static function normalizeListener($listener, $event)
// isn't the case, we're going to bail, as we don't know how
// to handle what the developer has provided to us. Fun.

// Make sure the listener is valid
if(!is_string($listener) || !class_exists($listener)) {
throw new InvalidArgumentException('Argument #1 passed to normalizeListener() must be a valid listener.');
// If the listener isn't a string, bail
if(!is_string($listener)) {
throw new InvalidArgumentException('Listener [' . json_encode($listener) . '] is invalid.');
}

// If the listener is a string, but not a class, bail
if(is_string($listener) && !class_exists($listener)) {
throw new InvalidArgumentException("Listener class [{$listener}] does not exist.");
}

// Reflect the listener
$listener = new ReflectionClass($listener);

// Make sure the listener can be instantiated
if(!$listener->isInstantiable()) {
throw new InvalidArgumentException('Argument #1 passed to normalizeListener() must be a valid listener.');
throw new InvalidArgumentException("Listener class [{$listener}] cannot be instantiated.");
}

// At this point, we need to determine which method to use. We
Expand Down Expand Up @@ -275,9 +280,15 @@ public static function normalizeModelMapping(array $models)
* @param string $model
*
* @return array
*
* @throws \InvalidArgumentException
*/
public static function getObservableEvents(string $observer, string $model)
{
if(!class_exists($observer)) {
throw new InvalidArgumentException("Observer class [{$observer}] does not exist.");
}

return array_values(array_filter((new $model)->getObservableEvents(), function($event) use ($observer) {
return method_exists($observer, $event);
}));
Expand Down
128 changes: 128 additions & 0 deletions tests/ConfiguredEventsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Reedware\LaravelEvents\Tests;

use Illuminate\Config\Repository;
use Illuminate\Container\Container;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Foundation\Application;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use Reedware\LaravelEvents\EventServiceProvider;
use Reedware\LaravelEvents\NormalizeEvents;
use ReflectionFunction;

class ConfiguredEventsTest extends TestCase
{
protected $app;
protected $provider;

protected function setUp(): void
{
parent::setUp();

$this->app = new Application(__DIR__);
$this->app->instance('config', $config = new Repository);

$config->set('events', require realpath(__DIR__ . DIRECTORY_SEPARATOR . 'events.php'));

$this->app->singleton('files', function() {
return tap(m::mock(Filesystem::class), function($mock) {
$mock->shouldReceive('exists')
->withAnyArgs()
->andReturn(false);
});
});

$this->provider = $this->app->register(EventServiceProvider::class);
$this->app->boot();
}

public function testConfiguredEvents()
{
$config = $this->app->make('config')->get('events');
$provided = $this->provider->configuredEvents();

foreach($config['listen'] as $event => $listeners) {
foreach($listeners as $listener) {
$normalized = NormalizeEvents::normalizeListener($listener, $event);
$this->assertTrue(in_array($normalized, $provided[$event]));
}
}

foreach($config['subscribe'] as $subscriber => $events) {
foreach($events as $event) {
[$event, $method] = is_array($event) ? $event : [$event, 'handle'];
$normalized = NormalizeEvents::normalizeListener([$subscriber, $method], $event);
$this->assertTrue(in_array($normalized, $provided[$event]));
}
}

foreach($config['observe'] as $observer => $models) {
foreach($models as $model) {
$observes = NormalizeEvents::getObservableEvents($observer, $model);
foreach($observes as $method) {
$this->assertTrue(in_array([$observer, $method], $provided["eloquent.{$method}: {$model}"]));
}
}
}

foreach($config['models'] as $model => $observers) {
foreach($observers as $observer) {
$observes = NormalizeEvents::getObservableEvents($observer, $model);
foreach($observes as $method) {
$this->assertTrue(in_array([$observer, $method], $provided["eloquent.{$method}: {$model}"]));
}
}
}
}

public function testEventRegistration()
{
$events = $this->provider->configuredEvents();
$dispatcher = $this->app->make('events');

foreach($events as $event => $configured) {

$registered = array_map(function($listener) {
return $this->unmakeListener($listener);
}, $dispatcher->getListeners($event));

foreach($configured as $listener) {
$this->assertTrue(
in_array($listener, $registered),
sprintf('Listener [%s] cannot be found within [%s]',
json_encode($listener),
json_encode($registered)
)
);
}

}
}

protected function unmakeListener($listener)
{
$r = new ReflectionFunction($listener);

$use = $r->getStaticVariables();

return $use['listener'];
}
}

namespace App\Listeners;
class SendAnotherNotification {}
class UserEventSubscriber {}

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {}
class User extends Model {}

namespace App\Observers;
class UserObserver { function created(){} }
class PostObserver { function created(){} }

namespace App\Subscribers;
class InverseUserEventSubscriber {}
30 changes: 0 additions & 30 deletions tests/NormalizeEventsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,12 @@

namespace Reedware\LaravelEvents\Tests;

use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Database\Query\Processors\Processor;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use Reedware\LaravelEvents\NormalizeEvents;

class NormalizeEventsTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
}

protected function tearDown(): void
{
m::close();
}

protected function setUpConnectionResolver()
{
BasicModel::setConnectionResolver($resolver = m::mock(ConnectionResolverInterface::class));

$resolver->shouldReceive('connection')->andReturn($mockConnection = m::mock(Connection::class));

$mockConnection->shouldReceive('getQueryGrammar')->andReturn($grammar = new Grammar);
$mockConnection->shouldReceive('getPostProcessor')->andReturn($mockProcessor = m::mock(Processor::class));
$mockConnection->shouldReceive('query')->andReturnUsing(function () use ($mockConnection, $grammar, $mockProcessor) {
return new Builder($mockConnection, $grammar, $mockProcessor);
});
}


public function testNormalizeListenerWithArray()
{
$listener = ['MyClass', 'myMethod'];
Expand Down
19 changes: 19 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Reedware\LaravelEvents\Tests;

use Mockery as m;
use PHPUnit\Framework\TestCase as TestBase;

abstract class TestCase extends TestBase
{
protected function setUp(): void
{
parent::setUp();
}

protected function tearDown(): void
{
m::close();
}
}
91 changes: 91 additions & 0 deletions tests/events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

return [

/*
|--------------------------------------------------------------------------
| Event / Listener Mapping
|--------------------------------------------------------------------------
|
| An event must be explictly listened to by a listener in order for that
| listener to be able to handle its event. You can register events &
| listeners by mapping events their respective listeners below.
|
*/

'listen' => [

Illuminate\Auth\Events\Registered::class => [
Illuminate\Auth\Listeners\SendEmailVerificationNotification::class,
App\Listeners\SendAnotherNotification::class,
],

Illuminate\Auth\Events\Login::class => [
App\Listeners\UserEventSubscriber::class,
],

Illuminate\Auth\Events\Logout::class => [
[App\Listeners\UserEventSubscriber::class, 'handleUserLogout']
],

],

/*
|--------------------------------------------------------------------------
| Subscriber / Event Mapping
|--------------------------------------------------------------------------
|
| If you prefer to list out subscribers with their explicit events, rather
| than listing the subscriber under each event like above, you instead
| can list them here as an alternative. Do what makes sense to you.
|
*/

'subscribe' => [

App\Subscribers\InverseUserEventSubscriber::class => [
Illuminate\Auth\Events\Login::class,
[Illuminate\Auth\Events\Logout::class, 'handleUserLogout']
]

],

/*
|--------------------------------------------------------------------------
| Observer / Model Mapping
|--------------------------------------------------------------------------
|
| In certain cases, it may be fitting to listen to one or many events used
| by eloquent models. This can also be done using the observer design
| pattern. Observers can have many models, simply list them below.
|
*/

'observe' => [

App\Observers\UserObserver::class => [
App\Models\User::class
]

],

/*
|--------------------------------------------------------------------------
| Model / Observer Mapping
|--------------------------------------------------------------------------
|
| If you prefer to list out models with their explicit observers, rather
| than listing each model under each observer like above, you instead
| can list them here as an alternative. Do what makes sense to you.
|
*/

'models' => [

App\Models\Post::class => [
App\Observers\PostObserver::class
]

]

];

0 comments on commit 107b968

Please sign in to comment.