Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ef72a1d
Merge pull request #65 from phenixphp/release/0.6.0
barbosa89 Aug 22, 2025
02126bb
refactor: reorder use statements in controller.stub for consistency
barbosa89 Aug 28, 2025
c577567
feat: include output file path in success message
barbosa89 Aug 28, 2025
9218b5e
fix: correct output directory path for seeders
barbosa89 Aug 28, 2025
f9057ac
Merge pull request #66 from phenixphp/feature/improve-make-commands
barbosa89 Aug 30, 2025
243dce3
feat: event system
barbosa89 Sep 11, 2025
12c8b6d
tests: make event command
barbosa89 Sep 11, 2025
d741479
tests: make listener command
barbosa89 Sep 11, 2025
08a9b11
refactor: update EventListener and AbstractListener for consistency i…
barbosa89 Sep 12, 2025
2543e7a
refactor: add setPriority method to EventListener interface and Abstr…
barbosa89 Sep 12, 2025
49682fc
refactor: streamline handle method in EventListener for improved read…
barbosa89 Sep 12, 2025
f711cee
refactor: normalize priority assignment in AbstractListener and Event…
barbosa89 Sep 12, 2025
edc1ec6
refactor: remove unnecessary docblock from shouldHandle method in lis…
barbosa89 Sep 17, 2025
657214a
refactor: remove timestamp handling from AbstractEvent and move it to…
barbosa89 Sep 17, 2025
819cd0f
refactor: clean up whitespace in emitAsync method for improved readab…
barbosa89 Sep 17, 2025
d5c64a0
refactor: add has method to App class and improve event listener hand…
barbosa89 Sep 18, 2025
7f57ab5
refactor: remove event constructor
barbosa89 Sep 18, 2025
48590a7
refactor: simplify event listener handling and remove unused exceptio…
barbosa89 Sep 18, 2025
40005d2
test: add test for registering and emitting events with facade syntax
barbosa89 Sep 18, 2025
eef9ad8
test: add test for removing non-registered event listeners
barbosa89 Sep 18, 2025
548ba7e
refactor: remove unused shouldHandle method from listener stub
barbosa89 Sep 18, 2025
9237629
test: add test to skip listener when shouldHandle returns false
barbosa89 Sep 18, 2025
2dfeb97
feat: add fake method to extend container with mock implementations
barbosa89 Sep 18, 2025
89c019c
feat: add shouldReceive method to Log facade for mocking expectations
barbosa89 Sep 18, 2025
872cc28
test: add test to handle listener error gracefully and log error
barbosa89 Sep 18, 2025
730fbbf
feat: add async event registration and emission test
barbosa89 Sep 18, 2025
1503189
test: add test for async event propagation handling
barbosa89 Sep 19, 2025
10d8d21
refactor: improve test description for event handling
barbosa89 Sep 19, 2025
eadfd87
test: add async event handling for listeners that should not be trigg…
barbosa89 Sep 19, 2025
a31d046
test: add async handling for listener errors and ensure proper logging
barbosa89 Sep 19, 2025
c9cc22d
test: enable warning emission for listener errors in async events
barbosa89 Sep 19, 2025
c3e8cf7
test: add async handling for one-time listeners and ensure proper rem…
barbosa89 Sep 25, 2025
65aefbf
test: add handling for listener errors in async events without warnings
barbosa89 Sep 25, 2025
3e55637
refactor: remove unnecessary check for event listeners in sortListene…
barbosa89 Sep 25, 2025
bbd318e
test: add warning for exceeding maximum number of listeners for an event
barbosa89 Sep 25, 2025
4d63e53
test: enable warning emission when exceeding maximum number of listeners
barbosa89 Sep 25, 2025
cfe099d
test: add check for warning suppression when exceeding maximum listeners
barbosa89 Sep 25, 2025
7d070ba
refactor: simplify removeListener method by removing unnecessary chec…
barbosa89 Sep 25, 2025
00f0fb9
refactor: simplify resolveListener method and improve conditional che…
barbosa89 Sep 25, 2025
ca5f2d1
test: add test for registering and emitting events with string-class …
barbosa89 Sep 25, 2025
cc041ed
refactor: remove unused has method from App class
barbosa89 Sep 25, 2025
eb814b0
style: php cs
barbosa89 Sep 25, 2025
0e1444b
refactor: remove debug dump from string-class listeners test
barbosa89 Sep 25, 2025
3280683
test: add test for handling invalid listeners and create InvalidListe…
barbosa89 Sep 25, 2025
887a60a
test: add test for registering and emitting string-class events
barbosa89 Sep 25, 2025
a4926d2
refactor: remove __toString method from AbstractEvent class
barbosa89 Sep 25, 2025
57e974f
test: add test for registering and emitting events with custom listen…
barbosa89 Sep 25, 2025
c0da37a
test: add assertion for event timestamp in Event handling test
barbosa89 Sep 25, 2025
3e1a87c
style: php cs
barbosa89 Sep 25, 2025
8839695
refactor: remove unused Log facade import from listener stub [skip ci]
barbosa89 Sep 25, 2025
3b1d258
Merge pull request #67 from phenixphp/feature/event-system
barbosa89 Sep 25, 2025
296fcf3
Merge branch 'main' of github.com:phenixphp/framework into release/0.7.0
barbosa89 Sep 26, 2025
190cda1
docs: update CHANGELOG for v0.7.0 release notes
barbosa89 Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

