mirror of https://github.com/flightphp/core
parent
d6854cb078
commit
5068995d17
@ -0,0 +1,23 @@
|
||||
name: Pull Request Check
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
name: Unit testing
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: curl, mbstring
|
||||
tools: composer:v2
|
||||
- run: composer install
|
||||
- run: composer test
|
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
class EventDispatcher
|
||||
{
|
||||
/** @var self|null Singleton instance of the EventDispatcher */
|
||||
private static ?self $instance = null;
|
||||
|
||||
/** @var array<string, array<int, callable>> */
|
||||
protected array $listeners = [];
|
||||
|
||||
/**
|
||||
* Singleton instance of the EventDispatcher.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback for an event.
|
||||
*
|
||||
* @param string $event Event name
|
||||
* @param callable $callback Callback function
|
||||
*/
|
||||
public function on(string $event, callable $callback): void
|
||||
{
|
||||
if (isset($this->listeners[$event]) === false) {
|
||||
$this->listeners[$event] = [];
|
||||
}
|
||||
$this->listeners[$event][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event with optional arguments.
|
||||
*
|
||||
* @param string $event Event name
|
||||
* @param mixed ...$args Arguments to pass to the callbacks
|
||||
*/
|
||||
public function trigger(string $event, ...$args): void
|
||||
{
|
||||
if (isset($this->listeners[$event]) === true) {
|
||||
foreach ($this->listeners[$event] as $callback) {
|
||||
$result = call_user_func_array($callback, $args);
|
||||
|
||||
// If you return false, it will break the loop and stop the other event listeners.
|
||||
if ($result === false) {
|
||||
break; // Stop executing further listeners
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event has any registered listeners.
|
||||
*
|
||||
* @param string $event Event name
|
||||
*
|
||||
* @return bool True if the event has listeners, false otherwise
|
||||
*/
|
||||
public function hasListeners(string $event): bool
|
||||
{
|
||||
return isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all listeners registered for a specific event.
|
||||
*
|
||||
* @param string $event Event name
|
||||
*
|
||||
* @return array<int, callable> Array of callbacks registered for the event
|
||||
*/
|
||||
public function getListeners(string $event): array
|
||||
{
|
||||
return $this->listeners[$event] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all events that have registered listeners.
|
||||
*
|
||||
* @return array<int, string> Array of event names
|
||||
*/
|
||||
public function getAllRegisteredEvents(): array
|
||||
{
|
||||
return array_keys($this->listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific listener for an event.
|
||||
*
|
||||
* @param string $event the event name
|
||||
* @param callable $callback the exact callback to remove
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeListener(string $event, callable $callback): void
|
||||
{
|
||||
if (isset($this->listeners[$event]) === true && count($this->listeners[$event]) > 0) {
|
||||
$this->listeners[$event] = array_filter($this->listeners[$event], function ($listener) use ($callback) {
|
||||
return $listener !== $callback;
|
||||
});
|
||||
$this->listeners[$event] = array_values($this->listeners[$event]); // Re-index the array
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all listeners for a specific event.
|
||||
*
|
||||
* @param string $event the event name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeAllListeners(string $event): void
|
||||
{
|
||||
if (isset($this->listeners[$event]) === true) {
|
||||
unset($this->listeners[$event]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current singleton instance of the EventDispatcher.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function resetInstance(): void
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
|
||||
class UploadedFile
|
||||
{
|
||||
/**
|
||||
* @var string $name The name of the uploaded file.
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var string $mimeType The MIME type of the uploaded file.
|
||||
*/
|
||||
private string $mimeType;
|
||||
|
||||
/**
|
||||
* @var int $size The size of the uploaded file in bytes.
|
||||
*/
|
||||
private int $size;
|
||||
|
||||
/**
|
||||
* @var string $tmpName The temporary name of the uploaded file.
|
||||
*/
|
||||
private string $tmpName;
|
||||
|
||||
/**
|
||||
* @var int $error The error code associated with the uploaded file.
|
||||
*/
|
||||
private int $error;
|
||||
|
||||
/**
|
||||
* Constructs a new UploadedFile object.
|
||||
*
|
||||
* @param string $name The name of the uploaded file.
|
||||
* @param string $mimeType The MIME type of the uploaded file.
|
||||
* @param int $size The size of the uploaded file in bytes.
|
||||
* @param string $tmpName The temporary name of the uploaded file.
|
||||
* @param int $error The error code associated with the uploaded file.
|
||||
*/
|
||||
public function __construct(string $name, string $mimeType, int $size, string $tmpName, int $error)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->mimeType = $mimeType;
|
||||
$this->size = $size;
|
||||
$this->tmpName = $tmpName;
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client-side filename of the uploaded file.
|
||||
*
|
||||
* @return string The client-side filename.
|
||||
*/
|
||||
public function getClientFilename(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the media type of the uploaded file as provided by the client.
|
||||
*
|
||||
* @return string The media type of the uploaded file.
|
||||
*/
|
||||
public function getClientMediaType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the uploaded file.
|
||||
*
|
||||
* @return int The size of the uploaded file.
|
||||
*/
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the temporary name of the uploaded file.
|
||||
*
|
||||
* @return string The temporary name of the uploaded file.
|
||||
*/
|
||||
public function getTempName(): string
|
||||
{
|
||||
return $this->tmpName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code associated with the uploaded file.
|
||||
*
|
||||
* @return int The error code.
|
||||
*/
|
||||
public function getError(): int
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the uploaded file to the specified target path.
|
||||
*
|
||||
* @param string $targetPath The path to move the file to.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function moveTo(string $targetPath): void
|
||||
{
|
||||
if ($this->error !== UPLOAD_ERR_OK) {
|
||||
throw new Exception($this->getUploadErrorMessage($this->error));
|
||||
}
|
||||
|
||||
$isUploadedFile = is_uploaded_file($this->tmpName) === true;
|
||||
if (
|
||||
$isUploadedFile === true
|
||||
&&
|
||||
move_uploaded_file($this->tmpName, $targetPath) === false
|
||||
) {
|
||||
throw new Exception('Cannot move uploaded file'); // @codeCoverageIgnore
|
||||
} elseif ($isUploadedFile === false && getenv('PHPUNIT_TEST')) {
|
||||
rename($this->tmpName, $targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the error message for a given upload error code.
|
||||
*
|
||||
* @param int $error The upload error code.
|
||||
*
|
||||
* @return string The error message.
|
||||
*/
|
||||
protected function getUploadErrorMessage(int $error): string
|
||||
{
|
||||
switch ($error) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return 'The uploaded file was only partially uploaded.';
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
return 'No file was uploaded.';
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
return 'Missing a temporary folder.';
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
return 'Failed to write file to disk.';
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
return 'A PHP extension stopped the file upload.';
|
||||
default:
|
||||
return 'An unknown error occurred. Error code: ' . $error;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\tests;
|
||||
|
||||
use Flight;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use flight\Engine;
|
||||
use TypeError;
|
||||
|
||||
class EventSystemTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Reset the Flight engine before each test to ensure a clean state
|
||||
Flight::setEngine(new Engine());
|
||||
Flight::app()->init();
|
||||
Flight::eventDispatcher()->resetInstance(); // Clear any existing listeners
|
||||
}
|
||||
|
||||
/**
|
||||
* Test registering and triggering a single listener.
|
||||
*/
|
||||
public function testRegisterAndTriggerSingleListener()
|
||||
{
|
||||
$called = false;
|
||||
Flight::onEvent('test.event', function () use (&$called) {
|
||||
$called = true;
|
||||
});
|
||||
Flight::triggerEvent('test.event');
|
||||
$this->assertTrue($called, 'Single listener should be called when event is triggered.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test registering multiple listeners for the same event.
|
||||
*/
|
||||
public function testRegisterMultipleListeners()
|
||||
{
|
||||
$counter = 0;
|
||||
Flight::onEvent('test.event', function () use (&$counter) {
|
||||
$counter++;
|
||||
});
|
||||
Flight::onEvent('test.event', function () use (&$counter) {
|
||||
$counter++;
|
||||
});
|
||||
Flight::triggerEvent('test.event');
|
||||
$this->assertEquals(2, $counter, 'All registered listeners should be called.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test triggering an event with no listeners registered.
|
||||
*/
|
||||
public function testTriggerWithNoListeners()
|
||||
{
|
||||
// Should not throw any errors
|
||||
Flight::triggerEvent('non.existent.event');
|
||||
$this->assertTrue(true, 'Triggering an event with no listeners should not throw an error.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a listener receives a single argument correctly.
|
||||
*/
|
||||
public function testListenerReceivesSingleArgument()
|
||||
{
|
||||
$received = null;
|
||||
Flight::onEvent('test.event', function ($arg) use (&$received) {
|
||||
$received = $arg;
|
||||
});
|
||||
Flight::triggerEvent('test.event', 'hello');
|
||||
$this->assertEquals('hello', $received, 'Listener should receive the passed argument.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a listener receives multiple arguments correctly.
|
||||
*/
|
||||
public function testListenerReceivesMultipleArguments()
|
||||
{
|
||||
$received = [];
|
||||
Flight::onEvent('test.event', function ($arg1, $arg2) use (&$received) {
|
||||
$received = [$arg1, $arg2];
|
||||
});
|
||||
Flight::triggerEvent('test.event', 'first', 'second');
|
||||
$this->assertEquals(['first', 'second'], $received, 'Listener should receive all passed arguments.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that listeners are called in the order they were registered.
|
||||
*/
|
||||
public function testListenersCalledInOrder()
|
||||
{
|
||||
$order = [];
|
||||
Flight::onEvent('test.event', function () use (&$order) {
|
||||
$order[] = 1;
|
||||
});
|
||||
Flight::onEvent('test.event', function () use (&$order) {
|
||||
$order[] = 2;
|
||||
});
|
||||
Flight::triggerEvent('test.event');
|
||||
$this->assertEquals([1, 2], $order, 'Listeners should be called in registration order.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that listeners are not called for unrelated events.
|
||||
*/
|
||||
public function testListenerNotCalledForOtherEvents()
|
||||
{
|
||||
$called = false;
|
||||
Flight::onEvent('test.event1', function () use (&$called) {
|
||||
$called = true;
|
||||
});
|
||||
Flight::triggerEvent('test.event2');
|
||||
$this->assertFalse($called, 'Listeners should not be called for different events.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test overriding the onEvent method.
|
||||
*/
|
||||
public function testOverrideOnEvent()
|
||||
{
|
||||
$called = false;
|
||||
Flight::map('onEvent', function ($event, $callback) use (&$called) {
|
||||
$called = true;
|
||||
});
|
||||
Flight::onEvent('test.event', function () {
|
||||
});
|
||||
$this->assertTrue($called, 'Overridden onEvent method should be called.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test overriding the triggerEvent method.
|
||||
*/
|
||||
public function testOverrideTriggerEvent()
|
||||
{
|
||||
$called = false;
|
||||
Flight::map('triggerEvent', function ($event, ...$args) use (&$called) {
|
||||
$called = true;
|
||||
});
|
||||
Flight::triggerEvent('test.event');
|
||||
$this->assertTrue($called, 'Overridden triggerEvent method should be called.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an overridden onEvent can still register listeners by calling the original method.
|
||||
*/
|
||||
public function testOverrideOnEventStillRegistersListener()
|
||||
{
|
||||
$overrideCalled = false;
|
||||
Flight::map('onEvent', function ($event, $callback) use (&$overrideCalled) {
|
||||
$overrideCalled = true;
|
||||
// Call the original method
|
||||
Flight::app()->_onEvent($event, $callback);
|
||||
});
|
||||
|
||||
$listenerCalled = false;
|
||||
Flight::onEvent('test.event', function () use (&$listenerCalled) {
|
||||
$listenerCalled = true;
|
||||
});
|
||||
|
||||
Flight::triggerEvent('test.event');
|
||||
|
||||
$this->assertTrue($overrideCalled, 'Overridden onEvent should be called.');
|
||||
$this->assertTrue($listenerCalled, 'Listener should still be triggered after override.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an overridden triggerEvent can still trigger listeners by calling the original method.
|
||||
*/
|
||||
public function testOverrideTriggerEventStillTriggersListeners()
|
||||
{
|
||||
$overrideCalled = false;
|
||||
Flight::map('triggerEvent', function ($event, ...$args) use (&$overrideCalled) {
|
||||
$overrideCalled = true;
|
||||
// Call the original method
|
||||
Flight::app()->_triggerEvent($event, ...$args);
|
||||
});
|
||||
|
||||
$listenerCalled = false;
|
||||
Flight::onEvent('test.event', function () use (&$listenerCalled) {
|
||||
$listenerCalled = true;
|
||||
});
|
||||
|
||||
Flight::triggerEvent('test.event');
|
||||
|
||||
$this->assertTrue($overrideCalled, 'Overridden triggerEvent should be called.');
|
||||
$this->assertTrue($listenerCalled, 'Listeners should still be triggered after override.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an invalid callable throws an exception (if applicable).
|
||||
*/
|
||||
public function testInvalidCallableThrowsException()
|
||||
{
|
||||
$this->expectException(TypeError::class);
|
||||
// Assuming the event system validates callables
|
||||
Flight::onEvent('test.event', 'not_a_callable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that event propagation stops if a listener returns false.
|
||||
*/
|
||||
public function testStopPropagation()
|
||||
{
|
||||
$firstCalled = false;
|
||||
$secondCalled = false;
|
||||
$thirdCalled = false;
|
||||
|
||||
Flight::onEvent('test.event', function () use (&$firstCalled) {
|
||||
$firstCalled = true;
|
||||
return true; // Continue propagation
|
||||
});
|
||||
|
||||
Flight::onEvent('test.event', function () use (&$secondCalled) {
|
||||
$secondCalled = true;
|
||||
return false; // Stop propagation
|
||||
});
|
||||
|
||||
Flight::onEvent('test.event', function () use (&$thirdCalled) {
|
||||
$thirdCalled = true;
|
||||
});
|
||||
|
||||
Flight::triggerEvent('test.event');
|
||||
|
||||
$this->assertTrue($firstCalled, 'First listener should be called');
|
||||
$this->assertTrue($secondCalled, 'Second listener should be called');
|
||||
$this->assertFalse($thirdCalled, 'Third listener should not be called after propagation stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that hasListeners() correctly identifies events with listeners.
|
||||
*/
|
||||
public function testHasListeners()
|
||||
{
|
||||
$this->assertFalse(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should not have listeners before registration');
|
||||
|
||||
Flight::onEvent('test.event', function () {
|
||||
});
|
||||
|
||||
$this->assertTrue(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have listeners after registration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that getListeners() returns the correct listeners for an event.
|
||||
*/
|
||||
public function testGetListeners()
|
||||
{
|
||||
$callback1 = function () {
|
||||
};
|
||||
$callback2 = function () {
|
||||
};
|
||||
|
||||
$this->assertEmpty(Flight::eventDispatcher()->getListeners('test.event'), 'Event should have no listeners before registration');
|
||||
|
||||
Flight::onEvent('test.event', $callback1);
|
||||
Flight::onEvent('test.event', $callback2);
|
||||
|
||||
$listeners = Flight::eventDispatcher()->getListeners('test.event');
|
||||
$this->assertCount(2, $listeners, 'Event should have two registered listeners');
|
||||
$this->assertSame($callback1, $listeners[0], 'First listener should match the first callback');
|
||||
$this->assertSame($callback2, $listeners[1], 'Second listener should match the second callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that getListeners() returns an empty array for events with no listeners.
|
||||
*/
|
||||
public function testGetListenersForNonexistentEvent()
|
||||
{
|
||||
$listeners = Flight::eventDispatcher()->getListeners('nonexistent.event');
|
||||
$this->assertIsArray($listeners, 'Should return an array for nonexistent events');
|
||||
$this->assertEmpty($listeners, 'Should return an empty array for nonexistent events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that getAllRegisteredEvents() returns all event names with registered listeners.
|
||||
*/
|
||||
public function testGetAllRegisteredEvents()
|
||||
{
|
||||
$this->assertEmpty(Flight::eventDispatcher()->getAllRegisteredEvents(), 'No events should be registered initially');
|
||||
|
||||
Flight::onEvent('test.event1', function () {
|
||||
});
|
||||
Flight::onEvent('test.event2', function () {
|
||||
});
|
||||
|
||||
$events = Flight::eventDispatcher()->getAllRegisteredEvents();
|
||||
$this->assertCount(2, $events, 'Should return all registered event names');
|
||||
$this->assertContains('test.event1', $events, 'Should contain the first event');
|
||||
$this->assertContains('test.event2', $events, 'Should contain the second event');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removeListener() correctly removes a specific listener from an event.
|
||||
*/
|
||||
public function testRemoveListener()
|
||||
{
|
||||
$callback1 = function () {
|
||||
return 'callback1';
|
||||
};
|
||||
$callback2 = function () {
|
||||
return 'callback2';
|
||||
};
|
||||
|
||||
Flight::onEvent('test.event', $callback1);
|
||||
Flight::onEvent('test.event', $callback2);
|
||||
|
||||
$this->assertCount(2, Flight::eventDispatcher()->getListeners('test.event'), 'Event should have two listeners initially');
|
||||
|
||||
Flight::eventDispatcher()->removeListener('test.event', $callback1);
|
||||
|
||||
$listeners = Flight::eventDispatcher()->getListeners('test.event');
|
||||
$this->assertCount(1, $listeners, 'Event should have one listener after removal');
|
||||
$this->assertSame($callback2, $listeners[0], 'Remaining listener should be the second callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that removeAllListeners() correctly removes all listeners for an event.
|
||||
*/
|
||||
public function testRemoveAllListeners()
|
||||
{
|
||||
Flight::onEvent('test.event', function () {
|
||||
});
|
||||
Flight::onEvent('test.event', function () {
|
||||
});
|
||||
Flight::onEvent('another.event', function () {
|
||||
});
|
||||
|
||||
$this->assertTrue(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have listeners before removal');
|
||||
$this->assertTrue(Flight::eventDispatcher()->hasListeners('another.event'), 'Another event should have listeners');
|
||||
|
||||
Flight::eventDispatcher()->removeAllListeners('test.event');
|
||||
|
||||
$this->assertFalse(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have no listeners after removal');
|
||||
$this->assertTrue(Flight::eventDispatcher()->hasListeners('another.event'), 'Another event should still have listeners');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that trying to remove listeners for nonexistent events doesn't cause errors.
|
||||
*/
|
||||
public function testRemoveListenersForNonexistentEvent()
|
||||
{
|
||||
// Should not throw any errors
|
||||
Flight::eventDispatcher()->removeListener('nonexistent.event', function () {
|
||||
});
|
||||
Flight::eventDispatcher()->removeAllListeners('nonexistent.event');
|
||||
|
||||
$this->assertTrue(true, 'Removing listeners for nonexistent events should not throw errors');
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests;
|
||||
|
||||
use Flight;
|
||||
use flight\Engine;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FlightAsyncTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
Flight::setEngine(new Engine());
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$_SERVER = [];
|
||||
$_REQUEST = [];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
unset($_REQUEST);
|
||||
unset($_SERVER);
|
||||
}
|
||||
|
||||
// Checks that default components are loaded
|
||||
public function testSingleRoute()
|
||||
{
|
||||
Flight::route('GET /', function () {
|
||||
echo 'hello world';
|
||||
});
|
||||
|
||||
$this->expectOutputString('hello world');
|
||||
Flight::start();
|
||||
}
|
||||
|
||||
public function testMultipleRoutes()
|
||||
{
|
||||
Flight::route('GET /', function () {
|
||||
echo 'hello world';
|
||||
});
|
||||
|
||||
Flight::route('GET /test', function () {
|
||||
echo 'test';
|
||||
});
|
||||
|
||||
$this->expectOutputString('test');
|
||||
$_SERVER['REQUEST_URI'] = '/test';
|
||||
Flight::start();
|
||||
}
|
||||
|
||||
public function testMultipleStartsSingleRoute()
|
||||
{
|
||||
Flight::route('GET /', function () {
|
||||
echo 'hello world';
|
||||
});
|
||||
|
||||
$this->expectOutputString('hello worldhello world');
|
||||
Flight::start();
|
||||
Flight::start();
|
||||
}
|
||||
|
||||
public function testMultipleStartsMultipleRoutes()
|
||||
{
|
||||
Flight::route('GET /', function () {
|
||||
echo 'hello world';
|
||||
});
|
||||
|
||||
Flight::route('GET /test', function () {
|
||||
echo 'test';
|
||||
});
|
||||
|
||||
$this->expectOutputString('testhello world');
|
||||
$_SERVER['REQUEST_URI'] = '/test';
|
||||
Flight::start();
|
||||
$_SERVER['REQUEST_URI'] = '/';
|
||||
Flight::start();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests;
|
||||
|
||||
use Exception;
|
||||
use flight\net\UploadedFile;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UploadedFileTest extends TestCase
|
||||
{
|
||||
public function tearDown(): void
|
||||
{
|
||||
if (file_exists('file.txt')) {
|
||||
unlink('file.txt');
|
||||
}
|
||||
if (file_exists('tmp_name')) {
|
||||
unlink('tmp_name');
|
||||
}
|
||||
}
|
||||
|
||||
public function testMoveToSuccess()
|
||||
{
|
||||
file_put_contents('tmp_name', 'test');
|
||||
$uploadedFile = new UploadedFile('file.txt', 'text/plain', 4, 'tmp_name', UPLOAD_ERR_OK);
|
||||
$uploadedFile->moveTo('file.txt');
|
||||
$this->assertFileExists('file.txt');
|
||||
}
|
||||
|
||||
public function getFileErrorMessageTests(): array
|
||||
{
|
||||
return [
|
||||
[ UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', ],
|
||||
[ UPLOAD_ERR_FORM_SIZE, 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', ],
|
||||
[ UPLOAD_ERR_PARTIAL, 'The uploaded file was only partially uploaded.', ],
|
||||
[ UPLOAD_ERR_NO_FILE, 'No file was uploaded.', ],
|
||||
[ UPLOAD_ERR_NO_TMP_DIR, 'Missing a temporary folder.', ],
|
||||
[ UPLOAD_ERR_CANT_WRITE, 'Failed to write file to disk.', ],
|
||||
[ UPLOAD_ERR_EXTENSION, 'A PHP extension stopped the file upload.', ],
|
||||
[ -1, 'An unknown error occurred. Error code: -1' ]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getFileErrorMessageTests
|
||||
*/
|
||||
public function testMoveToFailureMessages($error, $message)
|
||||
{
|
||||
file_put_contents('tmp_name', 'test');
|
||||
$uploadedFile = new UploadedFile('file.txt', 'text/plain', 4, 'tmp_name', $error);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage($message);
|
||||
$uploadedFile->moveTo('file.txt');
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\classes;
|
||||
|
||||
class ClassWithExceptionInConstruct
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
throw new \Exception('This is an exception in the constructor');
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use tests\groupcompactsyntax\PostsController;
|
||||
use tests\groupcompactsyntax\TodosController;
|
||||
use tests\groupcompactsyntax\UsersController;
|
||||
|
||||
require_once __DIR__ . '/UsersController.php';
|
||||
require_once __DIR__ . '/PostsController.php';
|
||||
|
||||
final class FlightRouteCompactSyntaxTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
Flight::router()->clear();
|
||||
}
|
||||
|
||||
public function testCanMapMethodsWithVerboseSyntax(): void
|
||||
{
|
||||
Flight::route('GET /users', [UsersController::class, 'index']);
|
||||
Flight::route('DELETE /users/@id', [UsersController::class, 'destroy']);
|
||||
|
||||
$routes = Flight::router()->getRoutes();
|
||||
|
||||
$this->assertCount(2, $routes);
|
||||
|
||||
$this->assertSame('/users', $routes[0]->pattern);
|
||||
$this->assertSame([UsersController::class, 'index'], $routes[0]->callback);
|
||||
$this->assertSame('GET', $routes[0]->methods[0]);
|
||||
|
||||
$this->assertSame('/users/@id', $routes[1]->pattern);
|
||||
$this->assertSame([UsersController::class, 'destroy'], $routes[1]->callback);
|
||||
$this->assertSame('DELETE', $routes[1]->methods[0]);
|
||||
}
|
||||
|
||||
public function testOptionsOnly(): void
|
||||
{
|
||||
Flight::resource('/users', UsersController::class, [
|
||||
'only' => [ 'index', 'destroy' ]
|
||||
]);
|
||||
|
||||
$routes = Flight::router()->getRoutes();
|
||||
|
||||
$this->assertCount(2, $routes);
|
||||
|
||||
$this->assertSame('/users', $routes[0]->pattern);
|
||||
$this->assertSame('GET', $routes[0]->methods[0]);
|
||||
$this->assertSame([UsersController::class, 'index'], $routes[0]->callback);
|
||||
|
||||
$this->assertSame('/users/@id', $routes[1]->pattern);
|
||||
$this->assertSame('DELETE', $routes[1]->methods[0]);
|
||||
$this->assertSame([UsersController::class, 'destroy'], $routes[1]->callback);
|
||||
}
|
||||
|
||||
public function testDefaultMethods(): void
|
||||
{
|
||||
Flight::resource('/posts', PostsController::class);
|
||||
|
||||
$routes = Flight::router()->getRoutes();
|
||||
$this->assertCount(7, $routes);
|
||||
|
||||
$this->assertSame('/posts', $routes[0]->pattern);
|
||||
$this->assertSame('GET', $routes[0]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'index'], $routes[0]->callback);
|
||||
$this->assertSame('posts.index', $routes[0]->alias);
|
||||
|
||||
$this->assertSame('/posts/create', $routes[1]->pattern);
|
||||
$this->assertSame('GET', $routes[1]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'create'], $routes[1]->callback);
|
||||
$this->assertSame('posts.create', $routes[1]->alias);
|
||||
|
||||
$this->assertSame('/posts', $routes[2]->pattern);
|
||||
$this->assertSame('POST', $routes[2]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'store'], $routes[2]->callback);
|
||||
$this->assertSame('posts.store', $routes[2]->alias);
|
||||
|
||||
$this->assertSame('/posts/@id', $routes[3]->pattern);
|
||||
$this->assertSame('GET', $routes[3]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'show'], $routes[3]->callback);
|
||||
$this->assertSame('posts.show', $routes[3]->alias);
|
||||
|
||||
$this->assertSame('/posts/@id/edit', $routes[4]->pattern);
|
||||
$this->assertSame('GET', $routes[4]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'edit'], $routes[4]->callback);
|
||||
$this->assertSame('posts.edit', $routes[4]->alias);
|
||||
|
||||
$this->assertSame('/posts/@id', $routes[5]->pattern);
|
||||
$this->assertSame('PUT', $routes[5]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'update'], $routes[5]->callback);
|
||||
$this->assertSame('posts.update', $routes[5]->alias);
|
||||
|
||||
$this->assertSame('/posts/@id', $routes[6]->pattern);
|
||||
$this->assertSame('DELETE', $routes[6]->methods[0]);
|
||||
$this->assertSame([PostsController::class, 'destroy'], $routes[6]->callback);
|
||||
$this->assertSame('posts.destroy', $routes[6]->alias);
|
||||
}
|
||||
|
||||
public function testOptionsExcept(): void
|
||||
{
|
||||
Flight::resource('/todos', TodosController::class, [
|
||||
'except' => [ 'create', 'store', 'update', 'destroy', 'edit' ]
|
||||
]);
|
||||
|
||||
$routes = Flight::router()->getRoutes();
|
||||
|
||||
$this->assertCount(2, $routes);
|
||||
|
||||
$this->assertSame('/todos', $routes[0]->pattern);
|
||||
$this->assertSame('GET', $routes[0]->methods[0]);
|
||||
$this->assertSame([TodosController::class, 'index'], $routes[0]->callback);
|
||||
|
||||
$this->assertSame('/todos/@id', $routes[1]->pattern);
|
||||
$this->assertSame('GET', $routes[1]->methods[0]);
|
||||
$this->assertSame([TodosController::class, 'show'], $routes[1]->callback);
|
||||
}
|
||||
|
||||
public function testOptionsMiddlewareAndAliasBase(): void
|
||||
{
|
||||
Flight::resource('/todos', TodosController::class, [
|
||||
'middleware' => [ 'auth' ],
|
||||
'alias_base' => 'nothanks'
|
||||
]);
|
||||
|
||||
$routes = Flight::router()->getRoutes();
|
||||
|
||||
$this->assertCount(7, $routes);
|
||||
|
||||
$this->assertSame('/todos', $routes[0]->pattern);
|
||||
$this->assertSame('GET', $routes[0]->methods[0]);
|
||||
$this->assertSame([TodosController::class, 'index'], $routes[0]->callback);
|
||||
$this->assertSame('auth', $routes[0]->middleware[0]);
|
||||
$this->assertSame('nothanks.index', $routes[0]->alias);
|
||||
|
||||
$this->assertSame('/todos/create', $routes[1]->pattern);
|
||||
$this->assertSame('GET', $routes[1]->methods[0]);
|
||||
$this->assertSame([TodosController::class, 'create'], $routes[1]->callback);
|
||||
$this->assertSame('auth', $routes[1]->middleware[0]);
|
||||
$this->assertSame('nothanks.create', $routes[1]->alias);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\groupcompactsyntax;
|
||||
|
||||
final class PostsController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function show(string $id): void
|
||||
{
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function store(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function edit(string $id): void
|
||||
{
|
||||
}
|
||||
|
||||
public function update(string $id): void
|
||||
{
|
||||
}
|
||||
|
||||
public function destroy(string $id): void
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\groupcompactsyntax;
|
||||
|
||||
final class TodosController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function show(string $id): void
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\groupcompactsyntax;
|
||||
|
||||
final class UsersController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
echo __METHOD__;
|
||||
}
|
||||
|
||||
public function destroy(): void
|
||||
{
|
||||
echo __METHOD__;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
This file downloaded successfully!
|
Loading…
Reference in new issue