# Release Notes for 0.7.x

## [v0.7.0 (2025-09-26)](https://github.com/phenixphp/framework/compare/0.6.0...0.7.0)

### Added

- Event system. ([#67](https://github.com/phenixphp/framework/pull/67))

# Release Notes for 0.6.x

## [v0.6.0 (2025-08-22)](https://github.com/phenixphp/framework/compare/0.5.2...0.6.0)
Expand Down
7 changes: 7 additions & 0 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Amp\Socket;
use League\Container\Container;
use League\Uri\Uri;
use Mockery\LegacyMockInterface;
use Mockery\MockInterface;
use Monolog\Logger;
use Phenix\Console\Phenix;
use Phenix\Contracts\App as AppContract;
Expand Down Expand Up @@ -105,6 +107,11 @@ public static function make(string $key): object
return self::$container->get($key);
}

public static function fake(string $key, LegacyMockInterface|MockInterface $concrete): void
{
self::$container->extend($key)->setConcrete($concrete);
}

public static function path(): string
{
return self::$path;
Expand Down
4 changes: 3 additions & 1 deletion src/Console/Commands/MakeModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int

File::put($filePath, $stub);

$output->writeln(["<info>{$this->commonName()} successfully generated!</info>", self::EMPTY_LINE]);
$outputPath = str_replace(base_path(), '', $filePath);

$output->writeln(["<info>{$this->commonName()} [{$outputPath}] successfully generated!</info>", self::EMPTY_LINE]);

return parent::SUCCESS;
}
Expand Down
4 changes: 3 additions & 1 deletion src/Console/Maker.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int

File::put($filePath, $stub);

$output->writeln(["<info>{$this->commonName()} successfully generated!</info>", self::EMPTY_LINE]);
$outputPath = str_replace(base_path(), '', $filePath);

$output->writeln(["<info>{$this->commonName()} [{$outputPath}] successfully generated!</info>", self::EMPTY_LINE]);

return Command::SUCCESS;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Console/MakeSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected function configure(): void

protected function outputDirectory(): string
{
return 'database' . DIRECTORY_SEPARATOR . 'seeds';
return 'database' . DIRECTORY_SEPARATOR . 'seeders';
}

protected function stub(): string
Expand Down
34 changes: 34 additions & 0 deletions src/Events/AbstractEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Phenix\Events;

use Phenix\Events\Contracts\Event as EventContract;

abstract class AbstractEvent implements EventContract
{
protected mixed $payload = null;

protected bool $propagationStopped = false;

public function getName(): string
{
return static::class;
}

public function getPayload(): mixed
{
return $this->payload;
}

public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}

public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
57 changes: 57 additions & 0 deletions src/Events/AbstractListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Phenix\Events;

use Closure;
use Phenix\Events\Contracts\Event;
use Phenix\Events\Contracts\EventListener as EventListenerContract;

abstract class AbstractListener implements EventListenerContract
{
protected int $priority = 0;

protected bool $once = false;

abstract public function handle(Event $event): mixed;

public function setPriority(int $priority): self
{
$this->priority = $this->normalizePriority($priority);

return $this;
}

public function getPriority(): int
{
return $this->priority;
}

public function shouldHandle(Event $event): bool
{
return true;
}

public function isOnce(): bool
{
return $this->once;
}

public function setOnce(bool $once = true): self
{
$this->once = $once;

return $this;
}

public function getHandler(): Closure|static|string
{
return $this;
}

protected function normalizePriority(int $priority): int
{
return max(0, min($priority, 100));
}
}
39 changes: 39 additions & 0 deletions src/Events/Console/MakeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Phenix\Events\Console;

use Phenix\Console\Maker;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

#[AsCommand(
name: 'make:event',
description: 'Create a new event class'
)]
class MakeEvent extends Maker
{
protected function configure(): void
{
$this->addArgument('name', InputArgument::REQUIRED, 'The name of the event');

$this->addOption('force', 'f', InputOption::VALUE_NONE, 'Force to create event');
}

protected function outputDirectory(): string
{
return 'app' . DIRECTORY_SEPARATOR . 'Events';
}

protected function commonName(): string
{
return 'Event';
}

protected function stub(): string
{
return 'event.stub';
}
}
39 changes: 39 additions & 0 deletions src/Events/Console/MakeListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Phenix\Events\Console;

use Phenix\Console\Maker;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

#[AsCommand(
name: 'make:listener',
description: 'Create a new event listener class'
)]
class MakeListener extends Maker
{
protected function configure(): void
{
$this->addArgument('name', InputArgument::REQUIRED, 'The name of the listener');

$this->addOption('force', 'f', InputOption::VALUE_NONE, 'Force to create listener');
}

protected function outputDirectory(): string
{
return 'app' . DIRECTORY_SEPARATOR . 'Listeners';
}

protected function commonName(): string
{
return 'Event Listener';
}

protected function stub(): string
{
return 'listener.stub';
}
}
16 changes: 16 additions & 0 deletions src/Events/Contracts/Event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Phenix\Events\Contracts;

interface Event
{
public function getName(): string;

public function getPayload(): mixed;

public function isPropagationStopped(): bool;

public function stopPropagation(): void;
}
27 changes: 27 additions & 0 deletions src/Events/Contracts/EventEmitter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Phenix\Events\Contracts;

use Amp\Future;
use Closure;

interface EventEmitter
{
public function on(string $event, Closure|EventListener|string $listener, int $priority = 0): void;

public function once(string $event, Closure|EventListener|string $listener, int $priority = 0): void;

public function off(string $event, Closure|EventListener|string|null $listener = null): void;

public function emit(string|Event $event, mixed $payload = null): array;

public function emitAsync(string|Event $event, mixed $payload = null): Future;

public function getListeners(string $event): array;

public function hasListeners(string $event): bool;

public function removeAllListeners(): void;
}
24 changes: 24 additions & 0 deletions src/Events/Contracts/EventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Phenix\Events\Contracts;

use Closure;

interface EventListener
{
public function handle(Event $event): mixed;

public function getPriority(): int;

public function setPriority(int $priority): self;

public function shouldHandle(Event $event): bool;

public function isOnce(): bool;

public function setOnce(bool $once = true): self;

public function getHandler(): Closure|static|string;
}
28 changes: 28 additions & 0 deletions src/Events/Event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Phenix\Events;

class Event extends AbstractEvent
{
protected float $timestamp;

public function __construct(
protected string $name,
mixed $payload = null
) {
$this->payload = $payload;
$this->timestamp = microtime(true);
}

public function getTimestamp(): float
{
return $this->timestamp;
}

public function getName(): string
{
return $this->name;
}
}
Loading