From df1238fc1e86920f8e82b0051bcad8ab2f61ec7b Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Thu, 24 Oct 2024 23:11:41 +0300 Subject: [PATCH 01/21] Console commands added --- composer.json | 16 +- composer.lock | 989 +++++-- console | 22 + index.php | 5 +- packages/cli-service/.gitignore | 1 + packages/cli-service/bootstrap.php | 13 + packages/cli-service/composer.json | 31 + packages/cli-service/composer.lock | 2554 +++++++++++++++++ .../cli-service/src/Commands/BaseCommand.php | 125 + .../src/Commands/CheckMailCommand.php | 43 + .../src/Commands/ListCommandsCommand.php | 53 + packages/cli-service/src/Events/BaseEvent.php | 20 + .../src/Providers/CommandServiceProvider.php | 31 + .../src/Traits/ScheduleFrequencyManager.php | 234 ++ .../tests/Commands/BaseCommandTest.php | 102 + .../cli-service/tests/Mocks/TestCommand.php | 86 + tests/phpunit/phpunit.xml | 3 + 17 files changed, 4157 insertions(+), 171 deletions(-) create mode 100644 console create mode 100644 packages/cli-service/.gitignore create mode 100644 packages/cli-service/bootstrap.php create mode 100644 packages/cli-service/composer.json create mode 100644 packages/cli-service/composer.lock create mode 100644 packages/cli-service/src/Commands/BaseCommand.php create mode 100644 packages/cli-service/src/Commands/CheckMailCommand.php create mode 100644 packages/cli-service/src/Commands/ListCommandsCommand.php create mode 100644 packages/cli-service/src/Events/BaseEvent.php create mode 100644 packages/cli-service/src/Providers/CommandServiceProvider.php create mode 100644 packages/cli-service/src/Traits/ScheduleFrequencyManager.php create mode 100644 packages/cli-service/tests/Commands/BaseCommandTest.php create mode 100644 packages/cli-service/tests/Mocks/TestCommand.php diff --git a/composer.json b/composer.json index b1a9f6e8ce..f896103157 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,7 @@ "bacon/bacon-qr-code": "^1.0.3 || ^2.0.0", "christian-riesen/base32": "^1.3.2", "composer": "^2.0.0", + "cypht/cli-service": "*", "ext-curl": "*", "ext-fileinfo": "*", "ext-iconv": "*", @@ -61,6 +62,11 @@ "require-dev": { "phpunit/phpunit": "^10.5" }, + "autoload-dev": { + "psr-4": { + "Tests\\": "packages/cli-service/tests/" + } + }, "suggest": { "ext-pdo": "To use database features, this needs to be installed", "ext-redis": "To use Redis for caching, this needs to be installed", @@ -82,5 +88,13 @@ "post-update-cmd": "composer suggest", "post-package-install": "composer suggest", "post-install-cmd": "composer suggest" - } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "repositories": [ + { + "type": "path", + "url": "./packages/cli-service" + } + ] } diff --git a/composer.lock b/composer.lock index 963936305d..bbb87cf79d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e11d399af68ed7416340c63ab7a213d6", + "content-hash": "d664be91483f1546046fb1b242ae4239", "packages": [ { "name": "bacon/bacon-qr-code", @@ -119,25 +119,65 @@ }, "time": "2021-02-26T10:19:33+00:00" }, + { + "name": "cypht/cli-service", + "version": "1.0.0", + "dist": { + "type": "path", + "url": "./packages/cli-service", + "reference": "5d38f44f996511d8ec37628fb3c268d8e05e2f45" + }, + "require": { + "php-di/php-di": "^7.0", + "symfony/console": "^5.3 || ^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cypht\\Service\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Cypht\\Service\\Tests\\": "tests/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Ngesera", + "email": "muhngesteven@gmail.com" + } + ], + "description": "A package that integrates with Cypht webmail to provide real-time email notifications using WebSockets and an event-driven scheduler system. It leverages Symfony's console commands and Ratchet for handling WebSocket connections.", + "transport-options": { + "relative": true + } + }, { "name": "dasprid/enum", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", "shasum": "" }, "require": { "php": ">=7.1 <9.0" }, "require-dev": { - "phpunit/phpunit": "^7 | ^8 | ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "*" }, "type": "library", @@ -165,22 +205,22 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" }, - "time": "2023-08-25T16:18:39+00:00" + "time": "2024-08-09T14:30:48+00:00" }, { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -240,9 +280,9 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2022-10-27T11:44:00+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "ezyang/htmlpurifier", @@ -307,16 +347,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -331,8 +371,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -403,7 +443,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -419,7 +459,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "henrique-borba/php-sieve-manager", @@ -490,18 +530,79 @@ }, "time": "2024-08-29T06:33:14+00:00" }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-09-23T13:33:08+00:00" + }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -514,8 +615,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -537,7 +638,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -594,7 +695,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -680,31 +781,31 @@ }, { "name": "nette/schema", - "version": "v1.2.5", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -736,35 +837,36 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", - "version": "v3.2.10", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=7.2 <8.4" + "php": "8.0 - 8.4" }, "conflict": { - "nette/di": "<3.0.6" + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "~2.0", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", @@ -772,13 +874,12 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -822,9 +923,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.10" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2023-07-30T15:38:18+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "paragonie/random_compat", @@ -880,6 +981,134 @@ }, "time": "2022-02-16T17:07:03+00:00" }, + { + "name": "php-di/invoker", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" + }, + { + "name": "php-di/php-di", + "version": "7.0.7", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=8.0", + "php-di/invoker": "^2.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.6" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2024-07-21T15:55:45+00:00" + }, { "name": "pimple/pimple", "version": "v3.5.0", @@ -1038,20 +1267,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -1075,7 +1304,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1087,9 +1316,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -1188,27 +1417,121 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "symfony/console", + "version": "v6.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:15:52+00:00" + }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.3", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1237,7 +1560,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -1253,7 +1576,7 @@ "type": "tidelift" } ], - "time": "2023-01-24T14:02:46+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/dotenv", @@ -1328,20 +1651,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1387,7 +1710,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1403,24 +1726,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f" + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f", - "reference": "cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-iconv": "*" @@ -1467,7 +1790,85 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -1483,24 +1884,105 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1547,7 +2029,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1563,24 +2045,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1627,7 +2109,90 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -1643,20 +2208,106 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9" + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", "shasum": "" }, "require": { @@ -1699,7 +2350,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.8" + "source": "https://github.com/symfony/yaml/tree/v6.4.12" }, "funding": [ { @@ -1715,7 +2366,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "thomaspark/bootswatch", @@ -2115,16 +2766,16 @@ "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -2132,11 +2783,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -2162,7 +2814,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -2170,30 +2822,31 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { + "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -2225,9 +2878,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", @@ -2349,32 +3002,32 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.14", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -2386,7 +3039,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -2415,7 +3068,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -2423,7 +3076,7 @@ "type": "github" } ], - "time": "2024-03-12T15:33:41+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2670,16 +3323,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.20", + "version": "10.5.37", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3" + "reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/547d314dc24ec1e177720d45c6263fb226cc2ae3", - "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c7cffa0efa2b70c22366523e6d804c9419eb2400", + "reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400", "shasum": "" }, "require": { @@ -2689,26 +3342,26 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.3", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -2751,7 +3404,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.37" }, "funding": [ { @@ -2767,7 +3420,7 @@ "type": "tidelift" } ], - "time": "2024-04-24T06:32:35+00:00" + "time": "2024-10-19T13:03:41+00:00" }, { "name": "sebastian/cli-parser", @@ -2939,16 +3592,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { @@ -2959,7 +3612,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -3004,7 +3657,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -3012,7 +3665,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/complexity", @@ -3737,9 +4390,9 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "composer": "^2.0.0", diff --git a/console b/console new file mode 100644 index 0000000000..4a198a4160 --- /dev/null +++ b/console @@ -0,0 +1,22 @@ +#!/usr/bin/env php +register($application, $container); + +// Run the console application +$application->run(); diff --git a/index.php b/index.php index 4e10632cb3..efaae8032e 100644 --- a/index.php +++ b/index.php @@ -48,9 +48,10 @@ if (!$config->get('disable_ini_settings')) { require APP_PATH.'lib/ini_set.php'; } - /* process the request */ -new Hm_Dispatch($config); +if (!defined('SKIP_HM_DISPATCH')) { + new Hm_Dispatch($config); +} /* log some debug stats about the page */ if (DEBUG_MODE) { diff --git a/packages/cli-service/.gitignore b/packages/cli-service/.gitignore new file mode 100644 index 0000000000..57872d0f1e --- /dev/null +++ b/packages/cli-service/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/packages/cli-service/bootstrap.php b/packages/cli-service/bootstrap.php new file mode 100644 index 0000000000..3bed06d43d --- /dev/null +++ b/packages/cli-service/bootstrap.php @@ -0,0 +1,13 @@ +addDefinitions([ + +]); + +$container = $containerBuilder->build(); + +return $container; diff --git a/packages/cli-service/composer.json b/packages/cli-service/composer.json new file mode 100644 index 0000000000..b4d50a0a11 --- /dev/null +++ b/packages/cli-service/composer.json @@ -0,0 +1,31 @@ +{ + "name": "cypht/cli-service", + "description": "A package that integrates with Cypht webmail to provide real-time email notifications using WebSockets and an event-driven scheduler system. It leverages Symfony's console commands and Ratchet for handling WebSocket connections.", + "type": "library", + "require": { + "symfony/console": "^5.3 || ^6.0", + "php-di/php-di": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "license": "MIT", + "autoload": { + "psr-4": { + "Cypht\\Service\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "authors": [ + { + "name": "Steven Ngesera", + "email": "muhngesteven@gmail.com" + } + ], + "version": "1.0.0", + "minimum-stability": "dev" +} diff --git a/packages/cli-service/composer.lock b/packages/cli-service/composer.lock new file mode 100644 index 0000000000..e0ccd85f88 --- /dev/null +++ b/packages/cli-service/composer.lock @@ -0,0 +1,2554 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9e47578e7f13c92732e2958f91bcd3a3", + "packages": [ + { + "name": "laravel/serializable-closure", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "43244bfd7161fdbce7419c76f9a776b7d705b333" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/43244bfd7161fdbce7419c76f9a776b7d705b333", + "reference": "43244bfd7161fdbce7419c76f9a776b7d705b333", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-09-24T15:52:47+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" + }, + { + "name": "php-di/php-di", + "version": "7.0.7", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=8.0", + "php-di/invoker": "^2.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.6" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2024-07-21T15:55:45+00:00" + }, + { + "name": "psr/container", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "707984727bd5b2b670e59559d3ed2500240cf875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/707984727bd5b2b670e59559d3ed2500240cf875", + "reference": "707984727bd5b2b670e59559d3ed2500240cf875", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container" + }, + "time": "2023-09-22T11:11:30+00:00" + }, + { + "name": "symfony/console", + "version": "6.4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/6.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-09T08:40:40+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.6-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/main" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2369cb908b33d7b7518cce042615de430142497f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2369cb908b33d7b7518cce042615de430142497f", + "reference": "2369cb908b33d7b7518cce042615de430142497f", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "5ad38698559cf88b6296629e19b15ef3239c9d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/5ad38698559cf88b6296629e19b15ef3239c9d7a", + "reference": "5ad38698559cf88b6296629e19b15ef3239c9d7a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.6-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/main" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/string", + "version": "7.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "205580699b4d3e11f7b679faf2c0f57ffca6981c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/205580699b4d3e11f7b679faf2c0f57ffca6981c", + "reference": "205580699b4d3e11f7b679faf2c0f57ffca6981c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/7.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T06:56:12+00:00" + } + ], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" + }, + { + "name": "phar-io/manifest", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "8b818f1073841c1cb59547bb5853871ce8e7569f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8b818f1073841c1cb59547bb5853871ce8e7569f", + "reference": "8b818f1073841c1cb59547bb5853871ce8e7569f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.3.1", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.5.36" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T06:19:11+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "585510209bc946b0b5686db50aaa89a20d574f94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/585510209bc946b0b5686db50aaa89a20d574f94", + "reference": "585510209bc946b0b5686db50aaa89a20d574f94", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:03:34+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "b278fa2f7b3adbb1601635ccae52dd2ae28208f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/b278fa2f7b3adbb1601635ccae52dd2ae28208f7", + "reference": "b278fa2f7b3adbb1601635ccae52dd2ae28208f7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.5" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:04:02+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "20d18853f9a3da202c01565c55130fb2c95f0f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/20d18853f9a3da202c01565c55130fb2c95f0f57", + "reference": "20d18853f9a3da202c01565c55130fb2c95f0f57", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:04:29+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "2bcef0a54b678667378fb2f6eabd332eec28ed7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2bcef0a54b678667378fb2f6eabd332eec28ed7e", + "reference": "2bcef0a54b678667378fb2f6eabd332eec28ed7e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:04:59+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "81306cb1b0b037d60bbd7cfc915f13626ec790e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/81306cb1b0b037d60bbd7cfc915f13626ec790e9", + "reference": "81306cb1b0b037d60bbd7cfc915f13626ec790e9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.3", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-10-23T09:43:30+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c37c9e6127dcbaeb0c4778afb9101f61f2bc5969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c37c9e6127dcbaeb0c4778afb9101f61f2bc5969", + "reference": "c37c9e6127dcbaeb0c4778afb9101f61f2bc5969", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:24:16+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "83fcced885622ae51329748de2f713c2ff460eed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/83fcced885622ae51329748de2f713c2ff460eed", + "reference": "83fcced885622ae51329748de2f713c2ff460eed", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:25:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "85662a56e440cdbcb14a6d9e3e07feb3b654b6db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/85662a56e440cdbcb14a6d9e3e07feb3b654b6db", + "reference": "85662a56e440cdbcb14a6d9e3e07feb3b654b6db", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:55:58+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-18T14:56:07+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "3d89639d42d385d4e3ba1a235a24eafe40ab995c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/3d89639d42d385d4e3ba1a235a24eafe40ab995c", + "reference": "3d89639d42d385d4e3ba1a235a24eafe40ab995c", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:57:41+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "eb50d310beb710c1fbbb8de600ed9d7ec28be385" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/eb50d310beb710c1fbbb8de600ed9d7ec28be385", + "reference": "eb50d310beb710c1fbbb8de600ed9d7ec28be385", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:58:49+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8", + "reference": "febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T06:59:16+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d", + "reference": "4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:00:08+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "63e42d7d47f0afeb531dce8a2e69173c97335d84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/63e42d7d47f0afeb531dce8a2e69173c97335d84", + "reference": "63e42d7d47f0afeb531dce8a2e69173c97335d84", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:00:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "be42b61b0c09d80e31315394ad02f9e8265c5780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/be42b61b0c09d80e31315394ad02f9e8265c5780", + "reference": "be42b61b0c09d80e31315394ad02f9e8265c5780", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:01:14+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "67c331ffb8e77a9d8129193dd0c60f568c2bd49f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/67c331ffb8e77a9d8129193dd0c60f568c2bd49f", + "reference": "67c331ffb8e77a9d8129193dd0c60f568c2bd49f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:02:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "bcd028a491280d802fe97e72748326196bcde7f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bcd028a491280d802fe97e72748326196bcde7f9", + "reference": "bcd028a491280d802fe97e72748326196bcde7f9", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:02:50+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "18e76a35cec6ee7547029064ea1a257d9d75ebf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/18e76a35cec6ee7547029064ea1a257d9d75ebf0", + "reference": "18e76a35cec6ee7547029064ea1a257d9d75ebf0", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:05:35+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e", + "reference": "d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-29T07:06:05+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "92ded34a7ac2cece0a3ae576e4850fcdd2276634" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/92ded34a7ac2cece0a3ae576e4850fcdd2276634", + "reference": "92ded34a7ac2cece0a3ae576e4850fcdd2276634", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-07T13:16:02+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/packages/cli-service/src/Commands/BaseCommand.php b/packages/cli-service/src/Commands/BaseCommand.php new file mode 100644 index 0000000000..5186b09a1d --- /dev/null +++ b/packages/cli-service/src/Commands/BaseCommand.php @@ -0,0 +1,125 @@ +container = $container; + } + + /** + * Initialize SymfonyStyle for consistent command output formatting. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->io = new SymfonyStyle($input, $output); + } + + /** + * Execute the console command. + * + * This method can be overridden in subclasses to provide specific command functionality. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command exit code. + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->info('Executing command: ' . $this->getName()); + + return Command::SUCCESS; + } + /** + * Get a service from the container. + * + * @param string $service The service class or interface. + * @return mixed The requested service. + */ + protected function getService(string $service) + { + return $this->container->get($service); + } + + /** + * Output a success message. + * + * @param string $message + */ + protected function success(string $message): void + { + $this->io->success($message); + } + + /** + * Output an error message. + * + * @param string $message + */ + protected function error(string $message): void + { + $this->io->error($message); + } + + /** + * Output an informational message. + * + * @param string $message + */ + protected function info(string $message): void + { + $this->io->info($message); + } + + /** + * Output a warning message. + * + * @param string $message + */ + protected function warning(string $message): void + { + $this->io->warning($message); + } + + /** + * Output a simple text message. + * + * @param string $message + */ + protected function text(string $message): void + { + $this->io->text($message); + } +} diff --git a/packages/cli-service/src/Commands/CheckMailCommand.php b/packages/cli-service/src/Commands/CheckMailCommand.php new file mode 100644 index 0000000000..0f19279d54 --- /dev/null +++ b/packages/cli-service/src/Commands/CheckMailCommand.php @@ -0,0 +1,43 @@ +info("Checking for new mail..."); + + // Example: Call the mail checking service from the container + // $imap = $this->getService('Hm_Imap'); + // $newMessages = $imap->search('UNSEEN'); + + if (!empty($newMessages)) { + $this->success('You have new messages!'); + // dispatch event + } else { + $this->info('No new messages.'); + } + + return Command::SUCCESS; + } +} diff --git a/packages/cli-service/src/Commands/ListCommandsCommand.php b/packages/cli-service/src/Commands/ListCommandsCommand.php new file mode 100644 index 0000000000..24b1c4bf26 --- /dev/null +++ b/packages/cli-service/src/Commands/ListCommandsCommand.php @@ -0,0 +1,53 @@ +success('Registered Commands:'); + + $this->initialize($input, $output); + + $commands = $this->getApplication()->all(); + + $commandList = []; + + foreach ($commands as $command) { + $commandList[] = sprintf('%-30s - %s', $command->getName(), $command->getDescription()); + } + + $this->text(implode(PHP_EOL, $commandList)); + + return Command::SUCCESS; + } +} diff --git a/packages/cli-service/src/Events/BaseEvent.php b/packages/cli-service/src/Events/BaseEvent.php new file mode 100644 index 0000000000..04fa6be1af --- /dev/null +++ b/packages/cli-service/src/Events/BaseEvent.php @@ -0,0 +1,20 @@ +payload = $payload; + } + + public function getPayload(): array + { + return $this->payload; + } + + abstract public function getEventName(): string; +} diff --git a/packages/cli-service/src/Providers/CommandServiceProvider.php b/packages/cli-service/src/Providers/CommandServiceProvider.php new file mode 100644 index 0000000000..b8a87445c4 --- /dev/null +++ b/packages/cli-service/src/Providers/CommandServiceProvider.php @@ -0,0 +1,31 @@ +add($command); + } + } + } +} diff --git a/packages/cli-service/src/Traits/ScheduleFrequencyManager.php b/packages/cli-service/src/Traits/ScheduleFrequencyManager.php new file mode 100644 index 0000000000..1c208d161e --- /dev/null +++ b/packages/cli-service/src/Traits/ScheduleFrequencyManager.php @@ -0,0 +1,234 @@ +expression = $expression; + return $this; + } + + /** + * Schedule the event to run between start and end time. + * + * @param string $startTime + * @param string $endTime + * @return $this + */ + public function between($startTime, $endTime) + { + return $this->when($this->inTimeInterval($startTime, $endTime)); + } + + /** + * Schedule the event to not run between start and end time. + * + * @param string $startTime + * @param string $endTime + * @return $this + */ + public function unlessBetween($startTime, $endTime) + { + return $this->skip($this->inTimeInterval($startTime, $endTime)); + } + + /** + * Check if the current time is within the given time interval. + * + * @param string $startTime + * @param string $endTime + * @return \Closure + */ + private function inTimeInterval($startTime, $endTime) + { + $now = new \DateTime('now', new \DateTimeZone($this->timezone)); + $startTime = new \DateTime($startTime, new \DateTimeZone($this->timezone)); + $endTime = new \DateTime($endTime, new \DateTimeZone($this->timezone)); + + if ($endTime < $startTime) { + if ($startTime > $now) { + $startTime->modify('-1 day'); + } else { + $endTime->modify('+1 day'); + } + } + + return function () use ($now, $startTime, $endTime) { + return $now >= $startTime && $now <= $endTime; + }; + } + + /** + * Schedule the event to run every minute. + * + * @return $this + */ + public function everyMinute() + { + return $this->spliceIntoPosition(1, '*'); + } + + /** + * Schedule the event to run every two minutes. + * + * @return $this + */ + public function everyTwoMinutes() + { + return $this->spliceIntoPosition(1, '*/2'); + } + + /** + * Schedule the event to run every three minutes. + * + * @return $this + */ + public function everyThreeMinutes() + { + return $this->spliceIntoPosition(1, '*/3'); + } + + /** + * Schedule the event to run every four minutes. + * + * @return $this + */ + public function everyFourMinutes() + { + return $this->spliceIntoPosition(1, '*/4'); + } + + /** + * Schedule the event to run every five minutes. + * + * @return $this + */ + public function everyFiveMinutes() + { + return $this->spliceIntoPosition(1, '*/5'); + } + + /** + * Schedule the event to run every ten minutes. + * + * @return $this + */ + public function everyTenMinutes() + { + return $this->spliceIntoPosition(1, '*/10'); + } + + /** + * Schedule the event to run every fifteen minutes. + * + * @return $this + */ + public function everyFifteenMinutes() + { + return $this->spliceIntoPosition(1, '*/15'); + } + + /** + * Schedule the event to run every thirty minutes. + * + * @return $this + */ + public function everyThirtyMinutes() + { + return $this->spliceIntoPosition(1, '0,30'); + } + + /** + * Schedule the event to run hourly. + * + * @return $this + */ + public function hourly() + { + return $this->spliceIntoPosition(1, 0); + } + + /** + * Schedule the event to run hourly at a given offset in the hour. + * + * @param array|int $offset + * @return $this + */ + public function hourlyAt($offset) + { + $offset = is_array($offset) ? implode(',', $offset) : $offset; + return $this->spliceIntoPosition(1, $offset); + } + + /** + * Schedule the event to run daily. + * + * @return $this + */ + public function daily() + { + return $this->spliceIntoPosition(1, 0)->spliceIntoPosition(2, 0); + } + + /** + * Schedule the event to run daily at a given time (10:00, 19:30, etc). + * + * @param string $time + * @return $this + */ + public function dailyAt($time) + { + $segments = explode(':', $time); + return $this->spliceIntoPosition(2, (int)$segments[0]) + ->spliceIntoPosition(1, count($segments) === 2 ? (int)$segments[1] : '0'); + } + + /** + * Set the timezone for scheduling. + * + * @param \DateTimeZone|string $timezone + * @return $this + */ + public function timezone($timezone) + { + $this->timezone = $timezone; + return $this; + } + + /** + * Splice the given value into the given position of the expression. + * + * @param int $position + * @param string $value + * @return $this + */ + protected function spliceIntoPosition($position, $value) + { + $segments = preg_split("/\s+/", $this->expression); + $segments[$position - 1] = $value; + return $this->cron(implode(' ', $segments)); + } +} diff --git a/packages/cli-service/tests/Commands/BaseCommandTest.php b/packages/cli-service/tests/Commands/BaseCommandTest.php new file mode 100644 index 0000000000..720a78ce4f --- /dev/null +++ b/packages/cli-service/tests/Commands/BaseCommandTest.php @@ -0,0 +1,102 @@ +container = $this->createMock(\DI\Container::class); + + $this->command = new TestCommand($this->container); + + $this->input = new ArrayInput([]); + $this->output = new BufferedOutput(); + + $this->command->run($this->input, $this->output); + } + + public function testSuccessOutput() + { + $this->command->success('This is a success message.'); + + $this->assertStringContainsString('This is a success message.', $this->output->fetch()); + } + + public function testErrorOutput(): void + { + // Simulate an error message + $this->command->error('This is an error message.'); + + // Fetch output from BufferedOutput + $outputContent = $this->output->fetch(); + + // Assert that the error message is in the output + $this->assertStringContainsString('This is an error message.', $outputContent); + } + + public function testInfoOutput(): void + { + // Simulate an informational message + $this->command->info('This is an info message.'); + + // Fetch output from BufferedOutput + $outputContent = $this->output->fetch(); + + // Assert that the info message is in the output + $this->assertStringContainsString('This is an info message.', $outputContent); + } + + public function testWarningOutput(): void + { + // Simulate a warning message + $this->command->warning('This is a warning message.'); + + // Fetch output from BufferedOutput + $outputContent = $this->output->fetch(); + + // Assert that the warning message is in the output + $this->assertStringContainsString('This is a warning message.', $outputContent); + } + + public function testTextOutput(): void + { + // Simulate a plain text message + $this->command->text('This is a plain text message.'); + + // Fetch output from BufferedOutput + $outputContent = $this->output->fetch(); + + // Assert that the plain text message is in the output + $this->assertStringContainsString('This is a plain text message.', $outputContent); + } + + public function testExecute() + { + $exitCode = $this->command->run($this->input, $this->output); + + $this->assertEquals(0, $exitCode); + $this->assertStringContainsString('Test command executed successfully.', $this->output->fetch()); + } + + // public function testGetService() + // { + // // TODO: Implement testGetService() method + // } +} \ No newline at end of file diff --git a/packages/cli-service/tests/Mocks/TestCommand.php b/packages/cli-service/tests/Mocks/TestCommand.php new file mode 100644 index 0000000000..a8d0469514 --- /dev/null +++ b/packages/cli-service/tests/Mocks/TestCommand.php @@ -0,0 +1,86 @@ +success("Test command executed successfully."); + return Command::SUCCESS; + } + + /** + * Override the protected success method for testing. + * + * @param string $message + */ + public function success(string $message): void + { + parent::success($message); + } + + /** + * Override the protected success method for testing. + * + * @param string $message + */ + public function error(string $message): void + { + parent::error($message); + } + + /** + * Override the protected success method for testing. + * + * @param string $message + */ + public function info(string $message): void + { + parent::info($message); + } + /** + * Override the protected success method for testing. + * + * @param string $message + */ + public function warning(string $message): void + { + parent::warning($message); + } + /** + * Override the protected success method for testing. + * + * @param string $message + */ + public function text(string $message): void + { + parent::text($message); + } + + public function getService(string $message) + { + parent::getService($message); + } +} diff --git a/tests/phpunit/phpunit.xml b/tests/phpunit/phpunit.xml index 347eed320a..37c207f204 100644 --- a/tests/phpunit/phpunit.xml +++ b/tests/phpunit/phpunit.xml @@ -138,6 +138,9 @@ ./modules/core/output_modules.php ./modules/core/output_modules_debug.php + + ./../../packages/cli-service/tests + From bea464ed693899c2b9df49e386538b4771b7bdfa Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Sat, 26 Oct 2024 14:41:55 +0300 Subject: [PATCH 02/21] Refactored cli-service structure --- composer.json | 18 +- composer.lock | 46 +- console | 46 +- index.php | 4 +- packages/cli-service/.gitignore | 1 - packages/cli-service/composer.json | 31 - packages/cli-service/composer.lock | 2554 ----------------- .../Commands/Hm_BaseCommand.php | 4 +- .../Commands/Hm_CheckMailCommand.php | 4 +- .../Commands/Hm_ListCommandsCommand.php | 4 +- .../Events/Hm_BaseEvent.php | 4 +- .../Hm_bootstrap.php | 0 .../Providers/Hm_CommandServiceProvider.php | 10 +- .../Traits/Hm_ScheduleFrequencyManager.php | 4 +- .../phpunit/command.php | 32 +- tests/phpunit/mocks.php | 1 - tests/phpunit/phpunit.xml | 6 +- tests/phpunit/run.sh | 2 +- .../phpunit/services/mocks.php | 8 +- 19 files changed, 91 insertions(+), 2688 deletions(-) delete mode 100644 packages/cli-service/.gitignore delete mode 100644 packages/cli-service/composer.json delete mode 100644 packages/cli-service/composer.lock rename packages/cli-service/src/Commands/BaseCommand.php => services/Commands/Hm_BaseCommand.php (97%) rename packages/cli-service/src/Commands/CheckMailCommand.php => services/Commands/Hm_CheckMailCommand.php (92%) rename packages/cli-service/src/Commands/ListCommandsCommand.php => services/Commands/Hm_ListCommandsCommand.php (94%) rename packages/cli-service/src/Events/BaseEvent.php => services/Events/Hm_BaseEvent.php (82%) rename packages/cli-service/bootstrap.php => services/Hm_bootstrap.php (100%) rename packages/cli-service/src/Providers/CommandServiceProvider.php => services/Providers/Hm_CommandServiceProvider.php (80%) rename packages/cli-service/src/Traits/ScheduleFrequencyManager.php => services/Traits/Hm_ScheduleFrequencyManager.php (98%) rename packages/cli-service/tests/Commands/BaseCommandTest.php => tests/phpunit/command.php (77%) rename packages/cli-service/tests/Mocks/TestCommand.php => tests/phpunit/services/mocks.php (94%) diff --git a/composer.json b/composer.json index f896103157..92f6841c64 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,11 @@ ], "homepage": "https://cypht.org", "license": "LGPL-2.1", + "autoload": { + "psr-4": { + "Services\\": "services/" + } + }, "authors": [ { "name": "Jason Munro", @@ -38,7 +43,6 @@ "bacon/bacon-qr-code": "^1.0.3 || ^2.0.0", "christian-riesen/base32": "^1.3.2", "composer": "^2.0.0", - "cypht/cli-service": "*", "ext-curl": "*", "ext-fileinfo": "*", "ext-iconv": "*", @@ -50,7 +54,9 @@ "henrique-borba/php-sieve-manager": "^1.0", "league/commonmark": "^2.4", "paragonie/random_compat": "^2.0.18", + "php-di/php-di": "^7.0", "php": ">=8.1", + "symfony/console": "^6.4", "symfony/dotenv": "^4.3 || 5.4", "symfony/yaml": "~6.4.3", "thomaspark/bootswatch": "^5.3", @@ -88,13 +94,5 @@ "post-update-cmd": "composer suggest", "post-package-install": "composer suggest", "post-install-cmd": "composer suggest" - }, - "minimum-stability": "dev", - "prefer-stable": true, - "repositories": [ - { - "type": "path", - "url": "./packages/cli-service" - } - ] + } } diff --git a/composer.lock b/composer.lock index bbb87cf79d..b9041e229a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d664be91483f1546046fb1b242ae4239", + "content-hash": "418c062e366f9db13a46adee68ef581e", "packages": [ { "name": "bacon/bacon-qr-code", @@ -119,46 +119,6 @@ }, "time": "2021-02-26T10:19:33+00:00" }, - { - "name": "cypht/cli-service", - "version": "1.0.0", - "dist": { - "type": "path", - "url": "./packages/cli-service", - "reference": "5d38f44f996511d8ec37628fb3c268d8e05e2f45" - }, - "require": { - "php-di/php-di": "^7.0", - "symfony/console": "^5.3 || ^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Cypht\\Service\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Cypht\\Service\\Tests\\": "tests/" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Steven Ngesera", - "email": "muhngesteven@gmail.com" - } - ], - "description": "A package that integrates with Cypht webmail to provide real-time email notifications using WebSockets and an event-driven scheduler system. It leverages Symfony's console commands and Ratchet for handling WebSocket connections.", - "transport-options": { - "relative": true - } - }, { "name": "dasprid/enum", "version": "1.0.6", @@ -4390,9 +4350,9 @@ } ], "aliases": [], - "minimum-stability": "dev", + "minimum-stability": "stable", "stability-flags": [], - "prefer-stable": true, + "prefer-stable": false, "prefer-lowest": false, "platform": { "composer": "^2.0.0", diff --git a/console b/console index 4a198a4160..34589b7ab1 100644 --- a/console +++ b/console @@ -1,21 +1,53 @@ #!/usr/bin/env php load(); + +/* get configuration */ +$config = new Hm_Site_Config_File(); +/* set default TZ */ +date_default_timezone_set($config->get('default_setting_timezone', 'UTC')); +/* set the default since and per_source values */ +$environment->define_default_constants($config); + +/* setup ini settings */ +if (!$config->get('disable_ini_settings')) { + require APP_PATH.'lib/ini_set.php'; +} use Symfony\Component\Console\Application; -use Cypht\Service\Providers\CommandServiceProvider; +use Services\Providers\Hm_CommandServiceProvider; -$container = require __DIR__ . '/packages/cli-service/bootstrap.php'; +$container = require __DIR__ . '/services/Hm_bootstrap.php'; $application = new Application('Cypht Console', '1.0'); // Register commands automatically using the service provider -$commandServiceProvider = new CommandServiceProvider(); +$commandServiceProvider = new Hm_CommandServiceProvider(); $commandServiceProvider->register($application, $container); // Run the console application diff --git a/index.php b/index.php index efaae8032e..f2b3a8d903 100644 --- a/index.php +++ b/index.php @@ -49,9 +49,7 @@ require APP_PATH.'lib/ini_set.php'; } /* process the request */ -if (!defined('SKIP_HM_DISPATCH')) { - new Hm_Dispatch($config); -} +new Hm_Dispatch($config); /* log some debug stats about the page */ if (DEBUG_MODE) { diff --git a/packages/cli-service/.gitignore b/packages/cli-service/.gitignore deleted file mode 100644 index 57872d0f1e..0000000000 --- a/packages/cli-service/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/vendor/ diff --git a/packages/cli-service/composer.json b/packages/cli-service/composer.json deleted file mode 100644 index b4d50a0a11..0000000000 --- a/packages/cli-service/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "cypht/cli-service", - "description": "A package that integrates with Cypht webmail to provide real-time email notifications using WebSockets and an event-driven scheduler system. It leverages Symfony's console commands and Ratchet for handling WebSocket connections.", - "type": "library", - "require": { - "symfony/console": "^5.3 || ^6.0", - "php-di/php-di": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "license": "MIT", - "autoload": { - "psr-4": { - "Cypht\\Service\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\": "tests/" - } - }, - "authors": [ - { - "name": "Steven Ngesera", - "email": "muhngesteven@gmail.com" - } - ], - "version": "1.0.0", - "minimum-stability": "dev" -} diff --git a/packages/cli-service/composer.lock b/packages/cli-service/composer.lock deleted file mode 100644 index e0ccd85f88..0000000000 --- a/packages/cli-service/composer.lock +++ /dev/null @@ -1,2554 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "9e47578e7f13c92732e2958f91bcd3a3", - "packages": [ - { - "name": "laravel/serializable-closure", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "43244bfd7161fdbce7419c76f9a776b7d705b333" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/43244bfd7161fdbce7419c76f9a776b7d705b333", - "reference": "43244bfd7161fdbce7419c76f9a776b7d705b333", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.61|^3.0", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2024-09-24T15:52:47+00:00" - }, - { - "name": "php-di/invoker", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2023-09-08T09:24:21+00:00" - }, - { - "name": "php-di/php-di", - "version": "7.0.7", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=8.0", - "php-di/invoker": "^2.0", - "psr/container": "^1.1 || ^2.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3", - "friendsofphp/proxy-manager-lts": "^1", - "mnapoli/phpunit-easymock": "^1.3", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.6" - }, - "suggest": { - "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2024-07-21T15:55:45+00:00" - }, - { - "name": "psr/container", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "707984727bd5b2b670e59559d3ed2500240cf875" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/707984727bd5b2b670e59559d3ed2500240cf875", - "reference": "707984727bd5b2b670e59559d3ed2500240cf875", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container" - }, - "time": "2023-09-22T11:11:30+00:00" - }, - { - "name": "symfony/console", - "version": "6.4.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f793dd5a7d9ae9923e35d0503d08ba734cec1d79", - "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/6.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-09T08:40:40+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "dev-main", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.6-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/main" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "default-branch": true, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "default-branch": true, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "default-branch": true, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2369cb908b33d7b7518cce042615de430142497f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2369cb908b33d7b7518cce042615de430142497f", - "reference": "2369cb908b33d7b7518cce042615de430142497f", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "default-branch": true, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-10T14:38:51+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "dev-main", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "5ad38698559cf88b6296629e19b15ef3239c9d7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/5ad38698559cf88b6296629e19b15ef3239c9d7a", - "reference": "5ad38698559cf88b6296629e19b15ef3239c9d7a", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.6-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/main" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/string", - "version": "7.2.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "205580699b4d3e11f7b679faf2c0f57ffca6981c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/205580699b4d3e11f7b679faf2c0f57ffca6981c", - "reference": "205580699b4d3e11f7b679faf2c0f57ffca6981c", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/7.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-23T06:56:12+00:00" - } - ], - "packages-dev": [ - { - "name": "myclabs/deep-copy", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "default-branch": true, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2024-06-12T14:39:25+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.3.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" - }, - "time": "2024-10-08T18:51:32+00:00" - }, - { - "name": "phar-io/manifest", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "8b818f1073841c1cb59547bb5853871ce8e7569f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8b818f1073841c1cb59547bb5853871ce8e7569f", - "reference": "8b818f1073841c1cb59547bb5853871ce8e7569f", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.3.1", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.5.36" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-10-09T06:19:11+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "585510209bc946b0b5686db50aaa89a20d574f94" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/585510209bc946b0b5686db50aaa89a20d574f94", - "reference": "585510209bc946b0b5686db50aaa89a20d574f94", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:03:34+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "b278fa2f7b3adbb1601635ccae52dd2ae28208f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/b278fa2f7b3adbb1601635ccae52dd2ae28208f7", - "reference": "b278fa2f7b3adbb1601635ccae52dd2ae28208f7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.5" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:04:02+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "20d18853f9a3da202c01565c55130fb2c95f0f57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/20d18853f9a3da202c01565c55130fb2c95f0f57", - "reference": "20d18853f9a3da202c01565c55130fb2c95f0f57", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:04:29+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2bcef0a54b678667378fb2f6eabd332eec28ed7e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2bcef0a54b678667378fb2f6eabd332eec28ed7e", - "reference": "2bcef0a54b678667378fb2f6eabd332eec28ed7e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:04:59+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "81306cb1b0b037d60bbd7cfc915f13626ec790e9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/81306cb1b0b037d60bbd7cfc915f13626ec790e9", - "reference": "81306cb1b0b037d60bbd7cfc915f13626ec790e9", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2024-10-23T09:43:30+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "2.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c37c9e6127dcbaeb0c4778afb9101f61f2bc5969" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c37c9e6127dcbaeb0c4778afb9101f61f2bc5969", - "reference": "c37c9e6127dcbaeb0c4778afb9101f61f2bc5969", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:24:16+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "83fcced885622ae51329748de2f713c2ff460eed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/83fcced885622ae51329748de2f713c2ff460eed", - "reference": "83fcced885622ae51329748de2f713c2ff460eed", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:25:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "85662a56e440cdbcb14a6d9e3e07feb3b654b6db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/85662a56e440cdbcb14a6d9e3e07feb3b654b6db", - "reference": "85662a56e440cdbcb14a6d9e3e07feb3b654b6db", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:55:58+00:00" - }, - { - "name": "sebastian/comparator", - "version": "5.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-10-18T14:56:07+00:00" - }, - { - "name": "sebastian/complexity", - "version": "3.2.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "3d89639d42d385d4e3ba1a235a24eafe40ab995c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/3d89639d42d385d4e3ba1a235a24eafe40ab995c", - "reference": "3d89639d42d385d4e3ba1a235a24eafe40ab995c", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:57:41+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "eb50d310beb710c1fbbb8de600ed9d7ec28be385" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/eb50d310beb710c1fbbb8de600ed9d7ec28be385", - "reference": "eb50d310beb710c1fbbb8de600ed9d7ec28be385", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:58:49+00:00" - }, - { - "name": "sebastian/environment", - "version": "6.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8", - "reference": "febdd1dca486af4e1b8d1ed7a0db4ddab0d5a7a8", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T06:59:16+00:00" - }, - { - "name": "sebastian/exporter", - "version": "5.1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d", - "reference": "4aceb7e6a74ec471a1ad824d7202ec4ea5c7433d", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:00:08+00:00" - }, - { - "name": "sebastian/global-state", - "version": "6.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "63e42d7d47f0afeb531dce8a2e69173c97335d84" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/63e42d7d47f0afeb531dce8a2e69173c97335d84", - "reference": "63e42d7d47f0afeb531dce8a2e69173c97335d84", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:00:35+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "2.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "be42b61b0c09d80e31315394ad02f9e8265c5780" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/be42b61b0c09d80e31315394ad02f9e8265c5780", - "reference": "be42b61b0c09d80e31315394ad02f9e8265c5780", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:01:14+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "5.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "67c331ffb8e77a9d8129193dd0c60f568c2bd49f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/67c331ffb8e77a9d8129193dd0c60f568c2bd49f", - "reference": "67c331ffb8e77a9d8129193dd0c60f568c2bd49f", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:02:13+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "3.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "bcd028a491280d802fe97e72748326196bcde7f9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bcd028a491280d802fe97e72748326196bcde7f9", - "reference": "bcd028a491280d802fe97e72748326196bcde7f9", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:02:50+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "5.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "18e76a35cec6ee7547029064ea1a257d9d75ebf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/18e76a35cec6ee7547029064ea1a257d9d75ebf0", - "reference": "18e76a35cec6ee7547029064ea1a257d9d75ebf0", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:05:35+00:00" - }, - { - "name": "sebastian/type", - "version": "4.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e", - "reference": "d59649a0eb6a5fdd22ea41e87fc1ca268f1c6a8e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/4.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-29T07:06:05+00:00" - }, - { - "name": "sebastian/version", - "version": "4.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "92ded34a7ac2cece0a3ae576e4850fcdd2276634" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/92ded34a7ac2cece0a3ae576e4850fcdd2276634", - "reference": "92ded34a7ac2cece0a3ae576e4850fcdd2276634", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/4.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-09-07T13:16:02+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:36:25+00:00" - } - ], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.6.0" -} diff --git a/packages/cli-service/src/Commands/BaseCommand.php b/services/Commands/Hm_BaseCommand.php similarity index 97% rename from packages/cli-service/src/Commands/BaseCommand.php rename to services/Commands/Hm_BaseCommand.php index 5186b09a1d..7cafdb9b85 100644 --- a/packages/cli-service/src/Commands/BaseCommand.php +++ b/services/Commands/Hm_BaseCommand.php @@ -1,6 +1,6 @@ add($command); } diff --git a/packages/cli-service/src/Traits/ScheduleFrequencyManager.php b/services/Traits/Hm_ScheduleFrequencyManager.php similarity index 98% rename from packages/cli-service/src/Traits/ScheduleFrequencyManager.php rename to services/Traits/Hm_ScheduleFrequencyManager.php index 1c208d161e..270cd6a264 100644 --- a/packages/cli-service/src/Traits/ScheduleFrequencyManager.php +++ b/services/Traits/Hm_ScheduleFrequencyManager.php @@ -1,9 +1,9 @@ container = $this->createMock(\DI\Container::class); + require_once __DIR__.'/services/mocks.php'; + $this->container = $this->getMockBuilder(ContainerInterface::class) + ->disableOriginalConstructor() + ->getMock(); - $this->command = new TestCommand($this->container); + $this->command = new Hm_TestCommand($this->container); $this->input = new ArrayInput([]); $this->output = new BufferedOutput(); @@ -32,14 +32,18 @@ protected function setUp(): void $this->command->run($this->input, $this->output); } - public function testSuccessOutput() + /** + * @preserveGlobalState disabled + * @runInSeparateProcess + */ + public function test_success_output() { $this->command->success('This is a success message.'); $this->assertStringContainsString('This is a success message.', $this->output->fetch()); } - public function testErrorOutput(): void + public function test_error_output(): void { // Simulate an error message $this->command->error('This is an error message.'); @@ -51,7 +55,7 @@ public function testErrorOutput(): void $this->assertStringContainsString('This is an error message.', $outputContent); } - public function testInfoOutput(): void + public function test_info_output(): void { // Simulate an informational message $this->command->info('This is an info message.'); @@ -63,7 +67,7 @@ public function testInfoOutput(): void $this->assertStringContainsString('This is an info message.', $outputContent); } - public function testWarningOutput(): void + public function test_warning_output(): void { // Simulate a warning message $this->command->warning('This is a warning message.'); @@ -75,7 +79,7 @@ public function testWarningOutput(): void $this->assertStringContainsString('This is a warning message.', $outputContent); } - public function testTextOutput(): void + public function test_text_output(): void { // Simulate a plain text message $this->command->text('This is a plain text message.'); @@ -87,7 +91,7 @@ public function testTextOutput(): void $this->assertStringContainsString('This is a plain text message.', $outputContent); } - public function testExecute() + public function test_execute() { $exitCode = $this->command->run($this->input, $this->output); @@ -95,7 +99,7 @@ public function testExecute() $this->assertStringContainsString('Test command executed successfully.', $this->output->fetch()); } - // public function testGetService() + // public function test_get_service() // { // // TODO: Implement testGetService() method // } diff --git a/tests/phpunit/mocks.php b/tests/phpunit/mocks.php index 84d750b511..0eb5d79e6e 100644 --- a/tests/phpunit/mocks.php +++ b/tests/phpunit/mocks.php @@ -1,5 +1,4 @@ ./tags.php + + ./command.php + @@ -138,9 +141,6 @@ ./modules/core/output_modules.php ./modules/core/output_modules_debug.php - - ./../../packages/cli-service/tests - diff --git a/tests/phpunit/run.sh b/tests/phpunit/run.sh index b2436a4989..a5fd4f3604 100755 --- a/tests/phpunit/run.sh +++ b/tests/phpunit/run.sh @@ -42,4 +42,4 @@ else exit 1 fi -phpunit --bootstrap vendor/autoload.php --configuration ${SCRIPT_DIR}/phpunit.xml --testdox $@ +php vendor/phpunit/phpunit/phpunit --bootstrap vendor/autoload.php --configuration ${SCRIPT_DIR}/phpunit.xml --testdox $@ diff --git a/packages/cli-service/tests/Mocks/TestCommand.php b/tests/phpunit/services/mocks.php similarity index 94% rename from packages/cli-service/tests/Mocks/TestCommand.php rename to tests/phpunit/services/mocks.php index a8d0469514..0fe9448c3a 100644 --- a/packages/cli-service/tests/Mocks/TestCommand.php +++ b/tests/phpunit/services/mocks.php @@ -1,8 +1,6 @@ Date: Sat, 26 Oct 2024 16:59:02 +0300 Subject: [PATCH 03/21] Job & Queue added with multiple drivers --- .env.example | 11 + composer.json | 1 + composer.lock | 485 ++++++++++++++++++- config/redis.php | 9 + config/sqs.php | 8 + lib/db.php | 4 + lib/redis.php | 137 ++++++ lib/sqs.php | 139 ++++++ services/Commands/Hm_CheckMailCommand.php | 2 + services/Contracts/Hm_Job.php | 9 + services/Contracts/Queue/Hm_ShouldQueue.php | 12 + services/Jobs/Hm_BaseJob.php | 20 + services/Jobs/Hm_ProcessNewEmail.php | 28 ++ services/Queue/Drivers/Hm_AmazonSQSQueue.php | 73 +++ services/Queue/Drivers/Hm_DatabaseQueue.php | 72 +++ services/Queue/Drivers/Hm_RedisQueue.php | 69 +++ services/Queue/Hm_JobDispatcher.php | 51 ++ services/Queue/Hm_QueueManager.php | 28 ++ services/Queue/Hm_QueueWorker.php | 41 ++ services/Traits/Hm_InteractsWithQueue.php | 74 +++ services/readme.rd | 16 + tests/phpunit/mocks.php | 1 + 22 files changed, 1287 insertions(+), 3 deletions(-) create mode 100644 config/redis.php create mode 100644 config/sqs.php create mode 100644 lib/redis.php create mode 100644 lib/sqs.php create mode 100644 services/Contracts/Hm_Job.php create mode 100644 services/Contracts/Queue/Hm_ShouldQueue.php create mode 100644 services/Jobs/Hm_BaseJob.php create mode 100644 services/Jobs/Hm_ProcessNewEmail.php create mode 100644 services/Queue/Drivers/Hm_AmazonSQSQueue.php create mode 100644 services/Queue/Drivers/Hm_DatabaseQueue.php create mode 100644 services/Queue/Drivers/Hm_RedisQueue.php create mode 100644 services/Queue/Hm_JobDispatcher.php create mode 100644 services/Queue/Hm_QueueManager.php create mode 100644 services/Queue/Hm_QueueWorker.php create mode 100644 services/Traits/Hm_InteractsWithQueue.php create mode 100644 services/readme.rd diff --git a/.env.example b/.env.example index 297b515afe..278968ed96 100644 --- a/.env.example +++ b/.env.example @@ -211,3 +211,14 @@ FANCY_LOGIN=false WIN_CACERT_DIR= JS_EXCLUDE_DEPS= + +AWS_KEY='your-aws-access-key' +AWS_SECRET='your-aws-secret-key' +AWS_REGION=us-east-1 +AWS_SQS_QUEUE_URL='https://sqs.us-east-1.amazonaws.com/123456789012/your-queue-name' + +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_USERNAME=null +REDIS_PWD=null +REDIS_PREFIX= diff --git a/composer.json b/composer.json index 92f6841c64..22a7f2f3b6 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "docs": "https://cypht.org/documentation.html" }, "require": { + "aws/aws-sdk-php": "*", "bacon/bacon-qr-code": "^1.0.3 || ^2.0.0", "christian-riesen/base32": "^1.3.2", "composer": "^2.0.0", diff --git a/composer.lock b/composer.lock index b9041e229a..86d3c93d28 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,160 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "418c062e366f9db13a46adee68ef581e", + "content-hash": "c5e622d9e95e73ff9041cd2b4a7a8882", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.324.11", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "a667ca29db288af083c3d8cff2e7f32d89fd4cf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a667ca29db288af083c3d8cff2e7f32d89fd4cf0", + "reference": "a667ca29db288af083c3d8cff2e7f32d89fd4cf0", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.324.11" + }, + "time": "2024-10-25T18:07:16+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "2.0.8", @@ -305,6 +457,215 @@ }, "time": "2023-11-17T15:01:25+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-10-17T10:06:22+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.7.0", @@ -739,6 +1100,72 @@ ], "time": "2022-12-11T20:36:23+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" + }, { "name": "nette/schema", "version": "v1.3.2", @@ -1225,6 +1652,58 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, { "name": "psr/http-factory", "version": "1.1.0", @@ -4351,7 +4830,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4365,7 +4844,7 @@ "ext-session": "*", "php": ">=8.1" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1" }, diff --git a/config/redis.php b/config/redis.php new file mode 100644 index 0000000000..6c1c921992 --- /dev/null +++ b/config/redis.php @@ -0,0 +1,9 @@ + env('REDIS_HOST',"127.0.0.1"), + 'redis_port' => env('REDIS_PORT',"6379"), + 'redis_username' => env('REDIS_USERNAME', null), + 'redis_password' => env('REDIS_PWD', null), + 'redis_prefix' => env('REDIS_PREFIX', ''), +]; \ No newline at end of file diff --git a/config/sqs.php b/config/sqs.php new file mode 100644 index 0000000000..16ce606123 --- /dev/null +++ b/config/sqs.php @@ -0,0 +1,8 @@ + env('AWS_KEY',""), + 'aws_secret' => env('AWS_SECRET',""), + 'aws_region' => env('AWS_REGION', 'us-east-1'), + 'sqs_queue_url' => env('AWS_SQS_QUEUE_URL', null), +]; \ No newline at end of file diff --git a/lib/db.php b/lib/db.php index d5f7b8f09c..001c8dc049 100644 --- a/lib/db.php +++ b/lib/db.php @@ -60,6 +60,10 @@ static private function db_key() { ); } + static public function getConfig() { + return self::$config; + } + /** * Build a DSN to connect to the db with * @return string diff --git a/lib/redis.php b/lib/redis.php new file mode 100644 index 0000000000..de579abd91 --- /dev/null +++ b/lib/redis.php @@ -0,0 +1,137 @@ + $site_config->get('redis_host', false), + 'redis_port' => $site_config->get('redis_port', false), + 'redis_password' => $site_config->get('redis_password', null), // Optional + 'redis_username' => $site_config->get('redis_username', null), // Optional for Redis 6+ + 'redis_prefix' => $site_config->get('redis_prefix', ''), // Optional prefix for keys + ]; + + foreach (self::$required_config as $v) { + if (!self::$config[$v]) { + Hm_Debug::add(sprintf('Missing configuration setting for %s', $v)); + } + } + } + + /** + * Connect to a Redis server + * @param object $site_config site settings + * @return object|false Redis connection on success + */ + static public function connect($site_config) { + self::parse_config($site_config); + + if (self::$redis) { + return self::$redis; + } + + try { + self::$redis = new Redis(); + self::$redis->connect(self::$config['redis_host'], self::$config['redis_port']); + + // Authenticate with password if provided + if (self::$config['redis_password']) { + self::$redis->auth(self::$config['redis_password']); + } + + // Authenticate with username if provided (for Redis 6+) + if (self::$config['redis_username']) { + self::$redis->auth(self::$config['redis_username'], self::$config['redis_password']); + } + + // Optionally, set a key prefix + if (self::$config['redis_prefix']) { + self::$redis->setOption(Redis::OPT_PREFIX, self::$config['redis_prefix']); + } + + Hm_Debug::add(sprintf('Connected to Redis at %s:%s', self::$config['redis_host'], self::$config['redis_port'])); + return self::$redis; + } catch (Exception $oops) { + Hm_Debug::add($oops->getMessage()); + Hm_Msgs::add('ERRUnable to connect to Redis. Please check your configuration settings and try again.'); + return false; + } + } + + /** + * Set a value in Redis + * @param string $key + * @param mixed $value + * @param int|null $expire + * @return bool + */ + static public function set($key, $value, $expire = null) { + if (!self::$redis) { + return false; + } + return self::$redis->set($key, $value, $expire); + } + + /** + * Get a value from Redis + * @param string $key + * @return mixed + */ + static public function get($key) { + if (!self::$redis) { + return false; + } + return self::$redis->get($key); + } + + /** + * Delete a key from Redis + * @param string $key + * @return bool + */ + static public function delete($key) { + if (!self::$redis) { + return false; + } + return self::$redis->delete($key); + } + + /** + * Check if a key exists in Redis + * @param string $key + * @return bool + */ + static public function exists($key) { + if (!self::$redis) { + return false; + } + return self::$redis->exists($key); + } + + /** + * Get the Redis connection + * @return Redis|null + */ + static public function getConnection() { + return self::$redis; + } +} diff --git a/lib/sqs.php b/lib/sqs.php new file mode 100644 index 0000000000..f3c7e73c06 --- /dev/null +++ b/lib/sqs.php @@ -0,0 +1,139 @@ + $site_config->get('aws_key', false), + 'aws_secret' => $site_config->get('aws_secret', false), + 'aws_region' => $site_config->get('aws_region', false), + 'sqs_queue_url' => $site_config->get('sqs_queue_url', false), + ]; + + foreach (self::$required_config as $v) { + if (!self::$config[$v]) { + Hm_Debug::add(sprintf('Missing configuration setting for %s', $v)); + } + } + } + + /** + * Connect to Amazon SQS + * @param object $site_config site settings + * @return SqsClient|false SQS Client on success + */ + static public function connect($site_config) { + self::parse_config($site_config); + + if (self::$sqsClient) { + return self::$sqsClient; + } + + try { + self::$sqsClient = new SqsClient([ + 'version' => 'latest', + 'region' => self::$config['aws_region'], + 'credentials' => [ + 'key' => self::$config['aws_key'], + 'secret' => self::$config['aws_secret'], + ], + ]); + Hm_Debug::add('Connected to Amazon SQS'); + return self::$sqsClient; + } catch (AwsException $e) { + Hm_Debug::add($e->getMessage()); + Hm_Msgs::add('ERRUnable to connect to Amazon SQS. Please check your configuration settings and try again.'); + return false; + } + } + + /** + * Send a message to the SQS queue + * @param string $message + * @return string|false Message ID or false on failure + */ + static public function sendMessage($message, int $delay = 0) { + if (!self::$sqsClient) { + return false; + } + + try { + $result = self::$sqsClient->sendMessage([ + 'QueueUrl' => self::$config['sqs_queue_url'], + 'MessageBody' => $message, + 'DelaySeconds' => $delay, + ]); + return $result['MessageId']; + } catch (AwsException $e) { + Hm_Debug::add($e->getMessage()); + return false; + } + } + + /** + * Receive messages from the SQS queue + * @param int $maxMessages + * @return array + */ + static public function receiveMessages($maxMessages = 1) { + if (!self::$sqsClient) { + return []; + } + + try { + $result = self::$sqsClient->receiveMessage([ + 'QueueUrl' => self::$config['sqs_queue_url'], + 'MaxNumberOfMessages' => $maxMessages, + 'WaitTimeSeconds' => 10, + ]); + return $result->get('Messages') ?: []; + } catch (AwsException $e) { + Hm_Debug::add($e->getMessage()); + return []; + } + } + + /** + * Delete a message from the SQS queue + * @param string $receiptHandle + * @return bool + */ + static public function deleteMessage($receiptHandle) { + if (!self::$sqsClient) { + return false; + } + + try { + self::$sqsClient->deleteMessage([ + 'QueueUrl' => self::$config['sqs_queue_url'], + 'ReceiptHandle' => $receiptHandle, + ]); + return true; + } catch (AwsException $e) { + Hm_Debug::add($e->getMessage()); + return false; + } + } +} diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index d53c1e86bf..dae364af9b 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -2,6 +2,7 @@ namespace Services\Commands; +use Services\Jobs\Hm_ProcessNewEmail; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -30,6 +31,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Example: Call the mail checking service from the container // $imap = $this->getService('Hm_Imap'); // $newMessages = $imap->search('UNSEEN'); + (new Hm_ProcessNewEmail('muhngesteven@gmail.com'))->handle(); if (!empty($newMessages)) { $this->success('You have new messages!'); diff --git a/services/Contracts/Hm_Job.php b/services/Contracts/Hm_Job.php new file mode 100644 index 0000000000..b044ce7e00 --- /dev/null +++ b/services/Contracts/Hm_Job.php @@ -0,0 +1,9 @@ +driver; + } + +} diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php new file mode 100644 index 0000000000..ef68e38d48 --- /dev/null +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -0,0 +1,28 @@ +email = $email; + } + + public function handle(): void + { + print("Processing email for {$this->email}\n"); + } + + public function failed(): void + { + print("Failed to process email for {$this->email}\n"); + } +} diff --git a/services/Queue/Drivers/Hm_AmazonSQSQueue.php b/services/Queue/Drivers/Hm_AmazonSQSQueue.php new file mode 100644 index 0000000000..6542f1e2b2 --- /dev/null +++ b/services/Queue/Drivers/Hm_AmazonSQSQueue.php @@ -0,0 +1,73 @@ +amazonSQS = $amazonSQS; + } + + /** + * Push the job to the queue + * + * @param Hm_BaseJob $job + * @return void + */ + public function push(Hm_BaseJob $job): void + { + $this->amazonSQS->sendMessage(serialize($job)); + } + + /** + * Pop the job from the queue + * + * @return Hm_BaseJob|null + */ + public function pop(): ?Hm_BaseJob + { + $messages = $this->amazonSQS->receiveMessages(); + + if (!empty($messages)) { + $message = $messages[0]; + $receiptHandle = $message['ReceiptHandle']; + + $this->amazonSQS->deleteMessage($receiptHandle); + + return unserialize($message['Body']); + } + + return null; + } + + /** + * Release the job back to the queue + * + * @param Hm_BaseJob $job + * @param int $delay + * @return void + */ + public function release(Hm_BaseJob $job, int $delay = 0): void + { + $messageBody = serialize($job); + $this->amazonSQS->sendMessage($messageBody, $delay); + } +} diff --git a/services/Queue/Drivers/Hm_DatabaseQueue.php b/services/Queue/Drivers/Hm_DatabaseQueue.php new file mode 100644 index 0000000000..38d549bf53 --- /dev/null +++ b/services/Queue/Drivers/Hm_DatabaseQueue.php @@ -0,0 +1,72 @@ +db = $db; + } + + /** + * Push the job to the queue + * + * @param Hm_BaseJob $job + * @return void + */ + public function push(Hm_BaseJob $job): void { + $dbh = $this->db->connect($this->db->getConfig()); + $sql = "INSERT INTO jobs (payload) VALUES (:payload)"; + $this->db->execute($dbh, $sql, ['payload' => serialize($job)], 'insert'); + } + + /** + * Pop the job from the queue + * + * @return Hm_BaseJob|null + */ + public function pop(): ?Hm_BaseJob { + $dbh = $this->db->connect($this->db->getConfig()); + $sql = "SELECT * FROM jobs ORDER BY id ASC LIMIT 1"; + $jobRecord = $this->db->execute($dbh, $sql, [], 'select'); + + if ($jobRecord) { + $deleteSql = "DELETE FROM jobs WHERE id = :id"; + $this->db->execute($dbh, $deleteSql, ['id' => $jobRecord['id']], 'modify'); + return unserialize($jobRecord['payload']); + } + + return null; + } + + /** + * Release the job back into the queue + * + * @param Hm_BaseJob $job + * @param int $delay + * @return void + */ + public function release(Hm_BaseJob $job, int $delay = 0): void { + if ($delay > 0) { + sleep($delay); + } + $this->push($job); + } +} diff --git a/services/Queue/Drivers/Hm_RedisQueue.php b/services/Queue/Drivers/Hm_RedisQueue.php new file mode 100644 index 0000000000..9fb0d5626f --- /dev/null +++ b/services/Queue/Drivers/Hm_RedisQueue.php @@ -0,0 +1,69 @@ +redis = $redis; + $this->queue = $queue; + } + + /** + * Push the job to the queue + * + * @param Hm_BaseJob $job + * @return void + */ + public function push(Hm_BaseJob $job): void { + // Use the Redis connection to push the serialized job to the queue + $this->redis->getConnection()->rpush($this->queue, serialize($job)); + } + + /** + * Pop the job from the queue + * + * @return Hm_BaseJob|null + */ + public function pop(): ?Hm_BaseJob { + $jobData = $this->redis->getConnection()->lpop($this->queue); + return $jobData ? unserialize($jobData) : null; + } + + /** + * Release the job back into the queue + * + * @param Hm_BaseJob $job + * @param int $delay + * @return void + */ + public function release(Hm_BaseJob $job, int $delay = 0): void { + if ($delay > 0) { + sleep($delay); + } + $this->push($job); + } +} diff --git a/services/Queue/Hm_JobDispatcher.php b/services/Queue/Hm_JobDispatcher.php new file mode 100644 index 0000000000..cf420ebd20 --- /dev/null +++ b/services/Queue/Hm_JobDispatcher.php @@ -0,0 +1,51 @@ +queueManager = $queueManager; + $this->defaultDriver = $defaultDriver; + } + + /** + * Dispatch the job to the queue + * + * @param Hm_BaseJob $job + * @param string|null $queue + * @return void + */ + public function dispatch(Hm_BaseJob $job, string $queue = null): void { + if ($job instanceof Hm_ShouldQueue) { + $driver = $job->driver ?? $this->defaultDriver; + $queueDriver = $this->queueManager->getDriver($driver); + + if ($queueDriver) { + $queueDriver->push($job); + } else { + throw new \Exception("Queue driver {$driver} not found."); + } + }else { + $job->handle(); + } + // $driver = $this->queueManager->getDriver($queue ?? $this->defaultDriver); + // $driver->push($job); + } +} \ No newline at end of file diff --git a/services/Queue/Hm_QueueManager.php b/services/Queue/Hm_QueueManager.php new file mode 100644 index 0000000000..e65acfeb2c --- /dev/null +++ b/services/Queue/Hm_QueueManager.php @@ -0,0 +1,28 @@ +drivers[$name] = $driver; + } + + public function getDriver(string $name): Hm_ShouldQueue + { + return $this->drivers[$name]; + } +} diff --git a/services/Queue/Hm_QueueWorker.php b/services/Queue/Hm_QueueWorker.php new file mode 100644 index 0000000000..f2ed4a6086 --- /dev/null +++ b/services/Queue/Hm_QueueWorker.php @@ -0,0 +1,41 @@ +queue = $queue; + } + + /** + * Work the queue + * + * @return void + */ + public function work(): void { + while ($job = $this->queue->pop()) + { + try { + $job->handle(); + } catch (\Exception $e) { + $job->failed(); + // Optionally release the job back to the queue with a delay + $this->queue->release($job, 30); + } + } + } +} diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/Hm_InteractsWithQueue.php new file mode 100644 index 0000000000..71280d9c90 --- /dev/null +++ b/services/Traits/Hm_InteractsWithQueue.php @@ -0,0 +1,74 @@ +getDriver(); + + // Call the appropriate method from the QueueManager to push the job + (new Hm_QueueManager)->getDriver($driver)->push($this, $data, $queue); + + echo "Job of type " . get_class($job) . " pushed to the queue successfully.\n"; + } + + /** + * Pop a job from the queue. + * + * @param string|null $queue Optional name of the queue. + * @return mixed The job from the queue or null if the queue is empty. + */ + public function pop($queue = null) + { + $driver = $this->getDriver(); + + // Call the appropriate method from the QueueManager to pop a job + $job = (new Hm_QueueManager)->getDriver($driver)->pop($queue); + + if ($job) { + echo "Job of type " . get_class($job) . " popped from the queue.\n"; + } else { + echo "No job available in the queue.\n"; + } + + return $job; + } + + /** + * Release a job back onto the queue with an optional delay. + * + * @param string|null $queue Optional name of the queue. + * @param int $delay The number of seconds to delay the release. + * @return void + */ + public function release($queue = null, $delay = 0) + { + $driver = $this->getDriver(); + + (new Hm_QueueManager)->getDriver($driver)->release($this, $queue, $delay); + + // echo "Job of type " . get_class($this) . " released back to the queue with a delay of {$delay} seconds.\n"; + } +} diff --git a/services/readme.rd b/services/readme.rd new file mode 100644 index 0000000000..f9e956fc38 --- /dev/null +++ b/services/readme.rd @@ -0,0 +1,16 @@ +#Queue usage example: + +``` +// Setup queue manager and drivers +$queueManager = new QueueManager(); +$queueManager->addDriver('redis', new RedisQueue(new Redis())); +$queueManager->addDriver('database', new DatabaseQueue(new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass'))); + +// Initialize dispatcher and dispatch a job +$dispatcher = new JobDispatcher($queueManager); +$dispatcher->dispatch(new ProcessNewEmail('example@example.com')); + +// Initialize worker to process jobs from the queue +$worker = new QueueWorker($queueManager->getDriver('redis')); +$worker->work(); +``` \ No newline at end of file diff --git a/tests/phpunit/mocks.php b/tests/phpunit/mocks.php index 0eb5d79e6e..84d750b511 100644 --- a/tests/phpunit/mocks.php +++ b/tests/phpunit/mocks.php @@ -1,4 +1,5 @@ Date: Tue, 29 Oct 2024 21:26:44 +0300 Subject: [PATCH 04/21] Events & listeners, Scheduling added --- .env.example | 2 + composer.json | 2 +- composer.lock | 349 ++++++++---------- console | 55 +-- lib/cache.php | 10 +- lib/redis.php | 137 ------- lib/sqs.php | 2 +- services/Commands/Hm_CheckMailCommand.php | 6 + services/Commands/Hm_ListCommandsCommand.php | 6 + services/Commands/Hm_QueueWorkCommand.php | 53 +++ services/Contracts/Queue/Hm_ShouldQueue.php | 2 +- services/Contracts/Scheduling/Mutex.php | 10 + .../{ => Core}/Commands/Hm_BaseCommand.php | 2 +- services/Core/Events/Hm_BaseEvent.php | 19 + services/Core/Events/Hm_EventManager.php | 27 ++ services/{ => Core}/Jobs/Hm_BaseJob.php | 2 +- .../Queue/Drivers/Hm_AmazonSQSQueue.php | 2 +- .../Queue/Drivers/Hm_DatabaseQueue.php | 2 +- .../Queue/Drivers/Hm_RedisQueue.php | 2 +- .../{ => Core}/Queue/Hm_JobDispatcher.php | 9 +- services/{ => Core}/Queue/Hm_QueueManager.php | 2 +- services/{ => Core}/Queue/Hm_QueueWorker.php | 2 +- services/Core/Scheduling/CacheMutex.php | 72 ++++ services/Core/Scheduling/CommandTask.php | 73 ++++ services/Core/Scheduling/ScheduledTask.php | 199 ++++++++++ services/Core/Scheduling/Scheduler.php | 69 ++++ services/Events/Hm_BaseEvent.php | 20 - services/Events/Hm_NewEmailProcessedEvent.php | 19 + services/Hm_bootstrap.php | 90 ++++- services/Jobs/Hm_ProcessNewEmail.php | 12 +- services/Listeners/Hm_NewMaiListener.php | 12 + .../Providers/Hm_CommandServiceProvider.php | 47 ++- .../Providers/Hm_EventServiceProvider.php | 38 ++ .../Providers/Hm_QueueServiceProvider.php | 50 +++ .../Providers/Hm_SchedulerServiceProvider.php | 34 ++ services/Traits/Hm_EventDispatchable.php | 48 +++ .../Traits/Hm_ScheduleFrequencyManager.php | 29 +- services/readme.rd | 28 ++ tests/phpunit/services/mocks.php | 2 +- 39 files changed, 1111 insertions(+), 434 deletions(-) delete mode 100644 lib/redis.php create mode 100644 services/Commands/Hm_QueueWorkCommand.php create mode 100644 services/Contracts/Scheduling/Mutex.php rename services/{ => Core}/Commands/Hm_BaseCommand.php (98%) create mode 100644 services/Core/Events/Hm_BaseEvent.php create mode 100644 services/Core/Events/Hm_EventManager.php rename services/{ => Core}/Jobs/Hm_BaseJob.php (92%) rename services/{ => Core}/Queue/Drivers/Hm_AmazonSQSQueue.php (97%) rename services/{ => Core}/Queue/Drivers/Hm_DatabaseQueue.php (97%) rename services/{ => Core}/Queue/Drivers/Hm_RedisQueue.php (97%) rename services/{ => Core}/Queue/Hm_JobDispatcher.php (80%) rename services/{ => Core}/Queue/Hm_QueueManager.php (94%) rename services/{ => Core}/Queue/Hm_QueueWorker.php (96%) create mode 100644 services/Core/Scheduling/CacheMutex.php create mode 100644 services/Core/Scheduling/CommandTask.php create mode 100644 services/Core/Scheduling/ScheduledTask.php create mode 100644 services/Core/Scheduling/Scheduler.php delete mode 100644 services/Events/Hm_BaseEvent.php create mode 100644 services/Events/Hm_NewEmailProcessedEvent.php create mode 100644 services/Listeners/Hm_NewMaiListener.php create mode 100644 services/Providers/Hm_EventServiceProvider.php create mode 100644 services/Providers/Hm_QueueServiceProvider.php create mode 100644 services/Providers/Hm_SchedulerServiceProvider.php create mode 100644 services/Traits/Hm_EventDispatchable.php diff --git a/.env.example b/.env.example index 278968ed96..d81ba96e3e 100644 --- a/.env.example +++ b/.env.example @@ -212,6 +212,8 @@ WIN_CACERT_DIR= JS_EXCLUDE_DEPS= +QUEUE_CONNECTION=database + AWS_KEY='your-aws-access-key' AWS_SECRET='your-aws-secret-key' AWS_REGION=us-east-1 diff --git a/composer.json b/composer.json index 22a7f2f3b6..a178a85d3b 100644 --- a/composer.json +++ b/composer.json @@ -55,9 +55,9 @@ "henrique-borba/php-sieve-manager": "^1.0", "league/commonmark": "^2.4", "paragonie/random_compat": "^2.0.18", - "php-di/php-di": "^7.0", "php": ">=8.1", "symfony/console": "^6.4", + "symfony/dependency-injection": "*", "symfony/dotenv": "^4.3 || 5.4", "symfony/yaml": "~6.4.3", "thomaspark/bootswatch": "^5.3", diff --git a/composer.lock b/composer.lock index 86d3c93d28..14593bc26b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5e622d9e95e73ff9041cd2b4a7a8882", + "content-hash": "d0b61ae7feb455c0bda982d900d7357a", "packages": [ { "name": "aws/aws-crt-php", @@ -851,67 +851,6 @@ }, "time": "2024-08-29T06:33:14+00:00" }, - { - "name": "laravel/serializable-closure", - "version": "v1.3.5", - "source": { - "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "shasum": "" - }, - "require": { - "php": "^7.3|^8.0" - }, - "require-dev": { - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.61|^3.0", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\SerializableClosure\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" - } - ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], - "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" - }, - "time": "2024-09-23T13:33:08+00:00" - }, { "name": "league/commonmark", "version": "2.5.3", @@ -1368,134 +1307,6 @@ }, "time": "2022-02-16T17:07:03+00:00" }, - { - "name": "php-di/invoker", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", - "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "psr/container": "^1.0|^2.0" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "mnapoli/hard-mode": "~0.3.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Invoker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Generic and extensible callable invoker", - "homepage": "https://github.com/PHP-DI/Invoker", - "keywords": [ - "callable", - "dependency", - "dependency-injection", - "injection", - "invoke", - "invoker" - ], - "support": { - "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - } - ], - "time": "2023-09-08T09:24:21+00:00" - }, - { - "name": "php-di/php-di", - "version": "7.0.7", - "source": { - "type": "git", - "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", - "shasum": "" - }, - "require": { - "laravel/serializable-closure": "^1.0", - "php": ">=8.0", - "php-di/invoker": "^2.0", - "psr/container": "^1.1 || ^2.0" - }, - "provide": { - "psr/container-implementation": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3", - "friendsofphp/proxy-manager-lts": "^1", - "mnapoli/phpunit-easymock": "^1.3", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.6" - }, - "suggest": { - "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "DI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "The dependency injection container for humans", - "homepage": "https://php-di.org/", - "keywords": [ - "PSR-11", - "container", - "container-interop", - "dependency injection", - "di", - "ioc", - "psr11" - ], - "support": { - "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", - "type": "tidelift" - } - ], - "time": "2024-07-21T15:55:45+00:00" - }, { "name": "pimple/pimple", "version": "v3.5.0", @@ -1950,6 +1761,87 @@ ], "time": "2024-09-20T08:15:52+00:00" }, + { + "name": "symfony/dependency-injection", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:07:50+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v3.5.0", @@ -2735,6 +2627,83 @@ ], "time": "2024-09-20T08:15:52+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/yaml", "version": "v6.4.12", diff --git a/console b/console index 34589b7ab1..13dcc0842b 100644 --- a/console +++ b/console @@ -1,54 +1,27 @@ #!/usr/bin/env php load(); - -/* get configuration */ -$config = new Hm_Site_Config_File(); -/* set default TZ */ -date_default_timezone_set($config->get('default_setting_timezone', 'UTC')); -/* set the default since and per_source values */ -$environment->define_default_constants($config); - -/* setup ini settings */ -if (!$config->get('disable_ini_settings')) { - require APP_PATH.'lib/ini_set.php'; -} use Symfony\Component\Console\Application; -use Services\Providers\Hm_CommandServiceProvider; +use Symfony\Component\DependencyInjection\ContainerInterface; -$container = require __DIR__ . '/services/Hm_bootstrap.php'; +list($containerBuilder, $config) = require __DIR__ . '/services/Hm_bootstrap.php'; $application = new Application('Cypht Console', '1.0'); -// Register commands automatically using the service provider -$commandServiceProvider = new Hm_CommandServiceProvider(); -$commandServiceProvider->register($application, $container); +$commandServiceProvider = $containerBuilder->get('command.serviceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); +$commandServiceProvider->register($application, $containerBuilder); + +$queueServiceProvider = $containerBuilder->get('queue.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); +$queueServiceProvider->register($containerBuilder); + +$queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); +$queueServiceProvider->register($config, new Hm_Session_Setup($config)); + +$eventServiceProvider = $containerBuilder->get('event.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); +// $eventServiceProvider->setContainer($containerBuilder); +$eventServiceProvider->register(); // Run the console application $application->run(); diff --git a/lib/cache.php b/lib/cache.php index c8cacc9e45..24c4867f35 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -240,7 +240,7 @@ public function __construct($config) { /** * @return boolean */ - private function connect() { + public function connect() { $this->cache_con = Hm_Functions::redis(); try { if ($this->socket) { @@ -294,6 +294,14 @@ public function close() { } return $this->cache_con->close(); } + + /** + * Get the Redis connection + * @return Redis|null + */ + static public function getConnection() { + return self::$cache_con; + } } /** diff --git a/lib/redis.php b/lib/redis.php deleted file mode 100644 index de579abd91..0000000000 --- a/lib/redis.php +++ /dev/null @@ -1,137 +0,0 @@ - $site_config->get('redis_host', false), - 'redis_port' => $site_config->get('redis_port', false), - 'redis_password' => $site_config->get('redis_password', null), // Optional - 'redis_username' => $site_config->get('redis_username', null), // Optional for Redis 6+ - 'redis_prefix' => $site_config->get('redis_prefix', ''), // Optional prefix for keys - ]; - - foreach (self::$required_config as $v) { - if (!self::$config[$v]) { - Hm_Debug::add(sprintf('Missing configuration setting for %s', $v)); - } - } - } - - /** - * Connect to a Redis server - * @param object $site_config site settings - * @return object|false Redis connection on success - */ - static public function connect($site_config) { - self::parse_config($site_config); - - if (self::$redis) { - return self::$redis; - } - - try { - self::$redis = new Redis(); - self::$redis->connect(self::$config['redis_host'], self::$config['redis_port']); - - // Authenticate with password if provided - if (self::$config['redis_password']) { - self::$redis->auth(self::$config['redis_password']); - } - - // Authenticate with username if provided (for Redis 6+) - if (self::$config['redis_username']) { - self::$redis->auth(self::$config['redis_username'], self::$config['redis_password']); - } - - // Optionally, set a key prefix - if (self::$config['redis_prefix']) { - self::$redis->setOption(Redis::OPT_PREFIX, self::$config['redis_prefix']); - } - - Hm_Debug::add(sprintf('Connected to Redis at %s:%s', self::$config['redis_host'], self::$config['redis_port'])); - return self::$redis; - } catch (Exception $oops) { - Hm_Debug::add($oops->getMessage()); - Hm_Msgs::add('ERRUnable to connect to Redis. Please check your configuration settings and try again.'); - return false; - } - } - - /** - * Set a value in Redis - * @param string $key - * @param mixed $value - * @param int|null $expire - * @return bool - */ - static public function set($key, $value, $expire = null) { - if (!self::$redis) { - return false; - } - return self::$redis->set($key, $value, $expire); - } - - /** - * Get a value from Redis - * @param string $key - * @return mixed - */ - static public function get($key) { - if (!self::$redis) { - return false; - } - return self::$redis->get($key); - } - - /** - * Delete a key from Redis - * @param string $key - * @return bool - */ - static public function delete($key) { - if (!self::$redis) { - return false; - } - return self::$redis->delete($key); - } - - /** - * Check if a key exists in Redis - * @param string $key - * @return bool - */ - static public function exists($key) { - if (!self::$redis) { - return false; - } - return self::$redis->exists($key); - } - - /** - * Get the Redis connection - * @return Redis|null - */ - static public function getConnection() { - return self::$redis; - } -} diff --git a/lib/sqs.php b/lib/sqs.php index f3c7e73c06..689b42d8b9 100644 --- a/lib/sqs.php +++ b/lib/sqs.php @@ -42,7 +42,7 @@ static private function parse_config($site_config) { /** * Connect to Amazon SQS * @param object $site_config site settings - * @return SqsClient|false SQS Client on success + * @return this|false */ static public function connect($site_config) { self::parse_config($site_config); diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index dae364af9b..09bc062020 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -3,6 +3,7 @@ namespace Services\Commands; use Services\Jobs\Hm_ProcessNewEmail; +use Services\Core\Commands\Hm_BaseCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -17,6 +18,11 @@ class Hm_CheckMailCommand extends Hm_BaseCommand protected static $defaultName = 'check:mail'; + protected function configure() + { + $this->setDescription('Check for new mail'); + } + /** * Execute the console command. * diff --git a/services/Commands/Hm_ListCommandsCommand.php b/services/Commands/Hm_ListCommandsCommand.php index 41c07afb53..a51ff6a825 100644 --- a/services/Commands/Hm_ListCommandsCommand.php +++ b/services/Commands/Hm_ListCommandsCommand.php @@ -3,6 +3,7 @@ namespace Services\Commands; use Psr\Container\ContainerInterface; +use Services\Core\Commands\Hm_BaseCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Input\InputInterface; @@ -25,6 +26,11 @@ public function __construct(ContainerInterface $container) parent::__construct($container); } + protected function configure() + { + $this->setDescription('Lists all registered commands in the application.'); + } + /** * Execute the command to list all registered commands. * diff --git a/services/Commands/Hm_QueueWorkCommand.php b/services/Commands/Hm_QueueWorkCommand.php new file mode 100644 index 0000000000..1c5817601b --- /dev/null +++ b/services/Commands/Hm_QueueWorkCommand.php @@ -0,0 +1,53 @@ +setDescription('Start processing jobs on the queue') + ->addArgument('connection', InputArgument::OPTIONAL, 'The name of the queue connection to work') + ->addOption('name', null, InputOption::VALUE_OPTIONAL, 'The name of the worker', 'default') + ->addOption('queue', null, InputOption::VALUE_OPTIONAL, 'The names of the queues to work') + ->addOption('once', null, InputOption::VALUE_NONE, 'Only process the next job on the queue') + ->addOption('sleep', null, InputOption::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3) + ->addOption('tries', null, InputOption::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 1); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $connection = $input->getArgument('connection') ?: 'default'; + $queue = $input->getOption('queue') ?: 'default'; + + $output->writeln("Processing jobs from the [$queue] on connection [$connection]..."); + + if ($input->getOption('once')) { + $this->container->get('queue.worker')->work(); + } else { + while (true) { + $this->container->get('queue.worker')->work(); + sleep($input->getOption('sleep')); + } + } + + return Command::SUCCESS; + } +} diff --git a/services/Contracts/Queue/Hm_ShouldQueue.php b/services/Contracts/Queue/Hm_ShouldQueue.php index fbd900c155..18b05f3ad3 100644 --- a/services/Contracts/Queue/Hm_ShouldQueue.php +++ b/services/Contracts/Queue/Hm_ShouldQueue.php @@ -2,7 +2,7 @@ namespace Services\Contracts\Queue; -use Services\Jobs\Hm_BaseJob; +use Services\Core\Jobs\Hm_BaseJob; interface Hm_ShouldQueue { diff --git a/services/Contracts/Scheduling/Mutex.php b/services/Contracts/Scheduling/Mutex.php new file mode 100644 index 0000000000..8724a39b21 --- /dev/null +++ b/services/Contracts/Scheduling/Mutex.php @@ -0,0 +1,10 @@ +params = $params; + } + + public function getParams(): array + { + return $this->params; + } +} diff --git a/services/Core/Events/Hm_EventManager.php b/services/Core/Events/Hm_EventManager.php new file mode 100644 index 0000000000..48e262de4f --- /dev/null +++ b/services/Core/Events/Hm_EventManager.php @@ -0,0 +1,27 @@ +handle($event); + } + } + } +} diff --git a/services/Jobs/Hm_BaseJob.php b/services/Core/Jobs/Hm_BaseJob.php similarity index 92% rename from services/Jobs/Hm_BaseJob.php rename to services/Core/Jobs/Hm_BaseJob.php index 858ca1d6ad..9457025c9a 100644 --- a/services/Jobs/Hm_BaseJob.php +++ b/services/Core/Jobs/Hm_BaseJob.php @@ -1,6 +1,6 @@ queueManager = $queueManager; + $this->queueManager = $container->get('Hm_QueueManager');//$this->queueManager = $queueManager; $this->defaultDriver = $defaultDriver; } diff --git a/services/Queue/Hm_QueueManager.php b/services/Core/Queue/Hm_QueueManager.php similarity index 94% rename from services/Queue/Hm_QueueManager.php rename to services/Core/Queue/Hm_QueueManager.php index e65acfeb2c..6afb42692b 100644 --- a/services/Queue/Hm_QueueManager.php +++ b/services/Core/Queue/Hm_QueueManager.php @@ -1,6 +1,6 @@ cache = $cache; + } + + /** + * Create a lock for the task. + * + * @param Task $task The task instance + * @param int $expiresAt Time in seconds for the lock expiration + * @return bool Returns true if the lock was created, false otherwise + */ + public function create($task, $expiresAt) + { + $key = $this->getMutexKey($task); + + // Attempt to set the cache key only if it doesn’t exist + if (!$this->cache->get($key, false)) { + return $this->cache->set($key, time() + $expiresAt, $expiresAt); + } + + return false; // Lock already exists + } + + /** + * Check if a lock exists for the task. + * + * @param Task $task The task instance + * @return bool + */ + public function exists($task) + { + $key = $this->getMutexKey($task); + $lockExpiry = $this->cache->get($key, false); + + return $lockExpiry && $lockExpiry > time(); + } + + /** + * Release the lock for the task. + * + * @param Task $task The task instance + * @return void + */ + public function release($task) + { + $key = $this->getMutexKey($task); + $this->cache->del($key); + } + + /** + * Generate a unique key for the mutex. + * + * @param Task $task The task instance + * @return string + */ + private function getMutexKey($task) + { + return 'mutex_' . hash('sha256', $task->name); + } +} diff --git a/services/Core/Scheduling/CommandTask.php b/services/Core/Scheduling/CommandTask.php new file mode 100644 index 0000000000..6eb77ec50b --- /dev/null +++ b/services/Core/Scheduling/CommandTask.php @@ -0,0 +1,73 @@ +command = $fullCommand; + $this->mutex = $mutex; + } + + /** + * Prevent command overlap by using a cache-based mutex. + * + * @param int $expiresAt Expiration time in seconds + * @return $this + */ + public function withoutOverlapping($expiresAt = 1440) + { + $this->withoutOverlapping = true; + $this->expiresAt = $expiresAt; + + // Skip execution if mutex exists + return $this->skip(function () { + return $this->mutex->exists($this); + }); + } + + /** + * Run the command if it’s due and not overlapping. + */ + public function run() + { + if ($this->isDue() && (!$this->withoutOverlapping || $this->acquireMutex())) { + parent::run(); // Executes task + if ($this->withoutOverlapping) { + $this->releaseMutex(); + } + } + } + + /** + * Acquire the mutex to prevent overlapping. + * + * @return bool + */ + private function acquireMutex() + { + return $this->mutex->create($this, $this->expiresAt); + } + + /** + * Release the mutex after execution. + */ + private function releaseMutex() + { + $this->mutex->release($this); + } +} diff --git a/services/Core/Scheduling/ScheduledTask.php b/services/Core/Scheduling/ScheduledTask.php new file mode 100644 index 0000000000..beb7dd0324 --- /dev/null +++ b/services/Core/Scheduling/ScheduledTask.php @@ -0,0 +1,199 @@ +callback = $callback; + $this->name = $name; + $this->description = $description; + $this->tags = $tags; + $this->timezone = $timezone; + $this->expression = $expression; + $this->nextRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); + } + + public function enable() + { + $this->isEnabled = true; + } + + public function disable() + { + $this->isEnabled = false; + } + + // Check if the task is due + public function isDue() + { + return $this->isEnabled && new \DateTime('now', new \DateTimeZone($this->timezone)) >= $this->nextRunTime; + } + + // Execute the task + public function run() + { + if (!$this->isDue()) { + return; + } + + try { + call_user_func($this->callback); + $this->lastRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); + } catch (\Exception $e) { + // Log the error message + error_log("Error running task {$this->name}: " . $e->getMessage()); + } + + $this->scheduleNextRun(); + } + + // Schedule the next run time based on the cron expression + public function scheduleNextRun() + { + $this->nextRunTime = $this->calculateNextRunTime(); + } + + public function getNextRunTime() + { + return $this->nextRunTime; + } + + // Calculate the next run time based on the cron expression + private function calculateNextRunTime() + { + // Ensure the cron expression is valid + if (empty($this->expression)) { + throw new \InvalidArgumentException("Cron expression must be set."); + } + + // Split the cron expression into parts + $parts = preg_split('/\s+/', $this->expression); + if (count($parts) !== 5) { + throw new \InvalidArgumentException("Invalid cron expression: {$this->expression}"); + } + + // Extract the cron fields + list($minuteField, $hourField, $dayOfMonthField, $monthField, $dayOfWeekField) = $parts; + + $now = new \DateTime('now', new \DateTimeZone($this->timezone)); + $next = clone $now; + + // Calculate next minute + $nextMinute = $this->getNextFieldValue($next->format('i'), $minuteField, 0, 59); + if ($nextMinute !== null) { + $next->setTime($next->format('H'), $nextMinute); + } else { + $next->modify('+1 hour'); + $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); + } + + // Calculate next hour + $nextHour = $this->getNextFieldValue($next->format('H'), $hourField, 0, 23); + if ($nextHour !== null) { + $next->setTime($nextHour, $next->format('i')); + } else { + $next->modify('+1 day'); + $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); + } + + // Calculate next day of the month + $nextDay = $this->getNextFieldValue($next->format('d'), $dayOfMonthField, 1, 31); + if ($nextDay !== null) { + $next->setDate($next->format('Y'), $next->format('m'), $nextDay); + } else { + $next->modify('+1 month'); + $next->setDate($next->format('Y'), $next->format('m'), $this->getNextFieldValue(1, $dayOfMonthField, 1, 31)); + } + + // Calculate next month + $nextMonth = $this->getNextFieldValue($next->format('n'), $monthField, 1, 12); + if ($nextMonth !== null) { + $next->setDate($next->format('Y'), $nextMonth, $next->format('d')); + } else { + $next->modify('+1 year'); + $next->setDate($next->format('Y'), $this->getNextFieldValue(1, $monthField, 1, 12), $next->format('d')); + } + + // Calculate next day of the week + $nextDayOfWeek = $this->getNextFieldValue($next->format('w'), $dayOfWeekField, 0, 6); + if ($nextDayOfWeek !== null) { + while ($next->format('w') !== $nextDayOfWeek) { + $next->modify('+1 day'); + } + } else { + $next->modify('+1 week'); + } + + return $next; + } + + // Get the next valid value for a cron field + private function getNextFieldValue($currentValue, $field, $min, $max) + { + // Handle a field in cron syntax (minute, hour, day, month, weekday) + $values = []; + + if ($field === '*') { + return $min; + } + + foreach (explode(',', $field) as $part) { + if (strpos($part, '/') !== false) { + // Handle step values, e.g., */2 + list($base, $step) = explode('/', $part); + if ($base === '*') { + for ($i = $min; $i <= $max; $i += $step) { + $values[] = $i; + } + } + } elseif (strpos($part, '-') !== false) { + // Handle ranges, e.g., 1-5 + list($rangeStart, $rangeEnd) = explode('-', $part); + for ($i = $rangeStart; $i <= $rangeEnd; $i++) { + $values[] = $i; + } + } else { + // Single values + $values[] = (int)$part; + } + } + + // Filter and sort unique values + $values = array_unique(array_filter($values, function ($value) use ($min, $max) { + return $value >= $min && $value <= $max; + })); + + sort($values); + + // Find the next valid value + foreach ($values as $value) { + if ($value > $currentValue) { + return $value; + } + } + + return null; // If no valid next value is found + } + + // Getter methods for the task properties + public function getName() { return $this->name; } + public function getDescription() { return $this->description; } + public function getTags() { return $this->tags; } + public function getTimezone() { return $this->timezone; } +} diff --git a/services/Core/Scheduling/Scheduler.php b/services/Core/Scheduling/Scheduler.php new file mode 100644 index 0000000000..8f7c38bd28 --- /dev/null +++ b/services/Core/Scheduling/Scheduler.php @@ -0,0 +1,69 @@ +config = $config; + } + + public function command($command) + { + $cache = new Hm_Cache($this->config, new Hm_Session_Setup($this->config)); + $commandTask = new CommandTask($command, new CacheMutex($cache)); + $this->tasks[] = $commandTask; + return $commandTask; + } + + /** + * Register a new task with the given configuration. + * + * @param callable $callback + * @param string $name + * @param string $description + * @param array $tags + * @param string $timezone + * @return ScheduledTask + */ + public function register(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC') + { + $task = new ScheduledTask($callback, $name, $description, $tags, $timezone); + $this->tasks[] = $task; + + return $task; + } + + /** + * Run all due tasks. + * + * @return void + */ + public function run() + { + foreach ($this->tasks as $task) { + if ($task->isDue()) { + $task->run(); + } + } + } + + /** + * Display all scheduled tasks (for debugging). + * + * @return void + */ + public function displayScheduledTasks() + { + foreach ($this->tasks as $task) { + echo "Task Name: {$task->name}, Next Run Time: {$task->nextRunTime->format('Y-m-d H:i:s')}, Last Run Time: " . ($task->lastRunTime ? $task->lastRunTime->format('Y-m-d H:i:s') : 'Never') . "\n"; + } + } +} diff --git a/services/Events/Hm_BaseEvent.php b/services/Events/Hm_BaseEvent.php deleted file mode 100644 index 3efe0f5303..0000000000 --- a/services/Events/Hm_BaseEvent.php +++ /dev/null @@ -1,20 +0,0 @@ -payload = $payload; - } - - public function getPayload(): array - { - return $this->payload; - } - - abstract public function getEventName(): string; -} diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php new file mode 100644 index 0000000000..8c74ea7b3e --- /dev/null +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -0,0 +1,19 @@ +load(); + +/* get configuration */ +$config = new Hm_Site_Config_File(); +/* set default TZ */ +date_default_timezone_set($config->get('default_setting_timezone', 'UTC')); +/* set the default since and per_source values */ +$environment->define_default_constants($config); + +/* setup ini settings */ +if (!$config->get('disable_ini_settings')) { + require APP_PATH.'lib/ini_set.php'; +} +// Initialize the scheduler +// $scheduler = new Scheduler($config); + $containerBuilder = new ContainerBuilder(); -$containerBuilder->addDefinitions([ - -]); +// Register Hm_DB +$containerBuilder->register('db', Hm_DB::class) + ->setShared(true); + +// Register Hm_Redis +$containerBuilder->register('redis', Hm_Redis::class) + ->setShared(true); + +// Register Hm_AmazonSQS +$containerBuilder->register('amazon.sqs', Hm_AmazonSQS::class) + ->setShared(true); + +// Register Hm_QueueManager +$containerBuilder->register('queue.manager', Hm_QueueManager::class) + ->setShared(true); + +// Register Hm_JobDispatcher +$containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) + ->setShared(true); + +// Register Hm_Site_Config_File +$containerBuilder->register('Hm_Site_Config_File', Hm_Site_Config_File::class) + ->setShared(true); + +// Register Hm_CommandServiceProvider +$containerBuilder->register('command.serviceProvider', Hm_CommandServiceProvider::class) + ->setShared(true); + +// Register Hm_QueueServiceProvider +$containerBuilder->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) + // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) + // ->addArgument(null) + ->setShared(true); -$container = $containerBuilder->build(); +$containerBuilder->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) + ->setShared(true); +$containerBuilder->register('event.ServiceProvider', Hm_EventServiceProvider::class) + ->setShared(true); -return $container; +// return $containerBuilder; +return [$containerBuilder,$config]; diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php index ef68e38d48..b746cf712c 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -2,12 +2,15 @@ namespace Services\Jobs; +use Services\Core\Jobs\Hm_BaseJob; +use Services\Traits\Hm_EventDispatchable; use Services\Traits\Hm_InteractsWithQueue; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Events\Hm_NewEmailProcessedEvent; -class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue +class Hm_ProcessNewEmail extends Hm_BaseJob //implements Hm_ShouldQueue { - use Hm_InteractsWithQueue; + use Hm_EventDispatchable;//Hm_InteractsWithQueue; protected string $driver = 'database'; @@ -18,7 +21,10 @@ public function __construct(public string $email) public function handle(): void { - print("Processing email for {$this->email}\n"); + //fetch new email + //then process it + (new Hm_NewEmailProcessedEvent($this->email))->dispatch(); + } public function failed(): void diff --git a/services/Listeners/Hm_NewMaiListener.php b/services/Listeners/Hm_NewMaiListener.php new file mode 100644 index 0000000000..ae8672b5a5 --- /dev/null +++ b/services/Listeners/Hm_NewMaiListener.php @@ -0,0 +1,12 @@ +email}"); + //TO DO: we implment notification dispatch here + } +} \ No newline at end of file diff --git a/services/Providers/Hm_CommandServiceProvider.php b/services/Providers/Hm_CommandServiceProvider.php index 5d53e0c078..93aa25a3b1 100644 --- a/services/Providers/Hm_CommandServiceProvider.php +++ b/services/Providers/Hm_CommandServiceProvider.php @@ -2,9 +2,11 @@ namespace Services\Providers; +use Hm_Debug; use Psr\Container\ContainerInterface; -use Services\Commands\Hm_BaseCommand; +use Services\Core\Commands\Hm_BaseCommand; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Exception\InvalidArgumentException; class Hm_CommandServiceProvider { @@ -13,19 +15,52 @@ class Hm_CommandServiceProvider * * @param Application $application The Symfony console application. * @param ContainerInterface $container The dependency injection container. + * @return void */ public function register(Application $application, ContainerInterface $container): void { $commandNamespace = 'Services\Commands'; - $commandFiles = glob(__DIR__ . '/../Commands/*.php'); + $commandFiles = $this->getCommandFiles(); foreach ($commandFiles as $file) { $className = $commandNamespace . '\\' . basename($file, '.php'); - - if (class_exists($className) && is_subclass_of($className, Hm_BaseCommand::class)) { - $command = new $className($container); - $application->add($command); + try { + $this->registerCommand($application, $className, $container); + } catch (InvalidArgumentException $e) { + Hm_Debug::add(sprintf('Command registration escaped for %s: No need to register the command class.', $className)); } } } + + /** + * Get the command files from the commands directory. + * + * @return array List of command files. + */ + protected function getCommandFiles(): array + { + return glob(__DIR__ . '/../Commands/*.php'); + } + + /** + * Register a command with the application. + * + * This method checks if the given class name is a valid command class. + * If it is valid, the command is instantiated and added to the application. + * If not, the registration is simply skipped without any exceptions. + * + * @param Application $application The Symfony console application. + * @param string $className The fully qualified name of the command class. + * @param ContainerInterface $container The dependency injection container. + * @return void + */ + protected function registerCommand(Application $application, string $className, ContainerInterface $container): void + { + if (!class_exists($className) || !is_subclass_of($className, Hm_BaseCommand::class)) { + throw new InvalidArgumentException(sprintf('Class "%s" is not a valid command class.', $className)); + } + + $command = new $className($container); + $application->add($command); + } } diff --git a/services/Providers/Hm_EventServiceProvider.php b/services/Providers/Hm_EventServiceProvider.php new file mode 100644 index 0000000000..6a3def3534 --- /dev/null +++ b/services/Providers/Hm_EventServiceProvider.php @@ -0,0 +1,38 @@ + [ + Hm_NewMaiListener::class, + ], + ]; + + public function register(): void + { + foreach ($this->listen as $event => $listeners) { + foreach ($listeners as $listener) { + Hm_EventManager::listen($event, $listener); + } + } + } + + /** + * Set the container instance for events and listeners. + * + * @param ContainerInterface $container + * @return void + */ + public function setContainer(ContainerInterface $container): void + { + // var_dump($container); + // Hm_EventManager::setContainer($container); + } +} diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php new file mode 100644 index 0000000000..a0abffdd31 --- /dev/null +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -0,0 +1,50 @@ +register('queue.driver.redis', Hm_RedisQueue::class) + ->addArgument(new Reference('redis')) + ->addArgument('default'); + $containerBuilder->getDefinition('queue.manager') + ->addMethodCall('addDriver', ['redis', new Reference('queue.driver.redis')]); + break; + case 'sqs': + $containerBuilder->register('queue.driver.database', Hm_AmazonSQSQueue::class) + ->addArgument($containerBuilder->get('amazon.sqs')); + $containerBuilder->getDefinition('queue.manager') + ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.redis')]); + break; + default: + $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) + ->addArgument($containerBuilder->get('db')); + $containerBuilder->getDefinition('queue.manager') + ->addMethodCall('addDriver', ['database', new Reference('queue.driver.database')]); + break; + } + $containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) + ->addArgument(new Reference('queue.manager')) + ->setShared(true); + + $containerBuilder->register('queue.worker', Hm_QueueWorker::class) + ->addArgument(new Reference('queue.driver.' . $queueConnection)) + ->setShared(true); + } +} diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/Hm_SchedulerServiceProvider.php new file mode 100644 index 0000000000..212c5f374a --- /dev/null +++ b/services/Providers/Hm_SchedulerServiceProvider.php @@ -0,0 +1,34 @@ +command('check:mail')->everyMinute()->withoutOverlapping(10); + + return $scheduler; + } +} diff --git a/services/Traits/Hm_EventDispatchable.php b/services/Traits/Hm_EventDispatchable.php new file mode 100644 index 0000000000..280a2431ba --- /dev/null +++ b/services/Traits/Hm_EventDispatchable.php @@ -0,0 +1,48 @@ +timezone)); $endTime = new \DateTime($endTime, new \DateTimeZone($this->timezone)); + // Adjust for overnight intervals if ($endTime < $startTime) { if ($startTime > $now) { $startTime->modify('-1 day'); @@ -88,7 +88,7 @@ private function inTimeInterval($startTime, $endTime) */ public function everyMinute() { - return $this->spliceIntoPosition(1, '*'); + return $this->cron('* * * * *'); } /** @@ -98,7 +98,7 @@ public function everyMinute() */ public function everyTwoMinutes() { - return $this->spliceIntoPosition(1, '*/2'); + return $this->cron('*/2 * * * *'); } /** @@ -108,7 +108,7 @@ public function everyTwoMinutes() */ public function everyThreeMinutes() { - return $this->spliceIntoPosition(1, '*/3'); + return $this->cron('*/3 * * * *'); } /** @@ -118,7 +118,7 @@ public function everyThreeMinutes() */ public function everyFourMinutes() { - return $this->spliceIntoPosition(1, '*/4'); + return $this->cron('*/4 * * * *'); } /** @@ -128,7 +128,7 @@ public function everyFourMinutes() */ public function everyFiveMinutes() { - return $this->spliceIntoPosition(1, '*/5'); + return $this->cron('*/5 * * * *'); } /** @@ -138,7 +138,7 @@ public function everyFiveMinutes() */ public function everyTenMinutes() { - return $this->spliceIntoPosition(1, '*/10'); + return $this->cron('*/10 * * * *'); } /** @@ -148,7 +148,7 @@ public function everyTenMinutes() */ public function everyFifteenMinutes() { - return $this->spliceIntoPosition(1, '*/15'); + return $this->cron('*/15 * * * *'); } /** @@ -158,7 +158,7 @@ public function everyFifteenMinutes() */ public function everyThirtyMinutes() { - return $this->spliceIntoPosition(1, '0,30'); + return $this->cron('0,30 * * * *'); } /** @@ -168,7 +168,7 @@ public function everyThirtyMinutes() */ public function hourly() { - return $this->spliceIntoPosition(1, 0); + return $this->cron('0 * * * *'); } /** @@ -180,7 +180,7 @@ public function hourly() public function hourlyAt($offset) { $offset = is_array($offset) ? implode(',', $offset) : $offset; - return $this->spliceIntoPosition(1, $offset); + return $this->cron("$offset * * * *"); } /** @@ -190,7 +190,7 @@ public function hourlyAt($offset) */ public function daily() { - return $this->spliceIntoPosition(1, 0)->spliceIntoPosition(2, 0); + return $this->cron('0 0 * * *'); // Midnight } /** @@ -202,8 +202,7 @@ public function daily() public function dailyAt($time) { $segments = explode(':', $time); - return $this->spliceIntoPosition(2, (int)$segments[0]) - ->spliceIntoPosition(1, count($segments) === 2 ? (int)$segments[1] : '0'); + return $this->cron(count($segments) === 2 ? "{$segments[1]} {$segments[0]} * * *" : "0 {$segments[0]} * * *"); } /** diff --git a/services/readme.rd b/services/readme.rd index f9e956fc38..45373442da 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -13,4 +13,32 @@ $dispatcher->dispatch(new ProcessNewEmail('example@example.com')); // Initialize worker to process jobs from the queue $worker = new QueueWorker($queueManager->getDriver('redis')); $worker->work(); +``` + +``` +register(function () { + echo "Running database cleanup\n"; +}, 'cleanup', 'Daily database cleanup task', ['database', 'daily'], 'America/New_York')->dailyAt('03:00'); + +// Register command tasks with options +$scheduler->command('check:mail')->everyMinute() + ->withoutOverlapping(10); + +$scheduler->command('backup:database')->dailyAt('02:00'); +``` + +``` +// Dispatch the event +(new NewEmailProcessedEvent)->dispatch('user@example.com'); ``` \ No newline at end of file diff --git a/tests/phpunit/services/mocks.php b/tests/phpunit/services/mocks.php index 0fe9448c3a..9ed437332f 100644 --- a/tests/phpunit/services/mocks.php +++ b/tests/phpunit/services/mocks.php @@ -1,6 +1,6 @@ Date: Tue, 29 Oct 2024 23:43:23 +0300 Subject: [PATCH 05/21] Adding Notifications --- composer.json | 1 + composer.lock | 130 +++++++++++++++++- .../Channels/Hm_NotificationChannel.php | 8 ++ .../Channels/Hm_SlackChannel.php | 25 ++++ .../Channels/Hm_TelegramChannel.php | 24 ++++ .../Channels/Hm_TwilioChannel.php | 24 ++++ .../Core/Notifications/Hm_Notification.php | 46 +++++++ .../Notifications/Hm_NewMailNotification.php | 26 ++++ services/readme.rd | 17 +++ 9 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 services/Core/Notifications/Channels/Hm_NotificationChannel.php create mode 100644 services/Core/Notifications/Channels/Hm_SlackChannel.php create mode 100644 services/Core/Notifications/Channels/Hm_TelegramChannel.php create mode 100644 services/Core/Notifications/Channels/Hm_TwilioChannel.php create mode 100644 services/Core/Notifications/Hm_Notification.php create mode 100644 services/Notifications/Hm_NewMailNotification.php diff --git a/composer.json b/composer.json index a178a85d3b..0d53f40664 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,7 @@ "symfony/console": "^6.4", "symfony/dependency-injection": "*", "symfony/dotenv": "^4.3 || 5.4", + "symfony/notifier": "*", "symfony/yaml": "~6.4.3", "thomaspark/bootswatch": "^5.3", "twbs/bootstrap": "^5.3", diff --git a/composer.lock b/composer.lock index 14593bc26b..0c9188c872 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d0b61ae7feb455c0bda982d900d7357a", + "content-hash": "f563ecfadd9972063701823cf07c172b", "packages": [ { "name": "aws/aws-crt-php", @@ -1623,6 +1623,56 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -1980,6 +2030,84 @@ ], "time": "2021-11-23T10:19:22+00:00" }, + { + "name": "symfony/notifier", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/notifier.git", + "reference": "c46321b53391088861bf627cd9e24873d216cf00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/notifier/zipball/c46321b53391088861bf627cd9e24873d216cf00", + "reference": "c46321b53391088861bf627cd9e24873d216cf00", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<5.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4" + }, + "require-dev": { + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Sends notifications via one or more channels (email, SMS, ...)", + "homepage": "https://symfony.com", + "keywords": [ + "notification", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/notifier/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.31.0", diff --git a/services/Core/Notifications/Channels/Hm_NotificationChannel.php b/services/Core/Notifications/Channels/Hm_NotificationChannel.php new file mode 100644 index 0000000000..8abe0bd1db --- /dev/null +++ b/services/Core/Notifications/Channels/Hm_NotificationChannel.php @@ -0,0 +1,8 @@ +transport = $transport; + } + + public function send($notifiable, string $message): void + { + // Assuming $notifiable has a method to get the Slack channel/user ID + $notification = (new Notification('Slack Notification')) + ->content($message); + + $this->transport->send($notification); + } +} diff --git a/services/Core/Notifications/Channels/Hm_TelegramChannel.php b/services/Core/Notifications/Channels/Hm_TelegramChannel.php new file mode 100644 index 0000000000..ea8a8af63e --- /dev/null +++ b/services/Core/Notifications/Channels/Hm_TelegramChannel.php @@ -0,0 +1,24 @@ +transport = $transport; + } + + public function send($notifiable, string $message): void + { + $notification = (new Notification('Telegram Notification')) + ->content($message); + + $this->transport->send($notification); + } +} diff --git a/services/Core/Notifications/Channels/Hm_TwilioChannel.php b/services/Core/Notifications/Channels/Hm_TwilioChannel.php new file mode 100644 index 0000000000..08360f2bf9 --- /dev/null +++ b/services/Core/Notifications/Channels/Hm_TwilioChannel.php @@ -0,0 +1,24 @@ +transport = $transport; + } + + public function send($notifiable, string $message): void + { + $notification = (new Notification('SMS Notification')) + ->content($message); + + $this->transport->send($notification); + } +} diff --git a/services/Core/Notifications/Hm_Notification.php b/services/Core/Notifications/Hm_Notification.php new file mode 100644 index 0000000000..9bc49f5f8a --- /dev/null +++ b/services/Core/Notifications/Hm_Notification.php @@ -0,0 +1,46 @@ +config = $config; // Set configuration in the constructor + } + + public function via(): array + { + return $this->config['channels'] ?? ['slack', 'telegram']; + } + + public function send($notifiable, string $title, string $content): void + { + $channels = $this->via(); + foreach ($channels as $channel) { + $this->sendThroughChannel($channel, $notifiable, $content); + } + } + + private function sendThroughChannel(string $channel, $notifiable, string $message): void + { + switch ($channel) { + case 'slack': + (new Hm_SlackChannel(new SlackTransport()))->send($notifiable, $message); + break; + case 'telegram': + (new Hm_TelegramChannel(new TelegramTransport()))->send($notifiable, $message); + break; + case 'twilio': + (new Hm_TwilioChannel(new TwilioTransport()))->send($notifiable, $message); + break; + // Add more channels as necessary + } + } +} + diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php new file mode 100644 index 0000000000..35805854f9 --- /dev/null +++ b/services/Notifications/Hm_NewMailNotification.php @@ -0,0 +1,26 @@ +send($notifiable, $title, $message); + } +} diff --git a/services/readme.rd b/services/readme.rd index 45373442da..76ac25e8f0 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -41,4 +41,21 @@ $scheduler->command('backup:database')->dailyAt('02:00'); ``` // Dispatch the event (new NewEmailProcessedEvent)->dispatch('user@example.com'); +``` +``` +// Notification Example usage + +use Services\Notifications\UserNotification; + +// Configure the notification channels +$config = [ + 'channels' => ['slack', 'telegram'], // User-defined channels +]; + +// Create an instance of UserNotification with the specified channels +$notification = new UserNotification($config); + +// Send a message through the specified channels +$message = "Hello! You have a new alert."; +$notification->sendNotification($message); ``` \ No newline at end of file From d8dd0fd8f40267d6ff381d6261ef3b9e6b079aa0 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Thu, 31 Oct 2024 16:00:21 +0300 Subject: [PATCH 06/21] add Singleton Class for containerBuilder --- services/Commands/Hm_ListCommandsCommand.php | 4 +- services/Core/Commands/Hm_BaseCommand.php | 2 +- services/Core/Hm_Container.php | 68 +++++++++++++++++++ services/Hm_bootstrap.php | 50 +------------- .../Providers/Hm_CommandServiceProvider.php | 2 +- 5 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 services/Core/Hm_Container.php diff --git a/services/Commands/Hm_ListCommandsCommand.php b/services/Commands/Hm_ListCommandsCommand.php index a51ff6a825..ae98017480 100644 --- a/services/Commands/Hm_ListCommandsCommand.php +++ b/services/Commands/Hm_ListCommandsCommand.php @@ -34,8 +34,8 @@ protected function configure() /** * Execute the command to list all registered commands. * - * @param InputInterface $input The input interface for the command. - * @param OutputInterface $output The output interface for the command. + * @param InputInterface $input The input interface Hm_ListCommandsCommand the command. + * @param OutputInterface $output The output interface Hm_ListCommandsCommand the command. * @return int Command exit code. */ protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/services/Core/Commands/Hm_BaseCommand.php b/services/Core/Commands/Hm_BaseCommand.php index 6f7b86d068..ae54f7e5d4 100644 --- a/services/Core/Commands/Hm_BaseCommand.php +++ b/services/Core/Commands/Hm_BaseCommand.php @@ -65,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * Get a service from the container. * - * @param string $service The service class or interface. + * @param string $service The service class Hm_BaseCommand interface. * @return mixed The requested service. */ protected function getService(string $service) diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php new file mode 100644 index 0000000000..ed2d523cea --- /dev/null +++ b/services/Core/Hm_Container.php @@ -0,0 +1,68 @@ +register('db', Hm_DB::class) + ->setShared(true); + + // Register Hm_Redis + self::$container->register('redis', Hm_Redis::class) + ->setShared(true); + + // Register Hm_AmazonSQS + self::$container->register('amazon.sqs', Hm_AmazonSQS::class) + ->setShared(true); + + // Register Hm_QueueManager + self::$container->register('queue.manager', Hm_QueueManager::class) + ->setShared(true); + + // Register Hm_JobDispatcher + self::$container->register('job.dispatcher', Hm_JobDispatcher::class) + ->setShared(true); + + // Register Hm_Site_Config_File + self::$container->register('Hm_Site_Config_File', Hm_Site_Config_File::class) + ->setShared(true); + + // Register Hm_CommandServiceProvider + self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) + ->setShared(true); + + // Register Hm_QueueServiceProvider + self::$container->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) + // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) + // ->addArgument(null) + ->setShared(true); + + self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) + ->setShared(true); + self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) + ->setShared(true); + } + return self::$container; + } +} diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index fe10c8151e..d6bf3074ab 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,10 +1,7 @@ get('disable_ini_settings')) { require APP_PATH.'lib/ini_set.php'; } -// Initialize the scheduler -// $scheduler = new Scheduler($config); +$containerBuilder = Hm_Container::getContainer(); -$containerBuilder = new ContainerBuilder(); - -// Register Hm_DB -$containerBuilder->register('db', Hm_DB::class) - ->setShared(true); - -// Register Hm_Redis -$containerBuilder->register('redis', Hm_Redis::class) - ->setShared(true); - -// Register Hm_AmazonSQS -$containerBuilder->register('amazon.sqs', Hm_AmazonSQS::class) - ->setShared(true); - -// Register Hm_QueueManager -$containerBuilder->register('queue.manager', Hm_QueueManager::class) - ->setShared(true); - -// Register Hm_JobDispatcher -$containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) - ->setShared(true); - -// Register Hm_Site_Config_File -$containerBuilder->register('Hm_Site_Config_File', Hm_Site_Config_File::class) - ->setShared(true); - -// Register Hm_CommandServiceProvider -$containerBuilder->register('command.serviceProvider', Hm_CommandServiceProvider::class) - ->setShared(true); - -// Register Hm_QueueServiceProvider -$containerBuilder->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) - // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) - // ->addArgument(null) - ->setShared(true); - -$containerBuilder->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) - ->setShared(true); -$containerBuilder->register('event.ServiceProvider', Hm_EventServiceProvider::class) - ->setShared(true); - -// return $containerBuilder; return [$containerBuilder,$config]; diff --git a/services/Providers/Hm_CommandServiceProvider.php b/services/Providers/Hm_CommandServiceProvider.php index 93aa25a3b1..181b7c1600 100644 --- a/services/Providers/Hm_CommandServiceProvider.php +++ b/services/Providers/Hm_CommandServiceProvider.php @@ -45,7 +45,7 @@ protected function getCommandFiles(): array /** * Register a command with the application. * - * This method checks if the given class name is a valid command class. + * This method checks if the given class Hm_CommandServiceProvider is a valid command class. * If it is valid, the command is instantiated and added to the application. * If not, the registration is simply skipped without any exceptions. * From 75585dc0c2b3596fe170dc22b6bb453a0357a75e Mon Sep 17 00:00:00 2001 From: Yannick nsenga romeo Date: Thu, 31 Oct 2024 15:30:46 +0200 Subject: [PATCH 07/21] Refactor queue worker command & adding error handling --- composer.json | 5 +- composer.lock | 226 +++++++++++++++--- console | 4 +- services/Commands/Hm_CheckMailCommand.php | 1 - services/Commands/Hm_ListCommandsCommand.php | 59 ----- services/Core/Commands/Hm_BaseCommand.php | 19 +- .../Commands/Hm_QueueWorkCommand.php | 15 +- .../Core/Queue/Drivers/Hm_DatabaseQueue.php | 2 +- services/Hm_bootstrap.php | 4 +- .../Providers/Hm_CommandServiceProvider.php | 34 ++- .../Providers/Hm_QueueServiceProvider.php | 6 +- 11 files changed, 238 insertions(+), 137 deletions(-) delete mode 100644 services/Commands/Hm_ListCommandsCommand.php rename services/{ => Core}/Commands/Hm_QueueWorkCommand.php (85%) diff --git a/composer.json b/composer.json index 0d53f40664..16ee41fe05 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,7 @@ "symfony/console": "^6.4", "symfony/dependency-injection": "*", "symfony/dotenv": "^4.3 || 5.4", + "symfony/error-handler": "^6.4", "symfony/notifier": "*", "symfony/yaml": "~6.4.3", "thomaspark/bootswatch": "^5.3", @@ -68,7 +69,8 @@ "zbateson/mail-mime-parser": "^2.4" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.5", + "symfony/var-dumper": "^6.4" }, "autoload-dev": { "psr-4": { @@ -88,6 +90,7 @@ "php": "8.1" }, "optimize-autoloader": true, + "sort-packages": true, "allow-plugins": { "endroid/installer": false } diff --git a/composer.lock b/composer.lock index 0c9188c872..76a9d8f248 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f563ecfadd9972063701823cf07c172b", + "content-hash": "4be9f7ab25b2c435f315264408b9b27c", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.324.11", + "version": "3.325.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "a667ca29db288af083c3d8cff2e7f32d89fd4cf0" + "reference": "ea36e53745cff21519c2dadd808e2482f0bfadf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a667ca29db288af083c3d8cff2e7f32d89fd4cf0", - "reference": "a667ca29db288af083c3d8cff2e7f32d89fd4cf0", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ea36e53745cff21519c2dadd808e2482f0bfadf5", + "reference": "ea36e53745cff21519c2dadd808e2482f0bfadf5", "shasum": "" }, "require": { @@ -154,9 +154,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.324.11" + "source": "https://github.com/aws/aws-sdk-php/tree/3.325.0" }, - "time": "2024-10-25T18:07:16+00:00" + "time": "2024-10-30T18:11:21+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1719,16 +1719,16 @@ }, { "name": "symfony/console", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "url": "https://api.github.com/repos/symfony/console/zipball/f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79", "shasum": "" }, "require": { @@ -1793,7 +1793,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.12" + "source": "https://github.com/symfony/console/tree/v6.4.13" }, "funding": [ { @@ -1809,7 +1809,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-10-09T08:40:40+00:00" }, { "name": "symfony/dependency-injection", @@ -2030,6 +2030,81 @@ ], "time": "2021-11-23T10:19:22+00:00" }, + { + "name": "symfony/error-handler", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", + "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/notifier", "version": "v6.4.13", @@ -2671,16 +2746,16 @@ }, { "name": "symfony/string", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", "shasum": "" }, "require": { @@ -2737,7 +2812,92 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.12" + "source": "https://github.com/symfony/string/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "2acb151474ed8cb43624e3f41a0bf7c4c8ce4f41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2acb151474ed8cb43624e3f41a0bf7c4c8ce4f41", + "reference": "2acb151474ed8cb43624e3f41a0bf7c4c8ce4f41", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.4.13" }, "funding": [ { @@ -2753,7 +2913,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/var-exporter", @@ -2834,16 +2994,16 @@ }, { "name": "symfony/yaml", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", "shasum": "" }, "require": { @@ -2886,7 +3046,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.12" + "source": "https://github.com/symfony/yaml/tree/v6.4.13" }, "funding": [ { @@ -2902,7 +3062,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:47:12+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "thomaspark/bootswatch", @@ -3859,16 +4019,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.37", + "version": "10.5.38", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400" + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c7cffa0efa2b70c22366523e6d804c9419eb2400", - "reference": "c7cffa0efa2b70c22366523e6d804c9419eb2400", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", "shasum": "" }, "require": { @@ -3940,7 +4100,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.37" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" }, "funding": [ { @@ -3956,7 +4116,7 @@ "type": "tidelift" } ], - "time": "2024-10-19T13:03:41+00:00" + "time": "2024-10-28T13:06:21+00:00" }, { "name": "sebastian/cli-parser", @@ -4927,7 +5087,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4941,7 +5101,7 @@ "ext-session": "*", "php": ">=8.1" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1" }, diff --git a/console b/console index 13dcc0842b..7b57a69953 100644 --- a/console +++ b/console @@ -11,10 +11,10 @@ list($containerBuilder, $config) = require __DIR__ . '/services/Hm_bootstrap.php $application = new Application('Cypht Console', '1.0'); $commandServiceProvider = $containerBuilder->get('command.serviceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); -$commandServiceProvider->register($application, $containerBuilder); +$commandServiceProvider->register($application); $queueServiceProvider = $containerBuilder->get('queue.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); -$queueServiceProvider->register($containerBuilder); +$queueServiceProvider->register(); $queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); $queueServiceProvider->register($config, new Hm_Session_Setup($config)); diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index 09bc062020..d414cf7034 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -17,7 +17,6 @@ class Hm_CheckMailCommand extends Hm_BaseCommand */ protected static $defaultName = 'check:mail'; - protected function configure() { $this->setDescription('Check for new mail'); diff --git a/services/Commands/Hm_ListCommandsCommand.php b/services/Commands/Hm_ListCommandsCommand.php deleted file mode 100644 index ae98017480..0000000000 --- a/services/Commands/Hm_ListCommandsCommand.php +++ /dev/null @@ -1,59 +0,0 @@ -setDescription('Lists all registered commands in the application.'); - } - - /** - * Execute the command to list all registered commands. - * - * @param InputInterface $input The input interface Hm_ListCommandsCommand the command. - * @param OutputInterface $output The output interface Hm_ListCommandsCommand the command. - * @return int Command exit code. - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - $this->success('Registered Commands:'); - - $this->initialize($input, $output); - - $commands = $this->getApplication()->all(); - - $commandList = []; - - foreach ($commands as $command) { - $commandList[] = sprintf('%-30s - %s', $command->getName(), $command->getDescription()); - } - - $this->text(implode(PHP_EOL, $commandList)); - - return Command::SUCCESS; - } -} diff --git a/services/Core/Commands/Hm_BaseCommand.php b/services/Core/Commands/Hm_BaseCommand.php index ae54f7e5d4..5c2167100e 100644 --- a/services/Core/Commands/Hm_BaseCommand.php +++ b/services/Core/Commands/Hm_BaseCommand.php @@ -13,10 +13,6 @@ */ abstract class Hm_BaseCommand extends Command { - /** - * @var ContainerInterface - */ - protected $container; /** * @var SymfonyStyle @@ -26,14 +22,11 @@ abstract class Hm_BaseCommand extends Command /** * BaseCommand constructor. * - * @param ContainerInterface $container Dependency Injection Container. * @param string|null $name The name of the command. */ - public function __construct(ContainerInterface $container, ?string $name = null) + public function __construct(?string $name = null) { parent::__construct($name); - - $this->container = $container; } /** @@ -62,16 +55,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - /** - * Get a service from the container. - * - * @param string $service The service class Hm_BaseCommand interface. - * @return mixed The requested service. - */ - protected function getService(string $service) - { - return $this->container->get($service); - } /** * Output a success message. diff --git a/services/Commands/Hm_QueueWorkCommand.php b/services/Core/Commands/Hm_QueueWorkCommand.php similarity index 85% rename from services/Commands/Hm_QueueWorkCommand.php rename to services/Core/Commands/Hm_QueueWorkCommand.php index 1c5817601b..395f8b19dc 100644 --- a/services/Commands/Hm_QueueWorkCommand.php +++ b/services/Core/Commands/Hm_QueueWorkCommand.php @@ -1,7 +1,9 @@ writeln("Processing jobs from the [$queue] on connection [$connection]..."); if ($input->getOption('once')) { - $this->container->get('queue.worker')->work(); + Hm_Container::getContainer()->get('queue.worker')->work(); } else { while (true) { - $this->container->get('queue.worker')->work(); + Hm_Container::getContainer()->get('queue.worker')->work(); sleep($input->getOption('sleep')); } } diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php index 1ee1fc623d..c73d271284 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php @@ -3,7 +3,7 @@ namespace Services\Core\Queue\Drivers; use Hm_DB; -use Services\Jobs\Hm_BaseJob; +use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; /** diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index d6bf3074ab..5fb4e33016 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,7 +1,7 @@ getCommandFiles(); foreach ($commandFiles as $file) { - $className = $commandNamespace . '\\' . basename($file, '.php'); + $namespace = $this->getNamespaceFromFilePath($file); + $className = $namespace . '\\' . basename($file, '.php'); + try { - $this->registerCommand($application, $className, $container); + $this->registerCommand($application, $className); } catch (InvalidArgumentException $e) { Hm_Debug::add(sprintf('Command registration escaped for %s: No need to register the command class.', $className)); } @@ -39,7 +39,24 @@ public function register(Application $application, ContainerInterface $container */ protected function getCommandFiles(): array { - return glob(__DIR__ . '/../Commands/*.php'); + return array_merge( + glob(__DIR__ . '/../Core/Commands/*.php'), + glob(__DIR__ . '/../Commands/*.php') + ); + } + + /** + * Get the namespace from the file path. + * + * @param string $filePath The file path. + * @return string The namespace. + */ + protected function getNamespaceFromFilePath(string $filePath): string + { + if (strpos($filePath, '/Core/Commands/') !== false) { + return 'Services\Core\Commands'; + } + return 'Services\Commands'; } /** @@ -51,16 +68,15 @@ protected function getCommandFiles(): array * * @param Application $application The Symfony console application. * @param string $className The fully qualified name of the command class. - * @param ContainerInterface $container The dependency injection container. * @return void */ - protected function registerCommand(Application $application, string $className, ContainerInterface $container): void + protected function registerCommand(Application $application, string $className): void { if (!class_exists($className) || !is_subclass_of($className, Hm_BaseCommand::class)) { throw new InvalidArgumentException(sprintf('Class "%s" is not a valid command class.', $className)); } - $command = new $className($container); + $command = new $className; $application->add($command); } } diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index a0abffdd31..4f4863eb87 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -1,6 +1,7 @@ register('queue.driver.redis', Hm_RedisQueue::class) From 8dd16b50d7b3e10dbdc302ce8d6cc3130cd8f5c5 Mon Sep 17 00:00:00 2001 From: Yannick nsenga romeo Date: Thu, 31 Oct 2024 17:15:10 +0200 Subject: [PATCH 08/21] Queue manager: dispatching jobs to queue --- services/Commands/Hm_CheckMailCommand.php | 2 +- ...ventManager.php => Hm_EventDispatcher.php} | 4 +- services/Core/Hm_Container.php | 71 +++++++++---------- .../Core/Queue/Drivers/Hm_DatabaseQueue.php | 23 +++--- services/Core/Queue/Hm_QueueWorker.php | 1 + services/Events/Hm_NewEmailProcessedEvent.php | 12 +--- services/Hm_bootstrap.php | 12 +++- services/Jobs/Hm_ProcessNewEmail.php | 9 +-- .../Providers/Hm_EventServiceProvider.php | 4 +- .../Providers/Hm_QueueServiceProvider.php | 23 +++--- ...ntDispatchable.php => Hm_Dispatchable.php} | 15 ++-- services/Traits/Hm_InteractsWithQueue.php | 4 +- 12 files changed, 91 insertions(+), 89 deletions(-) rename services/Core/Events/{Hm_EventManager.php => Hm_EventDispatcher.php} (93%) rename services/Traits/{Hm_EventDispatchable.php => Hm_Dispatchable.php} (72%) diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index d414cf7034..cd63faedae 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -36,7 +36,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Example: Call the mail checking service from the container // $imap = $this->getService('Hm_Imap'); // $newMessages = $imap->search('UNSEEN'); - (new Hm_ProcessNewEmail('muhngesteven@gmail.com'))->handle(); + Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); if (!empty($newMessages)) { $this->success('You have new messages!'); diff --git a/services/Core/Events/Hm_EventManager.php b/services/Core/Events/Hm_EventDispatcher.php similarity index 93% rename from services/Core/Events/Hm_EventManager.php rename to services/Core/Events/Hm_EventDispatcher.php index 48e262de4f..a0a40540e7 100644 --- a/services/Core/Events/Hm_EventManager.php +++ b/services/Core/Events/Hm_EventDispatcher.php @@ -2,9 +2,7 @@ namespace Services\Core\Events; -use Services\Events; - -class Hm_EventManager +class Hm_EventDispatcher { protected static array $listeners = []; diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index ed2d523cea..d76801a8ab 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -5,13 +5,10 @@ use Hm_DB; use Hm_Redis; use Hm_AmazonSQS; -use Hm_Site_Config_File; use Services\Core\Queue\Hm_QueueManager; -use Services\Core\Queue\Hm_JobDispatcher; use Symfony\Component\DependencyInjection\ContainerBuilder; use Services\Providers\{ Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider }; - class Hm_Container { private static $container = null; @@ -20,49 +17,51 @@ class Hm_Container private function __construct() {} private function __clone() {} - public static function getContainer(): ContainerBuilder + public static function setContainer(ContainerBuilder $containerBuilder): ContainerBuilder { if (self::$container === null) { - self::$container = new ContainerBuilder(); - // Register Hm_DB - self::$container->register('db', Hm_DB::class) - ->setShared(true); + self::$container = $containerBuilder; + } - // Register Hm_Redis - self::$container->register('redis', Hm_Redis::class) - ->setShared(true); + return self::$container; + } - // Register Hm_AmazonSQS - self::$container->register('amazon.sqs', Hm_AmazonSQS::class) - ->setShared(true); + public static function bind(): ContainerBuilder + { + // Register Hm_DB + self::$container->set('db.connection', Hm_DB::connect(self::$container->get('config'))); - // Register Hm_QueueManager - self::$container->register('queue.manager', Hm_QueueManager::class) - ->setShared(true); + self::$container->register('db', Hm_DB::class) + ->setShared(true); - // Register Hm_JobDispatcher - self::$container->register('job.dispatcher', Hm_JobDispatcher::class) - ->setShared(true); + // Register Hm_Redis + self::$container->register('redis', Hm_Redis::class) + ->setShared(true); - // Register Hm_Site_Config_File - self::$container->register('Hm_Site_Config_File', Hm_Site_Config_File::class) - ->setShared(true); + // Register Hm_AmazonSQS + self::$container->register('amazon.sqs', Hm_AmazonSQS::class) + ->setShared(true); - // Register Hm_CommandServiceProvider - self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) - ->setShared(true); + // Register Hm_CommandServiceProvider + self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) + ->setShared(true); - // Register Hm_QueueServiceProvider - self::$container->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) - // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) - // ->addArgument(null) - ->setShared(true); + // Register Hm_QueueServiceProvider + self::$container->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) + // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) + // ->addArgument(null) + ->setShared(true); - self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) - ->setShared(true); - self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) - ->setShared(true); - } + self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) + ->setShared(true); + self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) + ->setShared(true); + + return self::$container; + } + + public static function getContainer(): ContainerBuilder + { return self::$container; } } diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php index c73d271284..42532339dd 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php @@ -2,6 +2,7 @@ namespace Services\Core\Queue\Drivers; +use PDO; use Hm_DB; use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; @@ -12,17 +13,13 @@ */ class Hm_DatabaseQueue implements Hm_ShouldQueue { - /** - * @var Hm_DB - */ - protected Hm_DB $db; /** * Hm_DatabaseQueue constructor. * @param Hm_DB $db + * @param PDO $dbConnection */ - public function __construct(Hm_DB $db) { - $this->db = $db; + public function __construct(private Hm_DB $db, protected PDO $dbConnection) { } /** @@ -32,9 +29,8 @@ public function __construct(Hm_DB $db) { * @return void */ public function push(Hm_BaseJob $job): void { - $dbh = $this->db->connect($this->db->getConfig()); - $sql = "INSERT INTO jobs (payload) VALUES (:payload)"; - $this->db->execute($dbh, $sql, ['payload' => serialize($job)], 'insert'); + $sql = "INSERT INTO hm_jobs (payload) VALUES (:payload)"; + $this->db->execute($this->dbConnection, $sql, ['payload' => serialize($job)], 'insert'); } /** @@ -43,13 +39,12 @@ public function push(Hm_BaseJob $job): void { * @return Hm_BaseJob|null */ public function pop(): ?Hm_BaseJob { - $dbh = $this->db->connect($this->db->getConfig()); - $sql = "SELECT * FROM jobs ORDER BY id ASC LIMIT 1"; - $jobRecord = $this->db->execute($dbh, $sql, [], 'select'); + $sql = "SELECT * FROM hm_jobs ORDER BY id ASC LIMIT 1"; + $jobRecord = $this->db->execute($this->dbConnection, $sql, [], 'select'); if ($jobRecord) { - $deleteSql = "DELETE FROM jobs WHERE id = :id"; - $this->db->execute($dbh, $deleteSql, ['id' => $jobRecord['id']], 'modify'); + $deleteSql = "DELETE FROM hm_jobs WHERE id = :id"; + $this->db->execute($this->dbConnection, $deleteSql, ['id' => $jobRecord['id']], 'modify'); return unserialize($jobRecord['payload']); } diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/Hm_QueueWorker.php index 9c748b5c5f..9b3786095c 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/Hm_QueueWorker.php @@ -27,6 +27,7 @@ public function __construct(Hm_ShouldQueue $queue) * @return void */ public function work(): void { + dd($this->queue); while ($job = $this->queue->pop()) { try { diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php index 8c74ea7b3e..881d20ac4b 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -3,17 +3,9 @@ namespace Services\Events; use Services\Core\Events\Hm_BaseEvent; -use Services\Core\Events\Hm_EventManager; +use Services\Traits\Hm_Dispatchable; class Hm_NewEmailProcessedEvent extends Hm_BaseEvent { - public function __construct(public string $email) - { - parent::__construct($email); - } - - public function dispatch(): void - { - Hm_EventManager::dispatch($this); - } + use Hm_Dispatchable; } diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 5fb4e33016..24dbe07500 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -2,6 +2,7 @@ use Services\Core\Hm_Container; use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\DependencyInjection\ContainerBuilder; define('APP_PATH', dirname(__DIR__).'/'); define('VENDOR_PATH', APP_PATH.'vendor/'); @@ -40,8 +41,13 @@ require APP_PATH.'lib/ini_set.php'; } -$containerBuilder = Hm_Container::getContainer(); -// Activer le gestionnaire d'erreurs ErrorHandler::register(); -return [$containerBuilder,$config]; +$containerBuilder = Hm_Container::setContainer(new ContainerBuilder()); + +// Register Hm_Site_Config_File +$containerBuilder->set('config', $config); + +Hm_Container::bind(); + +return [$containerBuilder, $config]; diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php index b746cf712c..60751a3937 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -3,14 +3,14 @@ namespace Services\Jobs; use Services\Core\Jobs\Hm_BaseJob; -use Services\Traits\Hm_EventDispatchable; +use Services\Traits\Hm_Dispatchable; use Services\Traits\Hm_InteractsWithQueue; use Services\Contracts\Queue\Hm_ShouldQueue; use Services\Events\Hm_NewEmailProcessedEvent; -class Hm_ProcessNewEmail extends Hm_BaseJob //implements Hm_ShouldQueue +class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue { - use Hm_EventDispatchable;//Hm_InteractsWithQueue; + use Hm_Dispatchable, Hm_InteractsWithQueue; protected string $driver = 'database'; @@ -23,7 +23,8 @@ public function handle(): void { //fetch new email //then process it - (new Hm_NewEmailProcessedEvent($this->email))->dispatch(); + + dump('job processing'); } diff --git a/services/Providers/Hm_EventServiceProvider.php b/services/Providers/Hm_EventServiceProvider.php index 6a3def3534..71687a8145 100644 --- a/services/Providers/Hm_EventServiceProvider.php +++ b/services/Providers/Hm_EventServiceProvider.php @@ -2,7 +2,7 @@ namespace Services\Providers; -use Services\Core\Events\Hm_EventManager; +use Services\Core\Events\Hm_EventDispatcher; use Services\Listeners\Hm_NewMaiListener; use Services\Events\Hm_NewEmailProcessedEvent; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,7 +19,7 @@ public function register(): void { foreach ($this->listen as $event => $listeners) { foreach ($listeners as $listener) { - Hm_EventManager::listen($event, $listener); + Hm_EventDispatcher::listen($event, $listener); } } } diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index 4f4863eb87..8191981037 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -20,6 +20,17 @@ public function register() $queueConnection = getenv('QUEUE_CONNECTION') ?: 'database'; $containerBuilder = Hm_Container::getContainer(); + $containerBuilder->register('queue.manager', Hm_QueueManager::class) + ->setShared(true); + + $containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) + ->addArgument(new Reference('queue.manager')) + ->setShared(true); + + $containerBuilder->register('queue.worker', Hm_QueueWorker::class) + ->addArgument(new Reference('queue.driver.' . $queueConnection)) + ->setShared(true); + switch ($queueConnection) { case 'redis': $containerBuilder->register('queue.driver.redis', Hm_RedisQueue::class) @@ -29,24 +40,18 @@ public function register() ->addMethodCall('addDriver', ['redis', new Reference('queue.driver.redis')]); break; case 'sqs': - $containerBuilder->register('queue.driver.database', Hm_AmazonSQSQueue::class) + $containerBuilder->register('queue.driver.sqs', Hm_AmazonSQSQueue::class) ->addArgument($containerBuilder->get('amazon.sqs')); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.redis')]); break; default: $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) - ->addArgument($containerBuilder->get('db')); + ->addArgument(new Reference('db')) + ->addArgument(new Reference('db.connection')); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['database', new Reference('queue.driver.database')]); break; } - $containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) - ->addArgument(new Reference('queue.manager')) - ->setShared(true); - - $containerBuilder->register('queue.worker', Hm_QueueWorker::class) - ->addArgument(new Reference('queue.driver.' . $queueConnection)) - ->setShared(true); } } diff --git a/services/Traits/Hm_EventDispatchable.php b/services/Traits/Hm_Dispatchable.php similarity index 72% rename from services/Traits/Hm_EventDispatchable.php rename to services/Traits/Hm_Dispatchable.php index 280a2431ba..edc097fe6e 100644 --- a/services/Traits/Hm_EventDispatchable.php +++ b/services/Traits/Hm_Dispatchable.php @@ -2,9 +2,11 @@ namespace Services\Traits; -use Services\Events\Hm_EventManager; +use Services\Core\Events\Hm_EventDispatcher; +use Services\Core\Jobs\Hm_BaseJob; +use Services\Core\Queue\Hm_JobDispatcher; -trait Hm_EventDispatchable +trait Hm_Dispatchable { /** * Dispatch the event with the given arguments. @@ -13,9 +15,12 @@ trait Hm_EventDispatchable */ public static function dispatch(...$arguments) { - $event = new static(...$arguments); - // Call the event listener or handle it in some way - Hm_EventManager::dispatch($event); + $instance = new static(...$arguments); + + if (is_subclass_of($instance, Hm_BaseJob::class)) { + # code... + Hm_JobDispatcher::dispatch($instance); + } } /** diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/Hm_InteractsWithQueue.php index 71280d9c90..478333ac4a 100644 --- a/services/Traits/Hm_InteractsWithQueue.php +++ b/services/Traits/Hm_InteractsWithQueue.php @@ -3,7 +3,7 @@ namespace Services\Traits; use Services\Contracts\Hm_Job; -use Services\Queue\Hm_QueueManager; +use Services\Core\Queue\Hm_QueueManager; use Services\Contracts\Queue\Hm_ShouldQueue; /** @@ -26,7 +26,7 @@ trait Hm_InteractsWithQueue */ public function push(Hm_Job $job, $data = '', $queue = null) { - $driver = $job->getDriver(); + $driver = $this->getDriver(); // Call the appropriate method from the QueueManager to push the job (new Hm_QueueManager)->getDriver($driver)->push($this, $data, $queue); From 3269e274c447f9514b371a46c17f3d83cc380e2f Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Thu, 31 Oct 2024 21:13:46 +0300 Subject: [PATCH 09/21] Complete Database queue driver and tested in real scenario --- lib/db.php | 1 + services/Commands/Hm_CheckMailCommand.php | 12 -- services/Contracts/Queue/Hm_Queueable.php | 10 ++ services/Core/Events/Hm_BaseEvent.php | 5 +- services/Core/Events/Hm_EventDispatcher.php | 2 +- services/Core/Jobs/Hm_BaseJob.php | 26 +++- .../Core/Queue/Drivers/Hm_DatabaseQueue.php | 102 +++++++++++++- services/Core/Queue/Drivers/Hm_RedisQueue.php | 6 +- services/Core/Queue/Hm_JobDispatcher.php | 26 +--- services/Core/Queue/Hm_QueueWorker.php | 10 +- services/Events/Hm_NewEmailProcessedEvent.php | 19 ++- services/Jobs/Hm_ProcessNewEmail.php | 19 ++- services/Listeners/Hm_NewMaiListener.php | 3 +- .../Providers/Hm_EventServiceProvider.php | 12 -- services/Traits/Hm_Dispatchable.php | 7 +- services/Traits/Hm_InteractsWithQueue.php | 7 +- services/Traits/Hm_Queueable.php | 7 + services/Traits/Hm_Serializes.php | 126 ++++++++++++++++++ services/readme.rd | 20 +++ tests/phpunit/command.php | 8 +- 20 files changed, 347 insertions(+), 81 deletions(-) create mode 100644 services/Contracts/Queue/Hm_Queueable.php create mode 100644 services/Traits/Hm_Queueable.php create mode 100644 services/Traits/Hm_Serializes.php diff --git a/lib/db.php b/lib/db.php index 001c8dc049..154461aa35 100644 --- a/lib/db.php +++ b/lib/db.php @@ -92,6 +92,7 @@ static public function build_dsn() { * @return boolean|integer|array */ static public function execute($dbh, $sql, $args, $type = false, $all = false) { + if (!$dbh) { return false; } diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index cd63faedae..dc201d6a8e 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -32,19 +32,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { $this->info("Checking for new mail..."); - - // Example: Call the mail checking service from the container - // $imap = $this->getService('Hm_Imap'); - // $newMessages = $imap->search('UNSEEN'); Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); - - if (!empty($newMessages)) { - $this->success('You have new messages!'); - // dispatch event - } else { - $this->info('No new messages.'); - } - return Command::SUCCESS; } } diff --git a/services/Contracts/Queue/Hm_Queueable.php b/services/Contracts/Queue/Hm_Queueable.php new file mode 100644 index 0000000000..f17be64dbe --- /dev/null +++ b/services/Contracts/Queue/Hm_Queueable.php @@ -0,0 +1,10 @@ +data = $data; + } public function handle(): void {} public function failed(): void {} @@ -17,4 +22,21 @@ public function getDriver(): string return $this->driver; } + public function getAttempts(): int + { + return $this->attempts; + } + + // Method to increment the attempt count + public function incrementAttempts(): void + { + $this->attempts++; + } + + // Check if the job has exceeded the max attempts + public function hasExceededMaxAttempts(): bool + { + return $this->attempts >= $this->tries; + } + } diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php index 42532339dd..c9ba0249b2 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php @@ -5,22 +5,23 @@ use PDO; use Hm_DB; use Services\Core\Jobs\Hm_BaseJob; +use Services\Contracts\Queue\Hm_Queueable; use Services\Contracts\Queue\Hm_ShouldQueue; /** * Class Hm_DatabaseQueue - * @package App\Queue\Drivers + * @package Services\Core\Queue\Drivers */ -class Hm_DatabaseQueue implements Hm_ShouldQueue +class Hm_DatabaseQueue implements Hm_ShouldQueue, Hm_Queueable { + protected const FAILED_JOBS_TABLE = 'hm_failed_jobs'; /** * Hm_DatabaseQueue constructor. * @param Hm_DB $db * @param PDO $dbConnection */ - public function __construct(private Hm_DB $db, protected PDO $dbConnection) { - } + public function __construct(private Hm_DB $db, protected PDO $dbConnection) {} /** * Push the job to the queue @@ -30,7 +31,12 @@ public function __construct(private Hm_DB $db, protected PDO $dbConnection) { */ public function push(Hm_BaseJob $job): void { $sql = "INSERT INTO hm_jobs (payload) VALUES (:payload)"; - $this->db->execute($this->dbConnection, $sql, ['payload' => serialize($job)], 'insert'); + try { + // Use the __serialize method from the Serializer trait + $this->db->execute($this->dbConnection, $sql, ['payload' => serialize($job)], 'insert'); + } catch (\Throwable $th) { + throw new \Exception("Failed to push job to the queue: " . $th->getMessage()); + } } /** @@ -45,7 +51,11 @@ public function pop(): ?Hm_BaseJob { if ($jobRecord) { $deleteSql = "DELETE FROM hm_jobs WHERE id = :id"; $this->db->execute($this->dbConnection, $deleteSql, ['id' => $jobRecord['id']], 'modify'); - return unserialize($jobRecord['payload']); + + // Use the __unserialize method from the Serializer trait + $job = unserialize($jobRecord['payload']); + $job->incrementAttempts(); + return $job; } return null; @@ -64,4 +74,84 @@ public function release(Hm_BaseJob $job, int $delay = 0): void { } $this->push($job); } + + /** + * Process the job and handle failures. + * + * @param Hm_BaseJob $job + * @param int $maxAttempts + * @return void + */ + public function process(Hm_BaseJob $job): void + { + try { + $job->handle(); + } catch (\Exception $e) { + $job->incrementAttempts(); + if ($job->getAttempts() >= $job->tries) { + $this->fail($job, $e); + } else { + $this->release($job, 5); + } + } + } + + /** + * Move job to failed jobs table after max attempts. + * + * @param Hm_BaseJob $job + * @param Exception $exception + * @return void + */ + public function fail(Hm_BaseJob $job, \Exception $exception): void + { + $sql = "INSERT INTO " . self::FAILED_JOBS_TABLE . " (payload, failed_at, exception) VALUES (:payload, :failed_at, :exception)"; + $this->db->execute( + $this->dbConnection, + $sql, + [ + 'payload' => serialize($job), // This still requires serialization, keep in mind + 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), + 'exception' => $exception->getMessage() + ], + 'insert' + ); + } + + /** + * Retry a failed job by moving it back to the main queue. + * + * @param int $failedJobId + * @return void + */ + public function retry(int $failedJobId): void + { + $sql = "SELECT * FROM " . self::FAILED_JOBS_TABLE . " WHERE id = :id"; + $failedJobRecord = $this->db->execute($this->dbConnection, $sql, ['id' => $failedJobId], 'select'); + + if ($failedJobRecord) { + $job = unserialize($failedJobRecord['payload']); + + // Remove from failed jobs table + $deleteSql = "DELETE FROM " . self::FAILED_JOBS_TABLE . " WHERE id = :id"; + $this->db->execute($this->dbConnection, $deleteSql, ['id' => $failedJobId], 'modify'); + + // Push back to the main queue + $this->push($job); + } + } + + /** + * Log the failed job. + * + * @param Hm_BaseJob $job + * @return void + */ + protected function logFailedJob(Hm_BaseJob $job): void { + $sql = "INSERT INTO failed_jobs (payload, attempts) VALUES (:payload, :attempts)"; + $this->db->execute($this->dbConnection, $sql, [ + 'payload' => serialize($job), // This still requires serialization + 'attempts' => $job->getAttempts() + ], 'insert'); + } } diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/Hm_RedisQueue.php index 8a69bf71a3..af09191627 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/Hm_RedisQueue.php @@ -3,7 +3,7 @@ namespace Services\Core\Queue\Drivers; use Hm_Redis; -use Services\Jobs\Hm_BaseJob; +use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; /** @@ -66,4 +66,8 @@ public function release(Hm_BaseJob $job, int $delay = 0): void { } $this->push($job); } + public function process(Hm_BaseJob $job): void + { + //TO DO: Implement process() method + } } diff --git a/services/Core/Queue/Hm_JobDispatcher.php b/services/Core/Queue/Hm_JobDispatcher.php index 89cfec439e..64084269b6 100644 --- a/services/Core/Queue/Hm_JobDispatcher.php +++ b/services/Core/Queue/Hm_JobDispatcher.php @@ -4,6 +4,7 @@ use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Hm_Container; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -12,20 +13,6 @@ */ class Hm_JobDispatcher { - protected Hm_QueueManager $queueManager; - protected string $defaultDriver; - - /** - * Hm_JobDispatcher constructor. - * @param Hm_QueueManager $queueManager - * @param string $defaultDriver - */ - public function __construct(ContainerInterface $container, string $defaultDriver = 'redis') - { - $this->queueManager = $container->get('Hm_QueueManager');//$this->queueManager = $queueManager; - $this->defaultDriver = $defaultDriver; - } - /** * Dispatch the job to the queue * @@ -33,11 +20,10 @@ public function __construct(ContainerInterface $container, string $defaultDriver * @param string|null $queue * @return void */ - public function dispatch(Hm_BaseJob $job, string $queue = null): void { - if ($job instanceof Hm_ShouldQueue) { - $driver = $job->driver ?? $this->defaultDriver; - $queueDriver = $this->queueManager->getDriver($driver); - + static public function dispatch(Hm_BaseJob $job): void { + if (is_subclass_of($job, Hm_ShouldQueue::class)) { + $driver = $job->driver; + $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); if ($queueDriver) { $queueDriver->push($job); } else { @@ -46,7 +32,5 @@ public function dispatch(Hm_BaseJob $job, string $queue = null): void { }else { $job->handle(); } - // $driver = $this->queueManager->getDriver($queue ?? $this->defaultDriver); - // $driver->push($job); } } \ No newline at end of file diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/Hm_QueueWorker.php index 9b3786095c..011c4ba5ce 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/Hm_QueueWorker.php @@ -27,15 +27,15 @@ public function __construct(Hm_ShouldQueue $queue) * @return void */ public function work(): void { - dd($this->queue); while ($job = $this->queue->pop()) { try { - $job->handle(); + // dd($job); + $this->queue->process($job); } catch (\Exception $e) { - $job->failed(); - // Optionally release the job back to the queue with a delay - $this->queue->release($job, 30); + // $job->failed(); + // // Optionally release the job back to the queue with a delay + // $this->queue->release($job, 30); } } } diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php index 881d20ac4b..f3f370fd9c 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -2,10 +2,23 @@ namespace Services\Events; -use Services\Core\Events\Hm_BaseEvent; use Services\Traits\Hm_Dispatchable; +use Services\Core\Events\Hm_BaseEvent; +use Services\Traits\Hm_InteractsWithQueue; +use Services\Contracts\Queue\Hm_ShouldQueue; -class Hm_NewEmailProcessedEvent extends Hm_BaseEvent +class Hm_NewEmailProcessedEvent extends Hm_BaseEvent implements Hm_ShouldQueue { - use Hm_Dispatchable; + use Hm_Dispatchable, Hm_InteractsWithQueue; + + /** + * Create a new event instance. + * @param $email + * + * @return void + */ + public function __construct(public string $email) + { + $this->email = $email; + } } diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php index 60751a3937..bc4c878a4a 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -3,6 +3,7 @@ namespace Services\Jobs; use Services\Core\Jobs\Hm_BaseJob; +use Services\Traits\Hm_Serializes; use Services\Traits\Hm_Dispatchable; use Services\Traits\Hm_InteractsWithQueue; use Services\Contracts\Queue\Hm_ShouldQueue; @@ -10,11 +11,13 @@ class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue { - use Hm_Dispatchable, Hm_InteractsWithQueue; + use Hm_Dispatchable, Hm_InteractsWithQueue, Hm_Serializes; - protected string $driver = 'database'; + public string $driver = 'database'; - public function __construct(public string $email) + public string $email; + + public function __construct(string $email) { $this->email = $email; } @@ -22,9 +25,13 @@ public function __construct(public string $email) public function handle(): void { //fetch new email + // $imap = Hm_Container::getContainer()->get('imap');//Hm_Imap::class + // $newMessages = $imap->search('UNSEEN'); //then process it - dump('job processing'); + dump("Processing ".self::class); + + Hm_NewEmailProcessedEvent::dispatch($this->email); } @@ -32,4 +39,8 @@ public function failed(): void { print("Failed to process email for {$this->email}\n"); } + public function process(Hm_BaseJob $job): void + { + + } } diff --git a/services/Listeners/Hm_NewMaiListener.php b/services/Listeners/Hm_NewMaiListener.php index ae8672b5a5..67c9eaea93 100644 --- a/services/Listeners/Hm_NewMaiListener.php +++ b/services/Listeners/Hm_NewMaiListener.php @@ -6,7 +6,8 @@ class Hm_NewMaiListener { public function handle(object $event): void { - var_dump("Hm_NewMaiListener: New email processed: {$event->email}"); + // dd($event); + // dd("Hm_NewMaiListener: New email processed: {$event->email}"); //TO DO: we implment notification dispatch here } } \ No newline at end of file diff --git a/services/Providers/Hm_EventServiceProvider.php b/services/Providers/Hm_EventServiceProvider.php index 71687a8145..fc0eb75de7 100644 --- a/services/Providers/Hm_EventServiceProvider.php +++ b/services/Providers/Hm_EventServiceProvider.php @@ -23,16 +23,4 @@ public function register(): void } } } - - /** - * Set the container instance for events and listeners. - * - * @param ContainerInterface $container - * @return void - */ - public function setContainer(ContainerInterface $container): void - { - // var_dump($container); - // Hm_EventManager::setContainer($container); - } } diff --git a/services/Traits/Hm_Dispatchable.php b/services/Traits/Hm_Dispatchable.php index edc097fe6e..d994e8efe7 100644 --- a/services/Traits/Hm_Dispatchable.php +++ b/services/Traits/Hm_Dispatchable.php @@ -2,6 +2,7 @@ namespace Services\Traits; +use Services\Core\Events\Hm_BaseEvent; use Services\Core\Events\Hm_EventDispatcher; use Services\Core\Jobs\Hm_BaseJob; use Services\Core\Queue\Hm_JobDispatcher; @@ -16,10 +17,12 @@ trait Hm_Dispatchable public static function dispatch(...$arguments) { $instance = new static(...$arguments); - if (is_subclass_of($instance, Hm_BaseJob::class)) { - # code... Hm_JobDispatcher::dispatch($instance); + }elseif(is_subclass_of($instance, Hm_BaseEvent::class)){ + Hm_EventDispatcher::dispatch($instance); + }else{ + throw new \Exception("Class must be an instance of Hm_BaseJob or Hm_BaseEvent"); } } diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/Hm_InteractsWithQueue.php index 478333ac4a..831ca78e60 100644 --- a/services/Traits/Hm_InteractsWithQueue.php +++ b/services/Traits/Hm_InteractsWithQueue.php @@ -5,6 +5,7 @@ use Services\Contracts\Hm_Job; use Services\Core\Queue\Hm_QueueManager; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Jobs\Hm_BaseJob; /** * Trait Hm_InteractsWithQueue @@ -24,7 +25,7 @@ trait Hm_InteractsWithQueue * @param string|null $queue Optional name of the queue. * @return void */ - public function push(Hm_Job $job, $data = '', $queue = null) + public function push(Hm_Job $job, $data = '', $queue = null): void { $driver = $this->getDriver(); @@ -40,7 +41,7 @@ public function push(Hm_Job $job, $data = '', $queue = null) * @param string|null $queue Optional name of the queue. * @return mixed The job from the queue or null if the queue is empty. */ - public function pop($queue = null) + public function pop($queue = null): ?Hm_BaseJob { $driver = $this->getDriver(); @@ -63,7 +64,7 @@ public function pop($queue = null) * @param int $delay The number of seconds to delay the release. * @return void */ - public function release($queue = null, $delay = 0) + public function release($queue = null, $delay = 0): void { $driver = $this->getDriver(); diff --git a/services/Traits/Hm_Queueable.php b/services/Traits/Hm_Queueable.php new file mode 100644 index 0000000000..9ff4a22e02 --- /dev/null +++ b/services/Traits/Hm_Queueable.php @@ -0,0 +1,7 @@ +getProperties(); + + $values = []; + + foreach ($properties as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setAccessible(true); + $name = $property->getName(); + + // Avoid serializing if it has a default value + if ($property->hasDefaultValue() && $property->getValue($this) === $property->getDefaultValue()) { + continue; + } + + $values[$name] = $property->getValue($this); + } + + return array_keys($values); + } + + /** + * Restore the model after serialization. + * + * @return void + */ + public function __wakeup() + { + foreach ((new ReflectionClass($this))->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setAccessible(true); + // Restore the property using its current value + $property->setValue($this, $this->getRestoredPropertyValue( + $this->getPropertyValue($property) + )); + } + } + + /** + * Restore the model after serialization (PHP 7+). + * + * @return array + */ + public function __serialize(): array + { + $values = []; + $properties = (new ReflectionClass($this))->getProperties(); + + foreach ($properties as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setAccessible(true); + + if ($property->isInitialized($this)) { + $values[$property->getName()] = $property->getValue($this); + } + } + + return $values; + } + + public function __unserialize(array $data): void + { + $properties = (new ReflectionClass($this))->getProperties(); + + foreach ($properties as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setAccessible(true); + + if (array_key_exists($property->getName(), $data)) { + $property->setValue($this, $data[$property->getName()]); + } + } + } + + /** + * Get the property value for the given property. + * + * @param ReflectionProperty $property + * @return mixed + */ + protected function getPropertyValue(ReflectionProperty $property) + { + $property->setAccessible(true); + return $property->getValue($this); + } + + /** + * Placeholder method for restoring property value. + * You can customize this based on your needs. + * + * @param mixed $value + * @return mixed + */ + protected function getRestoredPropertyValue($value) + { + // Implement any custom restoration logic if needed + return $value; + } +} diff --git a/services/readme.rd b/services/readme.rd index 76ac25e8f0..7d723ad14d 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -58,4 +58,24 @@ $notification = new UserNotification($config); // Send a message through the specified channels $message = "Hello! You have a new alert."; $notification->sendNotification($message); +``` + +#Database queue tables + +We have twi database that we using to manage database queue: `hm_jobs` & `hm_failed_jobs` +``` +-- Active jobs table +CREATE TABLE hm_jobs ( + id INT AUTO_INCREMENT PRIMARY KEY, + payload TEXT, + attempts INT DEFAULT 0 +); + +-- Failed jobs table +CREATE TABLE hm_failed_jobs ( + id INT AUTO_INCREMENT PRIMARY KEY, + payload TEXT, + failed_at DATETIME, + exception TEXT +); ``` \ No newline at end of file diff --git a/tests/phpunit/command.php b/tests/phpunit/command.php index 73fc26ed40..9e10fb928d 100644 --- a/tests/phpunit/command.php +++ b/tests/phpunit/command.php @@ -1,14 +1,11 @@ container = $this->getMockBuilder(ContainerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->command = new Hm_TestCommand($this->container); + $this->command = new Hm_TestCommand(); $this->input = new ArrayInput([]); $this->output = new BufferedOutput(); From 39e5ea3bfe23e3c257369ea41d1fc31280d9f8ee Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Fri, 1 Nov 2024 05:44:09 +0300 Subject: [PATCH 10/21] Sqs & Redis queue driver completed and tested --- .env.example | 17 ++- config/app.php | 20 --- config/redis.php | 23 +++- config/sqs.php | 9 +- lib/cache.php | 8 ++ lib/framework.php | 1 + lib/sqs.php | 49 +++---- .../Core/Commands/Hm_QueueWorkCommand.php | 2 +- services/Core/Hm_Container.php | 12 +- .../Core/Queue/Drivers/Hm_AmazonSQSQueue.php | 107 +++++++++++++-- services/Core/Queue/Drivers/Hm_RedisQueue.php | 125 +++++++++++++++--- services/Events/Hm_NewEmailProcessedEvent.php | 2 +- services/Jobs/Hm_ProcessNewEmail.php | 2 +- .../Providers/Hm_EventServiceProvider.php | 1 - .../Providers/Hm_QueueServiceProvider.php | 7 +- 15 files changed, 282 insertions(+), 103 deletions(-) diff --git a/.env.example b/.env.example index d81ba96e3e..a2bb0b91f5 100644 --- a/.env.example +++ b/.env.example @@ -214,13 +214,16 @@ JS_EXCLUDE_DEPS= QUEUE_CONNECTION=database -AWS_KEY='your-aws-access-key' -AWS_SECRET='your-aws-secret-key' -AWS_REGION=us-east-1 -AWS_SQS_QUEUE_URL='https://sqs.us-east-1.amazonaws.com/123456789012/your-queue-name' +AWS_ACCESS_KEY_ID='your-aws-access-key' +AWS_SECRET_ACCESS_KEY='your-aws-secret-key' +AWS_DEFAULT_REGION=us-east-1 +# AWS_SQS_QUEUE_URL='https://sqs.us-east-1.amazonaws.com/your-account-id/your-queue-name' -REDIS_HOST=localhost +ENABLE_REDIS=true +REDIS_SERVER=localhost REDIS_PORT=6379 -REDIS_USERNAME=null -REDIS_PWD=null +REDIS_INDEX=1 +REDIS_PASS=null +REDIS_SOCKET=/var/run/redis/redis-server.sock REDIS_PREFIX= + diff --git a/config/app.php b/config/app.php index 063e7dd542..dca0cef799 100644 --- a/config/app.php +++ b/config/app.php @@ -429,26 +429,6 @@ | 'cache_class' => env('CACHE_CLASS') */ - - /* - | ------------- - | Redis Support - | ------------- - | - | Configure Redis details below to use it for caching - */ - 'enable_redis' => env('ENABLE_REDIS', true), - - 'redis_server' => env('REDIS_SERVER', '127.0.0.1'), - - 'redis_port' => env('REDIS_PORT', 6379), - - 'redis_index' => env('REDIS_INDEX', 1), - - 'redis_pass' => env('REDIS_PASS'), - - 'redis_socket' => env('REDIS_SOCKET', '/var/run/redis/redis-server.sock'), - /* | ----------------- | Memcached Support diff --git a/config/redis.php b/config/redis.php index 6c1c921992..4fbd4cb46b 100644 --- a/config/redis.php +++ b/config/redis.php @@ -1,9 +1,22 @@ env('REDIS_HOST',"127.0.0.1"), - 'redis_port' => env('REDIS_PORT',"6379"), - 'redis_username' => env('REDIS_USERNAME', null), - 'redis_password' => env('REDIS_PWD', null), - 'redis_prefix' => env('REDIS_PREFIX', ''), + /* + | ------------- + | Redis Support + | ------------- + | + | Configure Redis details below to use it for caching and queueing. + */ + 'enable_redis' => env('ENABLE_REDIS', true), + + 'redis_server' => env('REDIS_SERVER', '127.0.0.1'), + + 'redis_port' => env('REDIS_PORT', 6379), + + 'redis_index' => env('REDIS_INDEX', 1), + + 'redis_pass' => env('REDIS_PASS'), + + 'redis_socket' => env('REDIS_SOCKET', ''), ]; \ No newline at end of file diff --git a/config/sqs.php b/config/sqs.php index 16ce606123..168d911764 100644 --- a/config/sqs.php +++ b/config/sqs.php @@ -1,8 +1,9 @@ env('AWS_KEY',""), - 'aws_secret' => env('AWS_SECRET',""), - 'aws_region' => env('AWS_REGION', 'us-east-1'), - 'sqs_queue_url' => env('AWS_SQS_QUEUE_URL', null), + 'aws_key' => env('AWS_ACCESS_KEY_ID',""), + 'aws_secret' => env('AWS_SECRET_ACCESS_KEY',""), + 'aws_region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + // 'sqs_queue_url' => env('AWS_SQS_QUEUE_URL', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'sqs_queue' => env('SQS_QUEUE', 'default'), ]; \ No newline at end of file diff --git a/lib/cache.php b/lib/cache.php index 24c4867f35..1f54e07162 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -242,6 +242,7 @@ public function __construct($config) { */ public function connect() { $this->cache_con = Hm_Functions::redis(); + try { if ($this->socket) { $con = $this->cache_con->connect($this->socket); @@ -295,6 +296,13 @@ public function close() { return $this->cache_con->close(); } + /** + * Get the Redis connection + * @return Redis|null + */ + public function getInstance() { + return $this->cache_con; + } /** * Get the Redis connection * @return Redis|null diff --git a/lib/framework.php b/lib/framework.php index 1faf4c0a8d..3049ab663a 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -36,6 +36,7 @@ require APP_PATH.'lib/api.php'; require APP_PATH.'lib/webdav_formats.php'; require APP_PATH.'lib/js_libs.php'; +require APP_PATH.'lib/sqs.php'; require_once APP_PATH.'modules/core/functions.php'; diff --git a/lib/sqs.php b/lib/sqs.php index 689b42d8b9..d89b8a0bdb 100644 --- a/lib/sqs.php +++ b/lib/sqs.php @@ -6,7 +6,7 @@ /** * Amazon SQS wrapper * @package framework - * @subpackage queue + * @subpackage sqs */ class Hm_AmazonSQS { @@ -14,7 +14,7 @@ class Hm_AmazonSQS { private static $sqsClient; /** Required configuration parameters */ - static private $required_config = ['aws_key', 'aws_secret', 'aws_region', 'sqs_queue_url']; + static private $required_config = ['aws_key', 'aws_secret', 'aws_region']; /** SQS config */ static private $config; @@ -29,7 +29,7 @@ static private function parse_config($site_config) { 'aws_key' => $site_config->get('aws_key', false), 'aws_secret' => $site_config->get('aws_secret', false), 'aws_region' => $site_config->get('aws_region', false), - 'sqs_queue_url' => $site_config->get('sqs_queue_url', false), + 'sqs_queue' => $site_config->get('sqs_queue', 'default'), ]; foreach (self::$required_config as $v) { @@ -69,22 +69,31 @@ static public function connect($site_config) { } } + static private function getQueueUrl(SqsClient $client, string $queueName) { + try { + $result = $client->getQueueUrl([ + 'QueueName' => $queueName, + ]); + return $result['QueueUrl']; + } catch (AwsException $e) { + Hm_Debug::add($e->getMessage()); + return false; + } + } + /** * Send a message to the SQS queue * @param string $message * @return string|false Message ID or false on failure */ - static public function sendMessage($message, int $delay = 0) { - if (!self::$sqsClient) { - return false; - } - + static public function sendMessage(SqsClient $client, $message, int $delay = 0, string $queueName = null) { try { - $result = self::$sqsClient->sendMessage([ - 'QueueUrl' => self::$config['sqs_queue_url'], + $result = $client->sendMessage([ + 'QueueUrl' => self::getQueueUrl($client, !is_null($queueName) ? $queueName : self::$config['sqs_queue']), 'MessageBody' => $message, 'DelaySeconds' => $delay, ]); + return $result['MessageId']; } catch (AwsException $e) { Hm_Debug::add($e->getMessage()); @@ -97,14 +106,10 @@ static public function sendMessage($message, int $delay = 0) { * @param int $maxMessages * @return array */ - static public function receiveMessages($maxMessages = 1) { - if (!self::$sqsClient) { - return []; - } - + static public function receiveMessages(SqsClient $client, $maxMessages = 1) { try { - $result = self::$sqsClient->receiveMessage([ - 'QueueUrl' => self::$config['sqs_queue_url'], + $result = $client->receiveMessage([ + 'QueueUrl' => self::getQueueUrl($client, self::$config['sqs_queue']), 'MaxNumberOfMessages' => $maxMessages, 'WaitTimeSeconds' => 10, ]); @@ -120,14 +125,10 @@ static public function receiveMessages($maxMessages = 1) { * @param string $receiptHandle * @return bool */ - static public function deleteMessage($receiptHandle) { - if (!self::$sqsClient) { - return false; - } - + static public function deleteMessage(SqsClient $client, $receiptHandle) { try { - self::$sqsClient->deleteMessage([ - 'QueueUrl' => self::$config['sqs_queue_url'], + $client->deleteMessage([ + 'QueueUrl' => self::getQueueUrl($client, self::$config['sqs_queue']), 'ReceiptHandle' => $receiptHandle, ]); return true; diff --git a/services/Core/Commands/Hm_QueueWorkCommand.php b/services/Core/Commands/Hm_QueueWorkCommand.php index 395f8b19dc..711fce0e0e 100644 --- a/services/Core/Commands/Hm_QueueWorkCommand.php +++ b/services/Core/Commands/Hm_QueueWorkCommand.php @@ -29,7 +29,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { - $connection = $input->getArgument('connection') ?: 'default'; + $connection = $input->getArgument('connection') ?: env('QUEUE_CONNECTION', 'database'); $queue = $input->getOption('queue') ?: 'default'; $output->writeln("Processing jobs from the [$queue] on connection [$connection]..."); diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index d76801a8ab..8762d38b34 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -5,7 +5,6 @@ use Hm_DB; use Hm_Redis; use Hm_AmazonSQS; -use Services\Core\Queue\Hm_QueueManager; use Symfony\Component\DependencyInjection\ContainerBuilder; use Services\Providers\{ Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider }; @@ -35,11 +34,16 @@ public static function bind(): ContainerBuilder ->setShared(true); // Register Hm_Redis - self::$container->register('redis', Hm_Redis::class) + $redis = new Hm_Redis(self::$container->get('config')); + $redis->connect(); + self::$container->set('redis.connection', $redis->getInstance()); + self::$container->register('redis', Hm_Redis::class)->setArgument(0, self::$container->get('config')) + ->setShared(true); // Register Hm_AmazonSQS - self::$container->register('amazon.sqs', Hm_AmazonSQS::class) + self::$container->set('amazon.sqs.connection', Hm_AmazonSQS::connect(self::$container->get('config'))); + self::$container->register('amazon.sqs',Hm_AmazonSQS::class) ->setShared(true); // Register Hm_CommandServiceProvider @@ -48,8 +52,6 @@ public static function bind(): ContainerBuilder // Register Hm_QueueServiceProvider self::$container->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) - // ->addArgument(new \Symfony\Component\DependencyInjection\Reference(Hm_Site_Config_File::class)) - // ->addArgument(null) ->setShared(true); self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) diff --git a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php index b18e04b684..478bda6e64 100644 --- a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php +++ b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php @@ -3,27 +3,44 @@ namespace Services\Core\Queue\Drivers; use Hm_AmazonSQS; -use Services\Jobs\Hm_BaseJob; +use Aws\Sqs\SqsClient; +use Services\Contracts\Queue\Hm_Queueable; +use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; /** * Amazon SQS Queue */ -class Hm_AmazonSQSQueue implements Hm_ShouldQueue +class Hm_AmazonSQSQueue implements Hm_ShouldQueue, Hm_Queueable { /** * @var Hm_AmazonSQS */ protected Hm_AmazonSQS $amazonSQS; + /** + * @var SqsClient + */ + protected SqsClient $sqsConnection; + + /** + * Queue name for failed jobs + * + * @var string + */ + protected string $failedQueue; + /** * Constructor * * @param Hm_AmazonSQS $amazonSQS + * @param SqsClient $sqsConnection */ - public function __construct(Hm_AmazonSQS $amazonSQS) + public function __construct(Hm_AmazonSQS $amazonSQS, SqsClient $sqsConnection) { $this->amazonSQS = $amazonSQS; + $this->sqsConnection = $sqsConnection; + $this->failedQueue = 'failed_jobs'; } /** @@ -34,7 +51,7 @@ public function __construct(Hm_AmazonSQS $amazonSQS) */ public function push(Hm_BaseJob $job): void { - $this->amazonSQS->sendMessage(serialize($job)); + $this->amazonSQS->sendMessage($this->sqsConnection, serialize($job)); } /** @@ -44,15 +61,23 @@ public function push(Hm_BaseJob $job): void */ public function pop(): ?Hm_BaseJob { - $messages = $this->amazonSQS->receiveMessages(); - + $messages = $this->amazonSQS->receiveMessages($this->sqsConnection); if (!empty($messages)) { $message = $messages[0]; $receiptHandle = $message['ReceiptHandle']; + $body = $message['Body']; + $this->amazonSQS->deleteMessage($this->sqsConnection, $receiptHandle); + + $job = unserialize($body); - $this->amazonSQS->deleteMessage($receiptHandle); + try { + $job->handle(); + } catch (\Exception $e) { + $this->fail($job, $e); // Log the failure + throw new \Exception("Failed to process job: " . $e->getMessage()); + } - return unserialize($message['Body']); + return $job; // Return the job if it was processed successfully } return null; @@ -68,6 +93,70 @@ public function pop(): ?Hm_BaseJob public function release(Hm_BaseJob $job, int $delay = 0): void { $messageBody = serialize($job); - $this->amazonSQS->sendMessage($messageBody, $delay); + $this->amazonSQS->sendMessage($this->sqsConnection, $messageBody, $delay); + } + + /** + * Process the job and handle failures. + * + * @param Hm_BaseJob $job + * @param int $maxAttempts + * @return void + */ + public function process(Hm_BaseJob $job): void + { + try { + $job->handle(); + } catch (\Exception $e) { + $job->incrementAttempts(); + if ($job->getAttempts() >= $job->tries) { + $this->fail($job, $e); + } else { + $this->release($job, 5); + } + } + } + + /** + * Move a job to the failed jobs queue after max attempts + * + * @param Hm_BaseJob $job + * @param \Exception $exception + * @return void + */ + protected function fail(Hm_BaseJob $job, \Exception $exception): void + { + $failedJobData = [ + 'job' => serialize($job), + 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), + 'exception' => $exception->getMessage() + ]; + + // You may want to handle how you store failed jobs. + // For simplicity, we will just serialize and log them here, + // but ideally, you would want to use a persistent store. + $this->amazonSQS->sendMessage($this->sqsConnection, serialize($failedJobData), 0, $this->failedQueue); + } + + /** + * Retry failed jobs + * + * @return void + */ + public function retryFailedJobs(): void + { + $messages = $this->amazonSQS->receiveMessages($this->sqsConnection, $this->failedQueue); + foreach ($messages as $message) { + $body = $message['Body']; + $failedJobData = unserialize($body); + $job = unserialize($failedJobData['job']); + + // Optionally reset attempts if your job has that logic + $job->resetAttempts(); + $this->push($job); // Push back to current queue + + // Optionally delete the failed job message + $this->amazonSQS->deleteMessage($this->sqsConnection, $message['ReceiptHandle']); + } } } diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/Hm_RedisQueue.php index af09191627..895f06bd4f 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/Hm_RedisQueue.php @@ -3,71 +3,152 @@ namespace Services\Core\Queue\Drivers; use Hm_Redis; +use Exception; +use Redis; +use Services\Contracts\Queue\Hm_Queueable; use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; /** - * Redis Queue + * Redis Queue using Hm_Redis with Hm_Cache_Base trait support */ -class Hm_RedisQueue implements Hm_ShouldQueue +class Hm_RedisQueue implements Hm_ShouldQueue, Hm_Queueable { + protected Hm_Redis $redis; + protected Redis $redisConnection; + /** - * @var Hm_Redis + * Queue name for active jobs + * + * @var string */ - protected Hm_Redis $redis; + protected string $currentQueue; /** + * Queue name for failed jobs + * * @var string */ - protected string $queue; + protected string $failedQueue; /** * Constructor * - * @param Hm_Redis $redis + * @param $redisConnection * @param string $queue */ - public function __construct(Hm_Redis $redis, string $queue = 'default') { + public function __construct(Hm_Redis $redis, Redis $redisConnection, string $queue = 'default') { $this->redis = $redis; - $this->queue = $queue; + $this->redisConnection = $redisConnection; + $this->currentQueue = "hm_jobs:{$queue}_jobs"; + $this->failedQueue = "hm_jobs:{$queue}_failed"; } /** - * Push the job to the queue + * Push a job to the current queue * * @param Hm_BaseJob $job * @return void */ - public function push(Hm_BaseJob $job): void { - // Use the Redis connection to push the serialized job to the queue - $this->redis->getConnection()->rpush($this->queue, serialize($job)); + public function push(Hm_BaseJob $job): void + { + if ($this->redis->is_active()) { + try { + $serializedJob = serialize($job); + $this->redisConnection->rpush($this->currentQueue, $serializedJob); + } catch (Exception $e) { + throw new Exception("Failed to push job to the queue: " . $e->getMessage()); + } + } } /** - * Pop the job from the queue + * Pop a job from the current queue + * we are usig lpop to get the first job in the queue and remove it * * @return Hm_BaseJob|null */ public function pop(): ?Hm_BaseJob { - $jobData = $this->redis->getConnection()->lpop($this->queue); - return $jobData ? unserialize($jobData) : null; + if ($this->redis->is_active()) { + $jobData = $this->redisConnection->lpop($this->currentQueue); + if ($jobData) { + $job = unserialize($jobData); + $job->incrementAttempts(); + return $job; + } + } + + return null; } /** - * Release the job back into the queue + * Release a job back into the current queue after a delay * * @param Hm_BaseJob $job * @param int $delay * @return void */ public function release(Hm_BaseJob $job, int $delay = 0): void { - if ($delay > 0) { - sleep($delay); + if ($this->redis->is_active()) { + if ($delay > 0) { + sleep($delay); + } + $this->push($job); } - $this->push($job); } - public function process(Hm_BaseJob $job): void - { - //TO DO: Implement process() method + + /** + * Process a job with failure handling + * + * @param Hm_BaseJob $job + * @return void + */ + public function process(Hm_BaseJob $job): void { + try { + $job->handle(); + } catch (Exception $e) { + $job->incrementAttempts(); + if ($job->getAttempts() >= $job->tries) { + $this->fail($job, $e); + } else { + $this->release($job, 5); + } + } + } + + /** + * Move a job to the failed jobs queue after max attempts + * + * @param Hm_BaseJob $job + * @param Exception $exception + * @return void + */ + public function fail(Hm_BaseJob $job, Exception $exception): void { + if ($this->redis->is_active()) { + $failedJobData = [ + 'job' => serialize($job), + 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), + 'exception' => $exception->getMessage() + ]; + $this->redisConnection->rpush($this->failedQueue, serialize($failedJobData)); + } + } + + /** + * Retry a failed job by moving it back to the current queue + * + * @return void + */ + public function retryFailedJobs(): void { + if ($this->redis->is_active()) { + while ($failedJobData = $this->redisConnection->lpop($this->failedQueue)) { + $failedJobRecord = unserialize($failedJobData); + $job = unserialize($failedJobRecord['job']); + + // Reset attempts and move back to current queue + $job->resetAttempts(); + $this->push($job); + } + } } } diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php index f3f370fd9c..575afe399b 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -17,7 +17,7 @@ class Hm_NewEmailProcessedEvent extends Hm_BaseEvent implements Hm_ShouldQueue * * @return void */ - public function __construct(public string $email) + public function __construct(private string $email) { $this->email = $email; } diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php index bc4c878a4a..96c94aec1c 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -13,7 +13,7 @@ class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue { use Hm_Dispatchable, Hm_InteractsWithQueue, Hm_Serializes; - public string $driver = 'database'; + public string $driver = 'sqs'; public string $email; diff --git a/services/Providers/Hm_EventServiceProvider.php b/services/Providers/Hm_EventServiceProvider.php index fc0eb75de7..dfc549b57e 100644 --- a/services/Providers/Hm_EventServiceProvider.php +++ b/services/Providers/Hm_EventServiceProvider.php @@ -5,7 +5,6 @@ use Services\Core\Events\Hm_EventDispatcher; use Services\Listeners\Hm_NewMaiListener; use Services\Events\Hm_NewEmailProcessedEvent; -use Symfony\Component\DependencyInjection\ContainerInterface; class Hm_EventServiceProvider { diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index 8191981037..10f2722789 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -9,7 +9,6 @@ use Services\Core\Queue\Drivers\Hm_DatabaseQueue; use Services\Core\Queue\Drivers\Hm_AmazonSQSQueue; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ContainerBuilder; class Hm_QueueServiceProvider { @@ -35,15 +34,17 @@ public function register() case 'redis': $containerBuilder->register('queue.driver.redis', Hm_RedisQueue::class) ->addArgument(new Reference('redis')) + ->addArgument(new Reference('redis.connection')) ->addArgument('default'); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['redis', new Reference('queue.driver.redis')]); break; case 'sqs': $containerBuilder->register('queue.driver.sqs', Hm_AmazonSQSQueue::class) - ->addArgument($containerBuilder->get('amazon.sqs')); + ->addArgument(new Reference('amazon.sqs')) + ->addArgument(new Reference('amazon.sqs.connection')); $containerBuilder->getDefinition('queue.manager') - ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.redis')]); + ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.sqs')]); break; default: $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) From 7cb60c2730d8d673fc21ab325f84952d2032fc36 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Mon, 4 Nov 2024 07:11:39 +0300 Subject: [PATCH 11/21] Working on Scheduling --- services/Core/Queue/Drivers/Hm_RedisQueue.php | 2 +- .../Core/Scheduling/{CacheMutex.php => Hm_CacheMutex.php} | 2 +- .../Scheduling/{CommandTask.php => Hm_CommandTask.php} | 4 ++-- .../{ScheduledTask.php => Hm_ScheduledTask.php} | 2 +- .../Core/Scheduling/{Scheduler.php => Hm_Scheduler.php} | 6 +++--- services/Events/Hm_NewEmailProcessedEvent.php | 6 ++---- services/Providers/Hm_SchedulerServiceProvider.php | 8 ++++---- services/Traits/Hm_Dispatchable.php | 4 ++-- services/Traits/Hm_InteractsWithQueue.php | 2 +- 9 files changed, 17 insertions(+), 19 deletions(-) rename services/Core/Scheduling/{CacheMutex.php => Hm_CacheMutex.php} (97%) rename services/Core/Scheduling/{CommandTask.php => Hm_CommandTask.php} (93%) rename services/Core/Scheduling/{ScheduledTask.php => Hm_ScheduledTask.php} (99%) rename services/Core/Scheduling/{Scheduler.php => Hm_Scheduler.php} (88%) diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/Hm_RedisQueue.php index 895f06bd4f..a8b11fcf25 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/Hm_RedisQueue.php @@ -10,7 +10,7 @@ use Services\Contracts\Queue\Hm_ShouldQueue; /** - * Redis Queue using Hm_Redis with Hm_Cache_Base trait support + * Redis Queue using Hm_Redis with Hm_Cache_Base trait Hm_RedisQueue */ class Hm_RedisQueue implements Hm_ShouldQueue, Hm_Queueable { diff --git a/services/Core/Scheduling/CacheMutex.php b/services/Core/Scheduling/Hm_CacheMutex.php similarity index 97% rename from services/Core/Scheduling/CacheMutex.php rename to services/Core/Scheduling/Hm_CacheMutex.php index f23d093316..bcf48c3fec 100644 --- a/services/Core/Scheduling/CacheMutex.php +++ b/services/Core/Scheduling/Hm_CacheMutex.php @@ -5,7 +5,7 @@ use Hm_Cache; use Services\Contracts\Scheduling\Mutex; -class CacheMutex implements Mutex +class Hm_CacheMutex implements Mutex { private $cache; diff --git a/services/Core/Scheduling/CommandTask.php b/services/Core/Scheduling/Hm_CommandTask.php similarity index 93% rename from services/Core/Scheduling/CommandTask.php rename to services/Core/Scheduling/Hm_CommandTask.php index 6eb77ec50b..ea1aa530e2 100644 --- a/services/Core/Scheduling/CommandTask.php +++ b/services/Core/Scheduling/Hm_CommandTask.php @@ -2,7 +2,7 @@ namespace Services\Core\Scheduling; -class CommandTask extends ScheduledTask +class Hm_CommandTask extends Hm_ScheduledTask { private $command; private $onOneServer = false; @@ -10,7 +10,7 @@ class CommandTask extends ScheduledTask private $expiresAt = null; private $withoutOverlapping = false; - public function __construct($command, CacheMutex $mutex) + public function __construct($command, Hm_CacheMutex $mutex) { $fullCommand = "php console " . $command; parent::__construct(function () use ($fullCommand) { diff --git a/services/Core/Scheduling/ScheduledTask.php b/services/Core/Scheduling/Hm_ScheduledTask.php similarity index 99% rename from services/Core/Scheduling/ScheduledTask.php rename to services/Core/Scheduling/Hm_ScheduledTask.php index beb7dd0324..dbdcd2970f 100644 --- a/services/Core/Scheduling/ScheduledTask.php +++ b/services/Core/Scheduling/Hm_ScheduledTask.php @@ -4,7 +4,7 @@ use Services\Traits\Hm_ScheduleFrequencyManager; -class ScheduledTask +class Hm_ScheduledTask { use Hm_ScheduleFrequencyManager; diff --git a/services/Core/Scheduling/Scheduler.php b/services/Core/Scheduling/Hm_Scheduler.php similarity index 88% rename from services/Core/Scheduling/Scheduler.php rename to services/Core/Scheduling/Hm_Scheduler.php index 8f7c38bd28..fa7d25a9fa 100644 --- a/services/Core/Scheduling/Scheduler.php +++ b/services/Core/Scheduling/Hm_Scheduler.php @@ -5,7 +5,7 @@ use Hm_Cache; use Hm_Session_Setup; -class Scheduler +class Hm_Scheduler { protected $tasks = []; protected $config; @@ -18,7 +18,7 @@ public function __construct($config) public function command($command) { $cache = new Hm_Cache($this->config, new Hm_Session_Setup($this->config)); - $commandTask = new CommandTask($command, new CacheMutex($cache)); + $commandTask = new Hm_CommandTask($command, new Hm_CacheMutex($cache)); $this->tasks[] = $commandTask; return $commandTask; } @@ -35,7 +35,7 @@ public function command($command) */ public function register(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC') { - $task = new ScheduledTask($callback, $name, $description, $tags, $timezone); + $task = new Hm_ScheduledTask($callback, $name, $description, $tags, $timezone); $this->tasks[] = $task; return $task; diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php index 575afe399b..029f110d71 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -4,12 +4,10 @@ use Services\Traits\Hm_Dispatchable; use Services\Core\Events\Hm_BaseEvent; -use Services\Traits\Hm_InteractsWithQueue; -use Services\Contracts\Queue\Hm_ShouldQueue; -class Hm_NewEmailProcessedEvent extends Hm_BaseEvent implements Hm_ShouldQueue +class Hm_NewEmailProcessedEvent extends Hm_BaseEvent// implements Hm_ShouldQueue { - use Hm_Dispatchable, Hm_InteractsWithQueue; + use Hm_Dispatchable;//, Hm_InteractsWithQueue; /** * Create a new event instance. diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/Hm_SchedulerServiceProvider.php index 212c5f374a..33cbb192b9 100644 --- a/services/Providers/Hm_SchedulerServiceProvider.php +++ b/services/Providers/Hm_SchedulerServiceProvider.php @@ -3,8 +3,8 @@ namespace Services\Providers; use Hm_Cache; -use Services\Core\Scheduling\CacheMutex; -use Services\Core\Scheduling\Scheduler; +use Services\Core\Scheduling\Hm_CacheMutex; +use Services\Core\Scheduling\Hm_Scheduler; class Hm_SchedulerServiceProvider { @@ -20,10 +20,10 @@ public function register($config, $session) $cache = new Hm_Cache($config, $session); // Create the CacheMutex instance using the cache - $mutex = new CacheMutex($cache); + $mutex = new Hm_CacheMutex($cache); // Create the Scheduler instance, passing in the CacheMutex - $scheduler = new Scheduler($mutex); + $scheduler = new Hm_Scheduler($mutex); // Register scheduled tasks here (optional setup) // Example: diff --git a/services/Traits/Hm_Dispatchable.php b/services/Traits/Hm_Dispatchable.php index d994e8efe7..d9794d488b 100644 --- a/services/Traits/Hm_Dispatchable.php +++ b/services/Traits/Hm_Dispatchable.php @@ -2,10 +2,10 @@ namespace Services\Traits; -use Services\Core\Events\Hm_BaseEvent; -use Services\Core\Events\Hm_EventDispatcher; use Services\Core\Jobs\Hm_BaseJob; +use Services\Core\Events\Hm_BaseEvent; use Services\Core\Queue\Hm_JobDispatcher; +use Services\Core\Events\Hm_EventDispatcher; trait Hm_Dispatchable { diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/Hm_InteractsWithQueue.php index 831ca78e60..5a5cfa1dd9 100644 --- a/services/Traits/Hm_InteractsWithQueue.php +++ b/services/Traits/Hm_InteractsWithQueue.php @@ -3,9 +3,9 @@ namespace Services\Traits; use Services\Contracts\Hm_Job; +use Services\Core\Jobs\Hm_BaseJob; use Services\Core\Queue\Hm_QueueManager; use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Jobs\Hm_BaseJob; /** * Trait Hm_InteractsWithQueue From b49c1401f9ec3edab1321e716457fa170f3cd30a Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Fri, 8 Nov 2024 05:50:00 +0300 Subject: [PATCH 12/21] Testing Scheduling --- console | 3 - lib/cache.php | 1 + .../Core/Commands/Hm_ScheduleRunCommand.php | 37 +++++++ services/Core/Hm_Container.php | 1 + services/Core/Scheduling/Hm_CacheMutex.php | 25 ++++- services/Core/Scheduling/Hm_CommandTask.php | 14 ++- services/Core/Scheduling/Hm_ScheduledTask.php | 102 ++++++++++-------- services/Core/Scheduling/Hm_Scheduler.php | 11 +- services/Hm_ConsoleKernal.php | 29 +++++ services/Hm_bootstrap.php | 14 +++ .../Providers/Hm_SchedulerServiceProvider.php | 12 +-- .../Traits/Hm_ScheduleFrequencyManager.php | 15 +++ services/readme.rd | 5 + 13 files changed, 209 insertions(+), 60 deletions(-) create mode 100644 services/Core/Commands/Hm_ScheduleRunCommand.php create mode 100644 services/Hm_ConsoleKernal.php diff --git a/console b/console index 7b57a69953..31c37b1d0c 100644 --- a/console +++ b/console @@ -16,9 +16,6 @@ $commandServiceProvider->register($application); $queueServiceProvider = $containerBuilder->get('queue.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); $queueServiceProvider->register(); -$queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); -$queueServiceProvider->register($config, new Hm_Session_Setup($config)); - $eventServiceProvider = $containerBuilder->get('event.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); // $eventServiceProvider->setContainer($containerBuilder); $eventServiceProvider->register(); diff --git a/lib/cache.php b/lib/cache.php index 1f54e07162..336ef8f78c 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -603,6 +603,7 @@ protected function noop_get($key, $default) { * @return string */ protected function key_hash($key) { + dd($this->session->get('fingerprint')); return sprintf('hm_cache_%s', hash('sha256', (sprintf('%s%s%s%s', $key, SITE_ID, $this->session->get('fingerprint'), $this->session->get('username'))))); } diff --git a/services/Core/Commands/Hm_ScheduleRunCommand.php b/services/Core/Commands/Hm_ScheduleRunCommand.php new file mode 100644 index 0000000000..02cbbe2572 --- /dev/null +++ b/services/Core/Commands/Hm_ScheduleRunCommand.php @@ -0,0 +1,37 @@ +setDescription('Run all scheduled tasks that are due') + // Optionally, you can add other configuration or arguments here + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Get the scheduler instance from the container + $scheduler = Hm_Container::getContainer()->get('scheduler'); + // Run the tasks that are due + $scheduler->run(); + + $output->writeln("All due scheduled tasks have been executed."); + // dd($scheduler); + dd('hello'); + + return Command::SUCCESS; + } +} diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index 8762d38b34..ac6b520686 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -56,6 +56,7 @@ public static function bind(): ContainerBuilder self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) ->setShared(true); + self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) ->setShared(true); diff --git a/services/Core/Scheduling/Hm_CacheMutex.php b/services/Core/Scheduling/Hm_CacheMutex.php index bcf48c3fec..95e6ed538a 100644 --- a/services/Core/Scheduling/Hm_CacheMutex.php +++ b/services/Core/Scheduling/Hm_CacheMutex.php @@ -8,6 +8,7 @@ class Hm_CacheMutex implements Mutex { private $cache; + private $expiresAt; public function __construct(Hm_Cache $cache) { @@ -36,21 +37,20 @@ public function create($task, $expiresAt) /** * Check if a lock exists for the task. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return bool */ public function exists($task) { $key = $this->getMutexKey($task); $lockExpiry = $this->cache->get($key, false); - return $lockExpiry && $lockExpiry > time(); } /** * Release the lock for the task. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return void */ public function release($task) @@ -62,11 +62,26 @@ public function release($task) /** * Generate a unique key for the mutex. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return string */ private function getMutexKey($task) { - return 'mutex_' . hash('sha256', $task->name); + return 'mutex_' . hash('sha256', get_class($task) . $task->name . json_encode($task->command)); + // return 'mutex_' . hash('sha256', $task->name); + } + + /** + * Refresh the mutex lock expiry if it's still active. + * + * @param Task $task The task instance + * @return void + */ + public function refresh($task) + { + $key = $this->getMutexKey($task); + if ($this->cache->get($key, false)) { + $this->cache->set($key, time() + $this->expiresAt, $this->expiresAt); + } } } diff --git a/services/Core/Scheduling/Hm_CommandTask.php b/services/Core/Scheduling/Hm_CommandTask.php index ea1aa530e2..49f1728446 100644 --- a/services/Core/Scheduling/Hm_CommandTask.php +++ b/services/Core/Scheduling/Hm_CommandTask.php @@ -4,21 +4,29 @@ class Hm_CommandTask extends Hm_ScheduledTask { - private $command; + public $command; private $onOneServer = false; private $mutex; private $expiresAt = null; private $withoutOverlapping = false; - public function __construct($command, Hm_CacheMutex $mutex) + /** + * The name of the command task. + * + * @var string + */ + public $name; + + public function __construct($command, Hm_CacheMutex $mutex, $name = null) { + $this->name = $name ?: $command; $fullCommand = "php console " . $command; parent::__construct(function () use ($fullCommand) { echo "Executing Command: $fullCommand\n"; $output = shell_exec($fullCommand); echo $output; }, $command); - + $this->command = $fullCommand; $this->mutex = $mutex; } diff --git a/services/Core/Scheduling/Hm_ScheduledTask.php b/services/Core/Scheduling/Hm_ScheduledTask.php index dbdcd2970f..1d74323ca3 100644 --- a/services/Core/Scheduling/Hm_ScheduledTask.php +++ b/services/Core/Scheduling/Hm_ScheduledTask.php @@ -15,8 +15,10 @@ class Hm_ScheduledTask private $description; private $tags = []; private $lastRunTime; - private $timezone; - private $expression; + + private $maxRetries = 3; + private $retryInterval = 60; // Interval in seconds between retries + private $retryCount = 0; // Track the number of retries attempted public function __construct(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC', $expression = '* * * * *') { @@ -26,26 +28,19 @@ public function __construct(callable $callback, $name = '', $description = '', $ $this->tags = $tags; $this->timezone = $timezone; $this->expression = $expression; - $this->nextRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); - } - - public function enable() - { - $this->isEnabled = true; } - public function disable() - { - $this->isEnabled = false; - } - - // Check if the task is due public function isDue() { - return $this->isEnabled && new \DateTime('now', new \DateTimeZone($this->timezone)) >= $this->nextRunTime; + + if ($this->isEnabled) { + $this->calculateNextRunTime(); + dd($this->isEnabled); + return $this->nextRunTime <= new \DateTime('now', new \DateTimeZone($this->timezone)); + } + return false; } - // Execute the task public function run() { if (!$this->isDue()) { @@ -53,28 +48,51 @@ public function run() } try { + echo "Task '{$this->name}' is due and will be run.\n"; + + // Run the task callback call_user_func($this->callback); $this->lastRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); + + // Reset retry count after a successful run + $this->retryCount = 0; } catch (\Exception $e) { + echo "Error running task {$this->name}: " . $e->getMessage() . "\n"; + // Log the error message error_log("Error running task {$this->name}: " . $e->getMessage()); + + // Handle retries + $this->handleRetry($e); } $this->scheduleNextRun(); } - // Schedule the next run time based on the cron expression - public function scheduleNextRun() + private function handleRetry(\Exception $e) { - $this->nextRunTime = $this->calculateNextRunTime(); + if ($this->retryCount < $this->maxRetries) { + $this->retryCount++; + $retryTime = $this->retryInterval * $this->retryCount; + + // Log the retry attempt + error_log("Retry attempt {$this->retryCount} for task {$this->name}, will retry in {$retryTime} seconds."); + sleep($retryTime); + + // Try again + $this->run(); + } else { + // Log that we've exhausted all retries + error_log("Max retries reached for task {$this->name}. Task will not be retried."); + } } - public function getNextRunTime() + private function scheduleNextRun() { - return $this->nextRunTime; + // You can schedule the next run based on cron expression or your custom logic + $this->nextRunTime = $this->calculateNextRunTime(); } - // Calculate the next run time based on the cron expression private function calculateNextRunTime() { // Ensure the cron expression is valid @@ -97,12 +115,13 @@ private function calculateNextRunTime() // Calculate next minute $nextMinute = $this->getNextFieldValue($next->format('i'), $minuteField, 0, 59); if ($nextMinute !== null) { + $next->setTime($next->format('H'), $nextMinute); } else { $next->modify('+1 hour'); $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); } - + // Calculate next hour $nextHour = $this->getNextFieldValue($next->format('H'), $hourField, 0, 23); if ($nextHour !== null) { @@ -132,6 +151,7 @@ private function calculateNextRunTime() // Calculate next day of the week $nextDayOfWeek = $this->getNextFieldValue($next->format('w'), $dayOfWeekField, 0, 6); + if ($nextDayOfWeek !== null) { while ($next->format('w') !== $nextDayOfWeek) { $next->modify('+1 day'); @@ -143,16 +163,19 @@ private function calculateNextRunTime() return $next; } - // Get the next valid value for a cron field private function getNextFieldValue($currentValue, $field, $min, $max) { - // Handle a field in cron syntax (minute, hour, day, month, weekday) $values = []; - + if ($field === '*') { - return $min; + // If field is '*', we want the next value in sequence, wrapping if needed + if ($currentValue < $max) { + return $currentValue + 1; // Move to next minute, hour, etc. + } else { + return $min; // Wrap around to the minimum (e.g., new hour if on minute field) + } } - + foreach (explode(',', $field) as $part) { if (strpos($part, '/') !== false) { // Handle step values, e.g., */2 @@ -166,34 +189,29 @@ private function getNextFieldValue($currentValue, $field, $min, $max) // Handle ranges, e.g., 1-5 list($rangeStart, $rangeEnd) = explode('-', $part); for ($i = $rangeStart; $i <= $rangeEnd; $i++) { - $values[] = $i; + $values[] = (int)$i; } } else { // Single values $values[] = (int)$part; } } - - // Filter and sort unique values + + // Filter, sort, and keep unique values $values = array_unique(array_filter($values, function ($value) use ($min, $max) { return $value >= $min && $value <= $max; })); - sort($values); - - // Find the next valid value + + // Find the next valid value that is greater than the current value foreach ($values as $value) { if ($value > $currentValue) { return $value; } } - - return null; // If no valid next value is found + + // If no valid next value is found, wrap around to the first value in the list + return !empty($values) ? $values[0] : $min; } - - // Getter methods for the task properties - public function getName() { return $this->name; } - public function getDescription() { return $this->description; } - public function getTags() { return $this->tags; } - public function getTimezone() { return $this->timezone; } + } diff --git a/services/Core/Scheduling/Hm_Scheduler.php b/services/Core/Scheduling/Hm_Scheduler.php index fa7d25a9fa..af2230e5a0 100644 --- a/services/Core/Scheduling/Hm_Scheduler.php +++ b/services/Core/Scheduling/Hm_Scheduler.php @@ -3,6 +3,8 @@ namespace Services\Core\Scheduling; use Hm_Cache; +use Hm_Debug; +use Hm_Msgs; use Hm_Session_Setup; class Hm_Scheduler @@ -50,7 +52,14 @@ public function run() { foreach ($this->tasks as $task) { if ($task->isDue()) { - $task->run(); + try { + $task->run(); + Hm_Debug::add("Task '{$task->getName()}' executed successfully."); + Hm_Msgs::add("Task '{$task->getName()}' executed successfully."); + } catch (\Exception $e) { + Hm_Debug::add("ERRORExecuting task '{$task->getName()}': " . $e->getMessage()); + Hm_Msgs::add("ERRORExecuting task '{$task->getName()}': " . $e->getMessage()); + } } } } diff --git a/services/Hm_ConsoleKernal.php b/services/Hm_ConsoleKernal.php new file mode 100644 index 0000000000..383aa2db11 --- /dev/null +++ b/services/Hm_ConsoleKernal.php @@ -0,0 +1,29 @@ +scheduler = $scheduler; + + } + + /** + * Define the application's command schedule. + */ + public function schedule() + { + // Register tasks with the scheduler + $this->scheduler->command('check:mail') + ->everyMinute(); + // ->onOneServer() + // ->withoutOverlapping(10); + } +} diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 24dbe07500..0502a7b8ed 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,6 +1,7 @@ set('config', $config); +/* setup a session handler, but don't actually start a session yet */ +$session_config = new Hm_Session_Setup($config); +$session = $session_config->setup_session(); +// list($session, $request) = session_init(); +$containerBuilder->set('session', $session); + Hm_Container::bind(); +// Prepare Kernel instance parameters +$queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider'); +$queueServiceProvider->register($config, $session); + +// Create a new Kernel instance +$kernel = (new Hm_ConsoleKernal($containerBuilder->get('scheduler')))->schedule(); + return [$containerBuilder, $config]; diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/Hm_SchedulerServiceProvider.php index 33cbb192b9..d0c2013038 100644 --- a/services/Providers/Hm_SchedulerServiceProvider.php +++ b/services/Providers/Hm_SchedulerServiceProvider.php @@ -3,6 +3,7 @@ namespace Services\Providers; use Hm_Cache; +use Services\Core\Hm_Container; use Services\Core\Scheduling\Hm_CacheMutex; use Services\Core\Scheduling\Hm_Scheduler; @@ -16,6 +17,7 @@ class Hm_SchedulerServiceProvider */ public function register($config, $session) { + $containerBuilder = Hm_Container::getContainer(); // Initialize Hm_Cache $cache = new Hm_Cache($config, $session); @@ -23,12 +25,10 @@ public function register($config, $session) $mutex = new Hm_CacheMutex($cache); // Create the Scheduler instance, passing in the CacheMutex - $scheduler = new Hm_Scheduler($mutex); + $scheduler = new Hm_Scheduler($config); - // Register scheduled tasks here (optional setup) - // Example: - // $scheduler->command('check:mail')->everyMinute()->withoutOverlapping(10); - - return $scheduler; + $containerBuilder->set('scheduler', $scheduler); + $containerBuilder->set('mutex', $mutex); + $containerBuilder->set('cache', $cache); } } diff --git a/services/Traits/Hm_ScheduleFrequencyManager.php b/services/Traits/Hm_ScheduleFrequencyManager.php index 77f4cb4691..16b6e373e1 100644 --- a/services/Traits/Hm_ScheduleFrequencyManager.php +++ b/services/Traits/Hm_ScheduleFrequencyManager.php @@ -230,4 +230,19 @@ protected function spliceIntoPosition($position, $value) $segments[$position - 1] = $value; return $this->cron(implode(' ', $segments)); } + + /** + * Skip task execution based on a condition. + * + * @param callable $condition + * @return $this + */ + protected function skip(callable $condition) + { + if ($condition()) { + echo "Skipping task due to the condition.\n"; + return $this; + } + return $this; + } } diff --git a/services/readme.rd b/services/readme.rd index 7d723ad14d..f98c47b02d 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -38,6 +38,11 @@ $scheduler->command('check:mail')->everyMinute() $scheduler->command('backup:database')->dailyAt('02:00'); ``` +as we now have `Hm_SchedulerRunCommand.php` we can do: +``` +* * * * * php81 /path/to/cypht/project/console schedule:run +``` + ``` // Dispatch the event (new NewEmailProcessedEvent)->dispatch('user@example.com'); From 6eea27ae76dcf4bec66ca8d647798f2c9c6084b3 Mon Sep 17 00:00:00 2001 From: Yannick nsenga romeo Date: Fri, 8 Nov 2024 21:16:12 +0200 Subject: [PATCH 13/21] Refactor queue configuration and update schedule command handling --- .env.example | 2 +- config/queue.php | 13 +++++ lib/cache.php | 1 - .../Core/Commands/Hm_QueueWorkCommand.php | 2 +- .../Core/Commands/Hm_ScheduleRunCommand.php | 1 - services/Core/Hm_Container.php | 54 ++++++++++--------- services/Core/Scheduling/Hm_CacheMutex.php | 2 +- services/Core/Scheduling/Hm_ScheduledTask.php | 12 +++-- services/Core/Scheduling/Hm_Scheduler.php | 3 +- ...ConsoleKernal.php => Hm_ConsoleKernel.php} | 7 ++- services/Hm_bootstrap.php | 4 +- services/Jobs/Hm_ProcessNewEmail.php | 2 - .../Providers/Hm_QueueServiceProvider.php | 2 +- 13 files changed, 61 insertions(+), 44 deletions(-) create mode 100644 config/queue.php rename services/{Hm_ConsoleKernal.php => Hm_ConsoleKernel.php} (78%) diff --git a/.env.example b/.env.example index a2bb0b91f5..214b41e50d 100644 --- a/.env.example +++ b/.env.example @@ -212,7 +212,7 @@ WIN_CACERT_DIR= JS_EXCLUDE_DEPS= -QUEUE_CONNECTION=database +QUEUE_DRIVER=database AWS_ACCESS_KEY_ID='your-aws-access-key' AWS_SECRET_ACCESS_KEY='your-aws-secret-key' diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000000..b09c579e52 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,13 @@ + env('QUEUE_ENABLED', false), + + 'queue_driver' => env('QUEUE_DRIVER', 'database'), +]; \ No newline at end of file diff --git a/lib/cache.php b/lib/cache.php index 336ef8f78c..1f54e07162 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -603,7 +603,6 @@ protected function noop_get($key, $default) { * @return string */ protected function key_hash($key) { - dd($this->session->get('fingerprint')); return sprintf('hm_cache_%s', hash('sha256', (sprintf('%s%s%s%s', $key, SITE_ID, $this->session->get('fingerprint'), $this->session->get('username'))))); } diff --git a/services/Core/Commands/Hm_QueueWorkCommand.php b/services/Core/Commands/Hm_QueueWorkCommand.php index 711fce0e0e..c5ea0d8754 100644 --- a/services/Core/Commands/Hm_QueueWorkCommand.php +++ b/services/Core/Commands/Hm_QueueWorkCommand.php @@ -29,7 +29,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { - $connection = $input->getArgument('connection') ?: env('QUEUE_CONNECTION', 'database'); + $connection = $input->getArgument('connection') ?: env('QUEUE_DRIVER', 'database'); $queue = $input->getOption('queue') ?: 'default'; $output->writeln("Processing jobs from the [$queue] on connection [$connection]..."); diff --git a/services/Core/Commands/Hm_ScheduleRunCommand.php b/services/Core/Commands/Hm_ScheduleRunCommand.php index 02cbbe2572..cc294245f2 100644 --- a/services/Core/Commands/Hm_ScheduleRunCommand.php +++ b/services/Core/Commands/Hm_ScheduleRunCommand.php @@ -30,7 +30,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln("All due scheduled tasks have been executed."); // dd($scheduler); - dd('hello'); return Command::SUCCESS; } diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index ac6b520686..b50ef16f36 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -6,7 +6,7 @@ use Hm_Redis; use Hm_AmazonSQS; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Services\Providers\{ Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider }; +use Services\Providers\{Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider}; class Hm_Container { @@ -19,7 +19,7 @@ private function __clone() {} public static function setContainer(ContainerBuilder $containerBuilder): ContainerBuilder { if (self::$container === null) { - self::$container = $containerBuilder; + self::$container = $containerBuilder; } return self::$container; @@ -27,38 +27,42 @@ public static function setContainer(ContainerBuilder $containerBuilder): Contain public static function bind(): ContainerBuilder { - // Register Hm_DB - self::$container->set('db.connection', Hm_DB::connect(self::$container->get('config'))); - - self::$container->register('db', Hm_DB::class) - ->setShared(true); - - // Register Hm_Redis - $redis = new Hm_Redis(self::$container->get('config')); - $redis->connect(); - self::$container->set('redis.connection', $redis->getInstance()); - self::$container->register('redis', Hm_Redis::class)->setArgument(0, self::$container->get('config')) - - ->setShared(true); - - // Register Hm_AmazonSQS - self::$container->set('amazon.sqs.connection', Hm_AmazonSQS::connect(self::$container->get('config'))); - self::$container->register('amazon.sqs',Hm_AmazonSQS::class) - ->setShared(true); + $config = self::$container->get('config'); + + if ($config->get('queue_enabled')) { + + if ($config->get('queue_driver') === 'database') { + // Register Hm_DB + self::$container->set('db.connection', Hm_DB::connect(self::$container->get('config'))); + + self::$container->register('db', Hm_DB::class)->setShared(true); + } else if ($config->get('queue_driver') === 'redis') { + // Register Hm_Redis + $redis = new Hm_Redis($config); + $redis->connect(); + self::$container->set('redis.connection', $redis->getInstance()); + self::$container->register('redis', Hm_Redis::class)->setArgument(0, self::$container->get('config'))->setShared(true); + } else if ($config->get('queue_enabled') && $config->get('queue_driver') === 'sqs') { + // Register Hm_AmazonSQS + self::$container->set('amazon.sqs.connection', Hm_AmazonSQS::connect(self::$container->get('config'))); + self::$container->register('amazon.sqs', Hm_AmazonSQS::class) + ->setShared(true); + } + } // Register Hm_CommandServiceProvider self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) - ->setShared(true); + ->setShared(true); // Register Hm_QueueServiceProvider - self::$container->register('queue.ServiceProvider',Hm_QueueServiceProvider::class) - ->setShared(true); + self::$container->register('queue.ServiceProvider', Hm_QueueServiceProvider::class) + ->setShared(true); self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) - ->setShared(true); + ->setShared(true); self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) - ->setShared(true); + ->setShared(true); return self::$container; } diff --git a/services/Core/Scheduling/Hm_CacheMutex.php b/services/Core/Scheduling/Hm_CacheMutex.php index 95e6ed538a..8e3cca3275 100644 --- a/services/Core/Scheduling/Hm_CacheMutex.php +++ b/services/Core/Scheduling/Hm_CacheMutex.php @@ -18,7 +18,7 @@ public function __construct(Hm_Cache $cache) /** * Create a lock for the task. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @param int $expiresAt Time in seconds for the lock expiration * @return bool Returns true if the lock was created, false otherwise */ diff --git a/services/Core/Scheduling/Hm_ScheduledTask.php b/services/Core/Scheduling/Hm_ScheduledTask.php index 1d74323ca3..81dc0f9905 100644 --- a/services/Core/Scheduling/Hm_ScheduledTask.php +++ b/services/Core/Scheduling/Hm_ScheduledTask.php @@ -30,12 +30,15 @@ public function __construct(callable $callback, $name = '', $description = '', $ $this->expression = $expression; } + public function getName() + { + return $this->name; + } + public function isDue() - { - + { if ($this->isEnabled) { $this->calculateNextRunTime(); - dd($this->isEnabled); return $this->nextRunTime <= new \DateTime('now', new \DateTimeZone($this->timezone)); } return false; @@ -114,6 +117,7 @@ private function calculateNextRunTime() // Calculate next minute $nextMinute = $this->getNextFieldValue($next->format('i'), $minuteField, 0, 59); + if ($nextMinute !== null) { $next->setTime($next->format('H'), $nextMinute); @@ -153,7 +157,7 @@ private function calculateNextRunTime() $nextDayOfWeek = $this->getNextFieldValue($next->format('w'), $dayOfWeekField, 0, 6); if ($nextDayOfWeek !== null) { - while ($next->format('w') !== $nextDayOfWeek) { + while (intval($next->format('w')) !== $nextDayOfWeek) { $next->modify('+1 day'); } } else { diff --git a/services/Core/Scheduling/Hm_Scheduler.php b/services/Core/Scheduling/Hm_Scheduler.php index af2230e5a0..5ed6120c39 100644 --- a/services/Core/Scheduling/Hm_Scheduler.php +++ b/services/Core/Scheduling/Hm_Scheduler.php @@ -6,6 +6,7 @@ use Hm_Debug; use Hm_Msgs; use Hm_Session_Setup; +use Services\Core\Hm_Container; class Hm_Scheduler { @@ -19,7 +20,7 @@ public function __construct($config) public function command($command) { - $cache = new Hm_Cache($this->config, new Hm_Session_Setup($this->config)); + $cache = new Hm_Cache($this->config, Hm_Container::getContainer()->get('session')); $commandTask = new Hm_CommandTask($command, new Hm_CacheMutex($cache)); $this->tasks[] = $commandTask; return $commandTask; diff --git a/services/Hm_ConsoleKernal.php b/services/Hm_ConsoleKernel.php similarity index 78% rename from services/Hm_ConsoleKernal.php rename to services/Hm_ConsoleKernel.php index 383aa2db11..5e2f202277 100644 --- a/services/Hm_ConsoleKernal.php +++ b/services/Hm_ConsoleKernel.php @@ -5,7 +5,7 @@ use Services\Core\Scheduling\Hm_Scheduler; use Services\Core\Hm_Container; -class Hm_ConsoleKernal +class Hm_ConsoleKernel { protected $scheduler; @@ -22,8 +22,7 @@ public function schedule() { // Register tasks with the scheduler $this->scheduler->command('check:mail') - ->everyMinute(); - // ->onOneServer() - // ->withoutOverlapping(10); + ->everyMinute() + ->withoutOverlapping(); } } diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 0502a7b8ed..43b91b8923 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,7 +1,7 @@ register($config, $session); // Create a new Kernel instance -$kernel = (new Hm_ConsoleKernal($containerBuilder->get('scheduler')))->schedule(); +$kernel = (new Hm_ConsoleKernel($containerBuilder->get('scheduler')))->schedule(); return [$containerBuilder, $config]; diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/Hm_ProcessNewEmail.php index 96c94aec1c..21c7182b91 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/Hm_ProcessNewEmail.php @@ -13,8 +13,6 @@ class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue { use Hm_Dispatchable, Hm_InteractsWithQueue, Hm_Serializes; - public string $driver = 'sqs'; - public string $email; public function __construct(string $email) diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index 10f2722789..db44596c01 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -16,7 +16,7 @@ class Hm_QueueServiceProvider public function register() { - $queueConnection = getenv('QUEUE_CONNECTION') ?: 'database'; + $queueConnection = getenv('QUEUE_DRIVER') ?: 'database'; $containerBuilder = Hm_Container::getContainer(); $containerBuilder->register('queue.manager', Hm_QueueManager::class) From 8a254a9b7a437e267c7ca3b42a758551e8a65f2c Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Fri, 8 Nov 2024 05:50:00 +0300 Subject: [PATCH 14/21] Testing Scheduling --- console | 3 - lib/cache.php | 1 + .../Core/Commands/Hm_ScheduleRunCommand.php | 35 ++++++ services/Core/Hm_Container.php | 1 + services/Core/Scheduling/Hm_CacheMutex.php | 25 ++++- services/Core/Scheduling/Hm_CommandTask.php | 14 ++- services/Core/Scheduling/Hm_ScheduledTask.php | 102 ++++++++++-------- services/Core/Scheduling/Hm_Scheduler.php | 11 +- services/Hm_ConsoleKernal.php | 29 +++++ services/Hm_bootstrap.php | 14 +++ .../Providers/Hm_SchedulerServiceProvider.php | 12 +-- .../Traits/Hm_ScheduleFrequencyManager.php | 15 +++ services/readme.rd | 5 + 13 files changed, 207 insertions(+), 60 deletions(-) create mode 100644 services/Core/Commands/Hm_ScheduleRunCommand.php create mode 100644 services/Hm_ConsoleKernal.php diff --git a/console b/console index 7b57a69953..31c37b1d0c 100644 --- a/console +++ b/console @@ -16,9 +16,6 @@ $commandServiceProvider->register($application); $queueServiceProvider = $containerBuilder->get('queue.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); $queueServiceProvider->register(); -$queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); -$queueServiceProvider->register($config, new Hm_Session_Setup($config)); - $eventServiceProvider = $containerBuilder->get('event.ServiceProvider', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); // $eventServiceProvider->setContainer($containerBuilder); $eventServiceProvider->register(); diff --git a/lib/cache.php b/lib/cache.php index 1f54e07162..336ef8f78c 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -603,6 +603,7 @@ protected function noop_get($key, $default) { * @return string */ protected function key_hash($key) { + dd($this->session->get('fingerprint')); return sprintf('hm_cache_%s', hash('sha256', (sprintf('%s%s%s%s', $key, SITE_ID, $this->session->get('fingerprint'), $this->session->get('username'))))); } diff --git a/services/Core/Commands/Hm_ScheduleRunCommand.php b/services/Core/Commands/Hm_ScheduleRunCommand.php new file mode 100644 index 0000000000..1cdf3b20c3 --- /dev/null +++ b/services/Core/Commands/Hm_ScheduleRunCommand.php @@ -0,0 +1,35 @@ +setDescription('Run all scheduled tasks that are due') + // Optionally, you can add other configuration or arguments here + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Get the scheduler instance from the container + $scheduler = Hm_Container::getContainer()->get('scheduler'); + // Run the tasks that are due + $scheduler->run(); + + $output->writeln("All due scheduled tasks have been executed."); + + return Command::SUCCESS; + } +} diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index 8762d38b34..ac6b520686 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -56,6 +56,7 @@ public static function bind(): ContainerBuilder self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) ->setShared(true); + self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) ->setShared(true); diff --git a/services/Core/Scheduling/Hm_CacheMutex.php b/services/Core/Scheduling/Hm_CacheMutex.php index bcf48c3fec..95e6ed538a 100644 --- a/services/Core/Scheduling/Hm_CacheMutex.php +++ b/services/Core/Scheduling/Hm_CacheMutex.php @@ -8,6 +8,7 @@ class Hm_CacheMutex implements Mutex { private $cache; + private $expiresAt; public function __construct(Hm_Cache $cache) { @@ -36,21 +37,20 @@ public function create($task, $expiresAt) /** * Check if a lock exists for the task. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return bool */ public function exists($task) { $key = $this->getMutexKey($task); $lockExpiry = $this->cache->get($key, false); - return $lockExpiry && $lockExpiry > time(); } /** * Release the lock for the task. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return void */ public function release($task) @@ -62,11 +62,26 @@ public function release($task) /** * Generate a unique key for the mutex. * - * @param Task $task The task instance + * @param Hm_CommandTask $task The task instance * @return string */ private function getMutexKey($task) { - return 'mutex_' . hash('sha256', $task->name); + return 'mutex_' . hash('sha256', get_class($task) . $task->name . json_encode($task->command)); + // return 'mutex_' . hash('sha256', $task->name); + } + + /** + * Refresh the mutex lock expiry if it's still active. + * + * @param Task $task The task instance + * @return void + */ + public function refresh($task) + { + $key = $this->getMutexKey($task); + if ($this->cache->get($key, false)) { + $this->cache->set($key, time() + $this->expiresAt, $this->expiresAt); + } } } diff --git a/services/Core/Scheduling/Hm_CommandTask.php b/services/Core/Scheduling/Hm_CommandTask.php index ea1aa530e2..49f1728446 100644 --- a/services/Core/Scheduling/Hm_CommandTask.php +++ b/services/Core/Scheduling/Hm_CommandTask.php @@ -4,21 +4,29 @@ class Hm_CommandTask extends Hm_ScheduledTask { - private $command; + public $command; private $onOneServer = false; private $mutex; private $expiresAt = null; private $withoutOverlapping = false; - public function __construct($command, Hm_CacheMutex $mutex) + /** + * The name of the command task. + * + * @var string + */ + public $name; + + public function __construct($command, Hm_CacheMutex $mutex, $name = null) { + $this->name = $name ?: $command; $fullCommand = "php console " . $command; parent::__construct(function () use ($fullCommand) { echo "Executing Command: $fullCommand\n"; $output = shell_exec($fullCommand); echo $output; }, $command); - + $this->command = $fullCommand; $this->mutex = $mutex; } diff --git a/services/Core/Scheduling/Hm_ScheduledTask.php b/services/Core/Scheduling/Hm_ScheduledTask.php index dbdcd2970f..1d74323ca3 100644 --- a/services/Core/Scheduling/Hm_ScheduledTask.php +++ b/services/Core/Scheduling/Hm_ScheduledTask.php @@ -15,8 +15,10 @@ class Hm_ScheduledTask private $description; private $tags = []; private $lastRunTime; - private $timezone; - private $expression; + + private $maxRetries = 3; + private $retryInterval = 60; // Interval in seconds between retries + private $retryCount = 0; // Track the number of retries attempted public function __construct(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC', $expression = '* * * * *') { @@ -26,26 +28,19 @@ public function __construct(callable $callback, $name = '', $description = '', $ $this->tags = $tags; $this->timezone = $timezone; $this->expression = $expression; - $this->nextRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); - } - - public function enable() - { - $this->isEnabled = true; } - public function disable() - { - $this->isEnabled = false; - } - - // Check if the task is due public function isDue() { - return $this->isEnabled && new \DateTime('now', new \DateTimeZone($this->timezone)) >= $this->nextRunTime; + + if ($this->isEnabled) { + $this->calculateNextRunTime(); + dd($this->isEnabled); + return $this->nextRunTime <= new \DateTime('now', new \DateTimeZone($this->timezone)); + } + return false; } - // Execute the task public function run() { if (!$this->isDue()) { @@ -53,28 +48,51 @@ public function run() } try { + echo "Task '{$this->name}' is due and will be run.\n"; + + // Run the task callback call_user_func($this->callback); $this->lastRunTime = new \DateTime('now', new \DateTimeZone($this->timezone)); + + // Reset retry count after a successful run + $this->retryCount = 0; } catch (\Exception $e) { + echo "Error running task {$this->name}: " . $e->getMessage() . "\n"; + // Log the error message error_log("Error running task {$this->name}: " . $e->getMessage()); + + // Handle retries + $this->handleRetry($e); } $this->scheduleNextRun(); } - // Schedule the next run time based on the cron expression - public function scheduleNextRun() + private function handleRetry(\Exception $e) { - $this->nextRunTime = $this->calculateNextRunTime(); + if ($this->retryCount < $this->maxRetries) { + $this->retryCount++; + $retryTime = $this->retryInterval * $this->retryCount; + + // Log the retry attempt + error_log("Retry attempt {$this->retryCount} for task {$this->name}, will retry in {$retryTime} seconds."); + sleep($retryTime); + + // Try again + $this->run(); + } else { + // Log that we've exhausted all retries + error_log("Max retries reached for task {$this->name}. Task will not be retried."); + } } - public function getNextRunTime() + private function scheduleNextRun() { - return $this->nextRunTime; + // You can schedule the next run based on cron expression or your custom logic + $this->nextRunTime = $this->calculateNextRunTime(); } - // Calculate the next run time based on the cron expression private function calculateNextRunTime() { // Ensure the cron expression is valid @@ -97,12 +115,13 @@ private function calculateNextRunTime() // Calculate next minute $nextMinute = $this->getNextFieldValue($next->format('i'), $minuteField, 0, 59); if ($nextMinute !== null) { + $next->setTime($next->format('H'), $nextMinute); } else { $next->modify('+1 hour'); $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); } - + // Calculate next hour $nextHour = $this->getNextFieldValue($next->format('H'), $hourField, 0, 23); if ($nextHour !== null) { @@ -132,6 +151,7 @@ private function calculateNextRunTime() // Calculate next day of the week $nextDayOfWeek = $this->getNextFieldValue($next->format('w'), $dayOfWeekField, 0, 6); + if ($nextDayOfWeek !== null) { while ($next->format('w') !== $nextDayOfWeek) { $next->modify('+1 day'); @@ -143,16 +163,19 @@ private function calculateNextRunTime() return $next; } - // Get the next valid value for a cron field private function getNextFieldValue($currentValue, $field, $min, $max) { - // Handle a field in cron syntax (minute, hour, day, month, weekday) $values = []; - + if ($field === '*') { - return $min; + // If field is '*', we want the next value in sequence, wrapping if needed + if ($currentValue < $max) { + return $currentValue + 1; // Move to next minute, hour, etc. + } else { + return $min; // Wrap around to the minimum (e.g., new hour if on minute field) + } } - + foreach (explode(',', $field) as $part) { if (strpos($part, '/') !== false) { // Handle step values, e.g., */2 @@ -166,34 +189,29 @@ private function getNextFieldValue($currentValue, $field, $min, $max) // Handle ranges, e.g., 1-5 list($rangeStart, $rangeEnd) = explode('-', $part); for ($i = $rangeStart; $i <= $rangeEnd; $i++) { - $values[] = $i; + $values[] = (int)$i; } } else { // Single values $values[] = (int)$part; } } - - // Filter and sort unique values + + // Filter, sort, and keep unique values $values = array_unique(array_filter($values, function ($value) use ($min, $max) { return $value >= $min && $value <= $max; })); - sort($values); - - // Find the next valid value + + // Find the next valid value that is greater than the current value foreach ($values as $value) { if ($value > $currentValue) { return $value; } } - - return null; // If no valid next value is found + + // If no valid next value is found, wrap around to the first value in the list + return !empty($values) ? $values[0] : $min; } - - // Getter methods for the task properties - public function getName() { return $this->name; } - public function getDescription() { return $this->description; } - public function getTags() { return $this->tags; } - public function getTimezone() { return $this->timezone; } + } diff --git a/services/Core/Scheduling/Hm_Scheduler.php b/services/Core/Scheduling/Hm_Scheduler.php index fa7d25a9fa..af2230e5a0 100644 --- a/services/Core/Scheduling/Hm_Scheduler.php +++ b/services/Core/Scheduling/Hm_Scheduler.php @@ -3,6 +3,8 @@ namespace Services\Core\Scheduling; use Hm_Cache; +use Hm_Debug; +use Hm_Msgs; use Hm_Session_Setup; class Hm_Scheduler @@ -50,7 +52,14 @@ public function run() { foreach ($this->tasks as $task) { if ($task->isDue()) { - $task->run(); + try { + $task->run(); + Hm_Debug::add("Task '{$task->getName()}' executed successfully."); + Hm_Msgs::add("Task '{$task->getName()}' executed successfully."); + } catch (\Exception $e) { + Hm_Debug::add("ERRORExecuting task '{$task->getName()}': " . $e->getMessage()); + Hm_Msgs::add("ERRORExecuting task '{$task->getName()}': " . $e->getMessage()); + } } } } diff --git a/services/Hm_ConsoleKernal.php b/services/Hm_ConsoleKernal.php new file mode 100644 index 0000000000..383aa2db11 --- /dev/null +++ b/services/Hm_ConsoleKernal.php @@ -0,0 +1,29 @@ +scheduler = $scheduler; + + } + + /** + * Define the application's command schedule. + */ + public function schedule() + { + // Register tasks with the scheduler + $this->scheduler->command('check:mail') + ->everyMinute(); + // ->onOneServer() + // ->withoutOverlapping(10); + } +} diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 24dbe07500..0502a7b8ed 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,6 +1,7 @@ set('config', $config); +/* setup a session handler, but don't actually start a session yet */ +$session_config = new Hm_Session_Setup($config); +$session = $session_config->setup_session(); +// list($session, $request) = session_init(); +$containerBuilder->set('session', $session); + Hm_Container::bind(); +// Prepare Kernel instance parameters +$queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider'); +$queueServiceProvider->register($config, $session); + +// Create a new Kernel instance +$kernel = (new Hm_ConsoleKernal($containerBuilder->get('scheduler')))->schedule(); + return [$containerBuilder, $config]; diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/Hm_SchedulerServiceProvider.php index 33cbb192b9..d0c2013038 100644 --- a/services/Providers/Hm_SchedulerServiceProvider.php +++ b/services/Providers/Hm_SchedulerServiceProvider.php @@ -3,6 +3,7 @@ namespace Services\Providers; use Hm_Cache; +use Services\Core\Hm_Container; use Services\Core\Scheduling\Hm_CacheMutex; use Services\Core\Scheduling\Hm_Scheduler; @@ -16,6 +17,7 @@ class Hm_SchedulerServiceProvider */ public function register($config, $session) { + $containerBuilder = Hm_Container::getContainer(); // Initialize Hm_Cache $cache = new Hm_Cache($config, $session); @@ -23,12 +25,10 @@ public function register($config, $session) $mutex = new Hm_CacheMutex($cache); // Create the Scheduler instance, passing in the CacheMutex - $scheduler = new Hm_Scheduler($mutex); + $scheduler = new Hm_Scheduler($config); - // Register scheduled tasks here (optional setup) - // Example: - // $scheduler->command('check:mail')->everyMinute()->withoutOverlapping(10); - - return $scheduler; + $containerBuilder->set('scheduler', $scheduler); + $containerBuilder->set('mutex', $mutex); + $containerBuilder->set('cache', $cache); } } diff --git a/services/Traits/Hm_ScheduleFrequencyManager.php b/services/Traits/Hm_ScheduleFrequencyManager.php index 77f4cb4691..16b6e373e1 100644 --- a/services/Traits/Hm_ScheduleFrequencyManager.php +++ b/services/Traits/Hm_ScheduleFrequencyManager.php @@ -230,4 +230,19 @@ protected function spliceIntoPosition($position, $value) $segments[$position - 1] = $value; return $this->cron(implode(' ', $segments)); } + + /** + * Skip task execution based on a condition. + * + * @param callable $condition + * @return $this + */ + protected function skip(callable $condition) + { + if ($condition()) { + echo "Skipping task due to the condition.\n"; + return $this; + } + return $this; + } } diff --git a/services/readme.rd b/services/readme.rd index 7d723ad14d..f98c47b02d 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -38,6 +38,11 @@ $scheduler->command('check:mail')->everyMinute() $scheduler->command('backup:database')->dailyAt('02:00'); ``` +as we now have `Hm_SchedulerRunCommand.php` we can do: +``` +* * * * * php81 /path/to/cypht/project/console schedule:run +``` + ``` // Dispatch the event (new NewEmailProcessedEvent)->dispatch('user@example.com'); From 1b03512d371b4fec1360aec19aa7f4654a589c37 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Sat, 9 Nov 2024 00:41:14 +0300 Subject: [PATCH 15/21] Scheduling Comlete, next step Notification with channels: telegram,slack,twilio,nexmo,broadcast --- .env.example | 1 + lib/cache.php | 1 - .../Core/Commands/Hm_QueueWorkCommand.php | 14 + .../Core/Commands/Hm_ScheduleRunCommand.php | 22 +- .../Core/Commands/Hm_SchedulerWorkCommand.php | 100 +++++++ services/Core/Hm_Container.php | 22 +- services/Core/Jobs/Hm_BaseJob.php | 33 ++- services/Core/Queue/Hm_JobDispatcher.php | 1 + services/Core/Queue/Hm_QueueManager.php | 1 + services/Core/Queue/Hm_QueueWorker.php | 1 - services/Core/Scheduling/Hm_CacheMutex.php | 4 +- services/Core/Scheduling/Hm_ScheduledTask.php | 245 +++++++++++++----- services/Core/Scheduling/Hm_Scheduler.php | 24 +- services/Events/Hm_NewEmailProcessedEvent.php | 2 +- services/Hm_ConsoleKernal.php | 29 --- services/Hm_ConsoleKernel.php | 12 +- services/Hm_bootstrap.php | 2 +- services/Jobs/Hm_ProcessNewEmail.php | 4 + .../Providers/Hm_EventServiceProvider.php | 10 + .../Providers/Hm_QueueServiceProvider.php | 15 +- .../Providers/Hm_SchedulerServiceProvider.php | 6 +- services/readme.rd | 25 +- 22 files changed, 441 insertions(+), 133 deletions(-) create mode 100644 services/Core/Commands/Hm_SchedulerWorkCommand.php delete mode 100644 services/Hm_ConsoleKernal.php diff --git a/.env.example b/.env.example index 214b41e50d..b39df92243 100644 --- a/.env.example +++ b/.env.example @@ -212,6 +212,7 @@ WIN_CACERT_DIR= JS_EXCLUDE_DEPS= +QUEUE_ENABLED=false QUEUE_DRIVER=database AWS_ACCESS_KEY_ID='your-aws-access-key' diff --git a/lib/cache.php b/lib/cache.php index 336ef8f78c..1f54e07162 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -603,7 +603,6 @@ protected function noop_get($key, $default) { * @return string */ protected function key_hash($key) { - dd($this->session->get('fingerprint')); return sprintf('hm_cache_%s', hash('sha256', (sprintf('%s%s%s%s', $key, SITE_ID, $this->session->get('fingerprint'), $this->session->get('username'))))); } diff --git a/services/Core/Commands/Hm_QueueWorkCommand.php b/services/Core/Commands/Hm_QueueWorkCommand.php index c5ea0d8754..e6fce0d1f2 100644 --- a/services/Core/Commands/Hm_QueueWorkCommand.php +++ b/services/Core/Commands/Hm_QueueWorkCommand.php @@ -11,10 +11,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +/** + * Class Hm_QueueWorkCommand + * @package Services\Core\Commands + */ class Hm_QueueWorkCommand extends Hm_BaseCommand { protected static $defaultName = 'queue:work'; + /** + * Configure the command. + */ protected function configure() { $this @@ -27,6 +34,13 @@ protected function configure() ->addOption('tries', null, InputOption::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 1); } + /** + * Execute the console command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command exit code. + */ protected function execute(InputInterface $input, OutputInterface $output): int { $connection = $input->getArgument('connection') ?: env('QUEUE_DRIVER', 'database'); diff --git a/services/Core/Commands/Hm_ScheduleRunCommand.php b/services/Core/Commands/Hm_ScheduleRunCommand.php index 1cdf3b20c3..3152c8a162 100644 --- a/services/Core/Commands/Hm_ScheduleRunCommand.php +++ b/services/Core/Commands/Hm_ScheduleRunCommand.php @@ -8,11 +8,22 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +/** + * Class Hm_ScheduleRunCommand + * @package Services\Core\Commands + */ class Hm_ScheduleRunCommand extends Hm_BaseCommand { - // Default name for the command + /** + * The name of the command. + * + * @var string + */ protected static $defaultName = 'schedule:run'; + /** + * Configure the command. + */ protected function configure() { $this @@ -21,11 +32,16 @@ protected function configure() ; } + /** + * Execute the console command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command exit code. + */ protected function execute(InputInterface $input, OutputInterface $output): int { - // Get the scheduler instance from the container $scheduler = Hm_Container::getContainer()->get('scheduler'); - // Run the tasks that are due $scheduler->run(); $output->writeln("All due scheduled tasks have been executed."); diff --git a/services/Core/Commands/Hm_SchedulerWorkCommand.php b/services/Core/Commands/Hm_SchedulerWorkCommand.php new file mode 100644 index 0000000000..52a4ec3a22 --- /dev/null +++ b/services/Core/Commands/Hm_SchedulerWorkCommand.php @@ -0,0 +1,100 @@ +setDescription('Continuously run the scheduler to execute due tasks') + ->setHelp('This command runs the scheduler in a loop to continuously check and execute scheduled tasks.'); + } + + /** + * Execute the console command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command exit code. + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $scheduler = Hm_Container::getContainer()->get('scheduler'); + $output->writeln("Scheduler started. Press Ctrl+C to stop."); + + if (function_exists('pcntl_signal')) { + pcntl_signal(SIGINT, function () { + $this->shouldStop = true; + }); + } + + while (!$this->shouldStop) { + foreach ($scheduler->getTasks() as $task) { + $taskId = spl_object_hash($task); + $currentTime = new \DateTime('now', new \DateTimeZone($task->getTimezone())); + + $lastRunTime = isset($this->lastRunTimes[$taskId]) ? $this->lastRunTimes[$taskId] : $currentTime; + + $this->lastRunTimes[$taskId] = $currentTime; + + if ($task->isDue() && $currentTime > $lastRunTime) { + $output->writeln("Running task: {$task->getName()} at " . $currentTime->format('Y-m-d H:i:s')); + $task->run(); + $output->writeln("Task: {$task->getName()} added to queue"); + } + } + + // Wait one minute before the next loop iteration + sleep(60); + + // Dispatch any pending signals + if (function_exists('pcntl_signal_dispatch')) { + pcntl_signal_dispatch(); + } + } + + $output->writeln("Scheduler stopped gracefully."); + + return Command::SUCCESS; + } + + /** + * Stops the scheduler loop gracefully. + */ + public function stop() + { + $this->shouldStop = true; + } +} diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index b50ef16f36..3805f348b0 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -8,6 +8,10 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Services\Providers\{Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider}; +/** + * Class Hm_Container + * @package Services\Core + */ class Hm_Container { private static $container = null; @@ -16,6 +20,12 @@ class Hm_Container private function __construct() {} private function __clone() {} + /** + * Set the container + * + * @param ContainerBuilder $containerBuilder + * @return ContainerBuilder + */ public static function setContainer(ContainerBuilder $containerBuilder): ContainerBuilder { if (self::$container === null) { @@ -25,12 +35,17 @@ public static function setContainer(ContainerBuilder $containerBuilder): Contain return self::$container; } + /** + * Bind the container + * + * @return ContainerBuilder + */ public static function bind(): ContainerBuilder { $config = self::$container->get('config'); if ($config->get('queue_enabled')) { - + if ($config->get('queue_driver') === 'database') { // Register Hm_DB self::$container->set('db.connection', Hm_DB::connect(self::$container->get('config'))); @@ -67,6 +82,11 @@ public static function bind(): ContainerBuilder return self::$container; } + /** + * Get the container + * + * @return ContainerBuilder + */ public static function getContainer(): ContainerBuilder { return self::$container; diff --git a/services/Core/Jobs/Hm_BaseJob.php b/services/Core/Jobs/Hm_BaseJob.php index 9cd7a88e42..6e10737542 100644 --- a/services/Core/Jobs/Hm_BaseJob.php +++ b/services/Core/Jobs/Hm_BaseJob.php @@ -6,7 +6,7 @@ abstract class Hm_BaseJob implements Hm_Job { - public string $driver = 'database'; + public string $driver = ''; public int $tries = 3; protected int $attempts = 0; @@ -14,26 +14,53 @@ public function __construct(protected array $data = []) { $this->data = $data; } + /** + * Execute the job. + * + * @return void + */ public function handle(): void {} + /** + * Handle a job failure. + * + * @return void + */ public function failed(): void {} + /** + * Get the driver name for the job. + * + * @return int + */ public function getDriver(): string { return $this->driver; } + /** + * Get the number of times the job has been attempted. + * + * @return int + */ public function getAttempts(): int { return $this->attempts; } - // Method to increment the attempt count + /** + * Method to increment the attempt count + * + */ public function incrementAttempts(): void { $this->attempts++; } - // Check if the job has exceeded the max attempts + /** + * Determine if the job has exceeded the maximum number of attempts. + * + * @return bool + */ public function hasExceededMaxAttempts(): bool { return $this->attempts >= $this->tries; diff --git a/services/Core/Queue/Hm_JobDispatcher.php b/services/Core/Queue/Hm_JobDispatcher.php index 64084269b6..30d929c14a 100644 --- a/services/Core/Queue/Hm_JobDispatcher.php +++ b/services/Core/Queue/Hm_JobDispatcher.php @@ -23,6 +23,7 @@ class Hm_JobDispatcher static public function dispatch(Hm_BaseJob $job): void { if (is_subclass_of($job, Hm_ShouldQueue::class)) { $driver = $job->driver; + dd($driver); $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); if ($queueDriver) { $queueDriver->push($job); diff --git a/services/Core/Queue/Hm_QueueManager.php b/services/Core/Queue/Hm_QueueManager.php index 6afb42692b..9027f798fd 100644 --- a/services/Core/Queue/Hm_QueueManager.php +++ b/services/Core/Queue/Hm_QueueManager.php @@ -23,6 +23,7 @@ public function addDriver(string $name, Hm_ShouldQueue $driver): void public function getDriver(string $name): Hm_ShouldQueue { + dump("Getting driver $name"); return $this->drivers[$name]; } } diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/Hm_QueueWorker.php index 011c4ba5ce..e70cd0181b 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/Hm_QueueWorker.php @@ -30,7 +30,6 @@ public function work(): void { while ($job = $this->queue->pop()) { try { - // dd($job); $this->queue->process($job); } catch (\Exception $e) { // $job->failed(); diff --git a/services/Core/Scheduling/Hm_CacheMutex.php b/services/Core/Scheduling/Hm_CacheMutex.php index 8e3cca3275..cb5b1ad770 100644 --- a/services/Core/Scheduling/Hm_CacheMutex.php +++ b/services/Core/Scheduling/Hm_CacheMutex.php @@ -67,8 +67,8 @@ public function release($task) */ private function getMutexKey($task) { - return 'mutex_' . hash('sha256', get_class($task) . $task->name . json_encode($task->command)); - // return 'mutex_' . hash('sha256', $task->name); + // return 'mutex_' . hash('sha256', get_class($task) . $task->name . json_encode($task->command)); + return 'mutex_' . hash('sha256', $task->name); } /** diff --git a/services/Core/Scheduling/Hm_ScheduledTask.php b/services/Core/Scheduling/Hm_ScheduledTask.php index 81dc0f9905..de8258e548 100644 --- a/services/Core/Scheduling/Hm_ScheduledTask.php +++ b/services/Core/Scheduling/Hm_ScheduledTask.php @@ -8,18 +8,76 @@ class Hm_ScheduledTask { use Hm_ScheduleFrequencyManager; + /** + * The caallback to run + * + */ private $callback; + /** + * The next run time + * + * @var string + */ private $nextRunTime; + /** + * check if the task is enabled + * + * @var boolean + */ private $isEnabled = true; + /** + * The task name + * + * @var string + */ private $name; + /** + * The task name + * + * @var string + */ private $description; + /** + * The task name + * + * @var array + */ private $tags = []; + /** + * The last run time + * + */ private $lastRunTime; - - private $maxRetries = 3; - private $retryInterval = 60; // Interval in seconds between retries - private $retryCount = 0; // Track the number of retries attempted - + /** + * The maximum number of retries + * + * @var int + */ + private int $maxRetries = 3; + + /** + * Interval in seconds between retries + * + * @var int + */ + private int $retryInterval = 60; + /** + * Track the number of retries attempted + * + * @var int + */ + private int $retryCount = 0; + + /** + * Create a new scheduled task + * + * @param callable $callback + * @param string $name + * @param string $description + * @param array $tags + * @param string $timezone + * @param string $expression + */ public function __construct(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC', $expression = '* * * * *') { $this->callback = $callback; @@ -30,11 +88,21 @@ public function __construct(callable $callback, $name = '', $description = '', $ $this->expression = $expression; } + /** + * Get the task name + * + * @return string + */ public function getName() { return $this->name; } + /** + * Check if the task is due to run + * + * @return bool + */ public function isDue() { if ($this->isEnabled) { @@ -44,6 +112,19 @@ public function isDue() return false; } + /** + * Get the next run time + * + * @return \DateTime + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Run the scheduled task + */ public function run() { if (!$this->isDue()) { @@ -72,101 +153,121 @@ public function run() $this->scheduleNextRun(); } - private function handleRetry(\Exception $e) - { - if ($this->retryCount < $this->maxRetries) { - $this->retryCount++; - $retryTime = $this->retryInterval * $this->retryCount; - - // Log the retry attempt - error_log("Retry attempt {$this->retryCount} for task {$this->name}, will retry in {$retryTime} seconds."); - sleep($retryTime); - - // Try again - $this->run(); - } else { - // Log that we've exhausted all retries - error_log("Max retries reached for task {$this->name}. Task will not be retried."); - } - } - - private function scheduleNextRun() - { - // You can schedule the next run based on cron expression or your custom logic - $this->nextRunTime = $this->calculateNextRunTime(); - } - - private function calculateNextRunTime() + /** + * Calculate the next run time based on the cron expression + * + * @return \DateTime + */ + public function calculateNextRunTime() { // Ensure the cron expression is valid if (empty($this->expression)) { throw new \InvalidArgumentException("Cron expression must be set."); } - + // Split the cron expression into parts $parts = preg_split('/\s+/', $this->expression); if (count($parts) !== 5) { throw new \InvalidArgumentException("Invalid cron expression: {$this->expression}"); } - - // Extract the cron fields + + // Extract cron fields list($minuteField, $hourField, $dayOfMonthField, $monthField, $dayOfWeekField) = $parts; - + + // Initialize current time and timezone $now = new \DateTime('now', new \DateTimeZone($this->timezone)); $next = clone $now; - - // Calculate next minute - $nextMinute = $this->getNextFieldValue($next->format('i'), $minuteField, 0, 59); - - if ($nextMinute !== null) { - - $next->setTime($next->format('H'), $nextMinute); - } else { + + // Only increment the minute by default, assuming the task is set to run every minute + if ($minuteField === '*' && $hourField === '*' && $dayOfMonthField === '*' && $monthField === '*' && $dayOfWeekField === '*') { + $next->modify('+1 minute'); + return $next; + } + + // Calculate the next minute + $nextMinute = $this->getNextFieldValue((int)$next->format('i'), $minuteField, 0, 59); + if ($nextMinute < (int)$next->format('i')) { + // Increment the hour if the calculated minute is in the past for the current hour $next->modify('+1 hour'); - $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); } - - // Calculate next hour - $nextHour = $this->getNextFieldValue($next->format('H'), $hourField, 0, 23); - if ($nextHour !== null) { - $next->setTime($nextHour, $next->format('i')); - } else { + $next->setTime((int)$next->format('H'), $nextMinute); + + // Calculate the next hour + $nextHour = $this->getNextFieldValue((int)$next->format('H'), $hourField, 0, 23); + if ($nextHour < (int)$next->format('H')) { + // Increment the day if the calculated hour is in the past for the current day $next->modify('+1 day'); - $next->setTime(0, $this->getNextFieldValue(0, $minuteField, 0, 59)); } - - // Calculate next day of the month - $nextDay = $this->getNextFieldValue($next->format('d'), $dayOfMonthField, 1, 31); - if ($nextDay !== null) { - $next->setDate($next->format('Y'), $next->format('m'), $nextDay); - } else { + $next->setTime($nextHour, (int)$next->format('i')); + + // Calculate the next day of the month + $nextDay = $this->getNextFieldValue((int)$next->format('d'), $dayOfMonthField, 1, 31); + if ($nextDay < (int)$next->format('d')) { + // Increment the month if the calculated day is in the past for the current month $next->modify('+1 month'); - $next->setDate($next->format('Y'), $next->format('m'), $this->getNextFieldValue(1, $dayOfMonthField, 1, 31)); } - - // Calculate next month - $nextMonth = $this->getNextFieldValue($next->format('n'), $monthField, 1, 12); - if ($nextMonth !== null) { - $next->setDate($next->format('Y'), $nextMonth, $next->format('d')); - } else { + $next->setDate((int)$next->format('Y'), (int)$next->format('m'), $nextDay); + + // Calculate the next month + $nextMonth = $this->getNextFieldValue((int)$next->format('n'), $monthField, 1, 12); + if ($nextMonth < (int)$next->format('n')) { + // Increment the year if the calculated month is in the past for the current year $next->modify('+1 year'); - $next->setDate($next->format('Y'), $this->getNextFieldValue(1, $monthField, 1, 12), $next->format('d')); } + $next->setDate((int)$next->format('Y'), $nextMonth, (int)$next->format('d')); + + // Calculate the next day of the week if specified + if ($dayOfWeekField !== '*') { + $nextDayOfWeek = $this->getNextFieldValue((int)$next->format('w'), $dayOfWeekField, 0, 6); + while ((int)$next->format('w') !== $nextDayOfWeek) { + $next->modify('+1 day'); // Move forward by one day until it matches the specified day of the week + } + } + + return $next; + } + + /** + * Handle retries for the task + * + * @param \Exception $e + */ + private function handleRetry(\Exception $e) + { + if ($this->retryCount < $this->maxRetries) { + $this->retryCount++; + $retryTime = $this->retryInterval * $this->retryCount; - // Calculate next day of the week - $nextDayOfWeek = $this->getNextFieldValue($next->format('w'), $dayOfWeekField, 0, 6); + // Log the retry attempt + error_log("Retry attempt {$this->retryCount} for task {$this->name}, will retry in {$retryTime} seconds."); + sleep($retryTime); - if ($nextDayOfWeek !== null) { - while (intval($next->format('w')) !== $nextDayOfWeek) { - $next->modify('+1 day'); - } + // Try again + $this->run(); } else { - $next->modify('+1 week'); + // Log that we've exhausted all retries + error_log("Max retries reached for task {$this->name}. Task will not be retried."); } + } - return $next; + /** + * Schedule the next run time for the task + */ + private function scheduleNextRun() + { + // You can schedule the next run based on cron expression or your custom logic + $this->nextRunTime = $this->calculateNextRunTime(); } + /** + * Get the next valid field value + * + * @param int $currentValue + * @param string $field + * @param int $min + * @param int $max + * @return int + */ private function getNextFieldValue($currentValue, $field, $min, $max) { $values = []; diff --git a/services/Core/Scheduling/Hm_Scheduler.php b/services/Core/Scheduling/Hm_Scheduler.php index 5ed6120c39..d3292880bc 100644 --- a/services/Core/Scheduling/Hm_Scheduler.php +++ b/services/Core/Scheduling/Hm_Scheduler.php @@ -2,10 +2,9 @@ namespace Services\Core\Scheduling; +use Hm_Msgs; use Hm_Cache; use Hm_Debug; -use Hm_Msgs; -use Hm_Session_Setup; use Services\Core\Hm_Container; class Hm_Scheduler @@ -13,11 +12,21 @@ class Hm_Scheduler protected $tasks = []; protected $config; + /** + * Hm_Scheduler constructor. + * @param $config + */ public function __construct($config) { $this->config = $config; } + /** + * Register a new command task with the given configuration. + * + * @param string $command + * @return Hm_CommandTask + */ public function command($command) { $cache = new Hm_Cache($this->config, Hm_Container::getContainer()->get('session')); @@ -54,6 +63,7 @@ public function run() foreach ($this->tasks as $task) { if ($task->isDue()) { try { + echo "Running Task '{$task->getName()}'\n"; $task->run(); Hm_Debug::add("Task '{$task->getName()}' executed successfully."); Hm_Msgs::add("Task '{$task->getName()}' executed successfully."); @@ -76,4 +86,14 @@ public function displayScheduledTasks() echo "Task Name: {$task->name}, Next Run Time: {$task->nextRunTime->format('Y-m-d H:i:s')}, Last Run Time: " . ($task->lastRunTime ? $task->lastRunTime->format('Y-m-d H:i:s') : 'Never') . "\n"; } } + + /** + * Get all scheduled tasks. + * + * @return array + */ + public function getTasks() + { + return $this->tasks; + } } diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/Hm_NewEmailProcessedEvent.php index 029f110d71..9fd5ce6c85 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/Hm_NewEmailProcessedEvent.php @@ -7,7 +7,7 @@ class Hm_NewEmailProcessedEvent extends Hm_BaseEvent// implements Hm_ShouldQueue { - use Hm_Dispatchable;//, Hm_InteractsWithQueue; + use Hm_Dispatchable;//Hm_InteractsWithQueue; /** * Create a new event instance. diff --git a/services/Hm_ConsoleKernal.php b/services/Hm_ConsoleKernal.php deleted file mode 100644 index 383aa2db11..0000000000 --- a/services/Hm_ConsoleKernal.php +++ /dev/null @@ -1,29 +0,0 @@ -scheduler = $scheduler; - - } - - /** - * Define the application's command schedule. - */ - public function schedule() - { - // Register tasks with the scheduler - $this->scheduler->command('check:mail') - ->everyMinute(); - // ->onOneServer() - // ->withoutOverlapping(10); - } -} diff --git a/services/Hm_ConsoleKernel.php b/services/Hm_ConsoleKernel.php index 5e2f202277..a9f9e21edd 100644 --- a/services/Hm_ConsoleKernel.php +++ b/services/Hm_ConsoleKernel.php @@ -3,12 +3,22 @@ namespace Services; use Services\Core\Scheduling\Hm_Scheduler; -use Services\Core\Hm_Container; +/** + * Class Hm_ConsoleKernel + * @package Services + */ class Hm_ConsoleKernel { + /** + * @var Hm_Scheduler + */ protected $scheduler; + /** + * Hm_ConsoleKernel constructor. + * @param Hm_Scheduler $scheduler + */ public function __construct(Hm_Scheduler $scheduler) { $this->scheduler = $scheduler; diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 43b91b8923..0faf8d0995 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,7 +1,7 @@ [ Hm_NewMaiListener::class, ], ]; + /** + * Register the application's event listeners. + * + * @return void + */ public function register(): void { foreach ($this->listen as $event => $listeners) { diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index db44596c01..822af80eee 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -10,14 +10,25 @@ use Services\Core\Queue\Drivers\Hm_AmazonSQSQueue; use Symfony\Component\DependencyInjection\Reference; +/** + * Class Hm_QueueServiceProvider + * @package Services\Providers + */ class Hm_QueueServiceProvider { + /** + * @var Hm_QueueManager + */ protected Hm_QueueManager $queueManager; + /** + * Register the service provider + */ public function register() { - $queueConnection = getenv('QUEUE_DRIVER') ?: 'database'; $containerBuilder = Hm_Container::getContainer(); + $config = $containerBuilder->get('config'); + $queueConnection = $config->get('queue_driver'); $containerBuilder->register('queue.manager', Hm_QueueManager::class) ->setShared(true); @@ -46,7 +57,7 @@ public function register() $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.sqs')]); break; - default: + case 'database': $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) ->addArgument(new Reference('db')) ->addArgument(new Reference('db.connection')); diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/Hm_SchedulerServiceProvider.php index d0c2013038..b9a219d789 100644 --- a/services/Providers/Hm_SchedulerServiceProvider.php +++ b/services/Providers/Hm_SchedulerServiceProvider.php @@ -4,9 +4,13 @@ use Hm_Cache; use Services\Core\Hm_Container; -use Services\Core\Scheduling\Hm_CacheMutex; use Services\Core\Scheduling\Hm_Scheduler; +use Services\Core\Scheduling\Hm_CacheMutex; +/** + * Class Hm_SchedulerServiceProvider + * @package Services\Providers + */ class Hm_SchedulerServiceProvider { diff --git a/services/readme.rd b/services/readme.rd index f98c47b02d..55c86e8c79 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -15,17 +15,10 @@ $worker = new QueueWorker($queueManager->getDriver('redis')); $worker->work(); ``` +#Adding scheduler +you can use register or command on `$scheduler`, we prepared the class `Hm_ConsoleKernel` for that: ``` register(function () { echo "Running database cleanup\n"; @@ -40,16 +33,22 @@ $scheduler->command('backup:database')->dailyAt('02:00'); as we now have `Hm_SchedulerRunCommand.php` we can do: ``` -* * * * * php81 /path/to/cypht/project/console schedule:run +* * * * * cd /path-to-your-project && php console schedule:run >> /dev/null 2>&1 ``` +#Running the Scheduler Locally +Typically, you would not add a scheduler cron entry to your local development machine. Instead, you may use the `schedule:work` Console command. This command will run in the foreground and invoke the scheduler every minute until you terminate the command: ``` -// Dispatch the event -(new NewEmailProcessedEvent)->dispatch('user@example.com'); +php console schedule:work ``` + +#Dispatch the event +``` +(new NewEmailProcessedEvent)->dispatch('user@example.com'); ``` -// Notification Example usage +#Notification Example usage +``` use Services\Notifications\UserNotification; // Configure the notification channels From 6ae244468b4d9072f683f26df2a401aefd396128 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Wed, 13 Nov 2024 07:44:39 +0300 Subject: [PATCH 16/21] Implement Channels Notifications --- .env.example | 14 + composer.json | 16 +- composer.lock | 2395 ++++++++++++++--- config/broadcasting.php | 7 + config/services.php | 22 + .../Contracts/Notifications/Hm_Dispatcher.php | 27 + .../Contracts/Notifications/Hm_Factory.php | 32 + services/Contracts/Queue/Hm_ShouldQueue.php | 8 +- .../Commands/Hm_WebSocketServerCommand.php | 69 + services/Core/Jobs/Hm_BaseJob.php | 60 +- .../Core/{Queue => Jobs}/Hm_JobDispatcher.php | 5 +- .../Channels/Hm_BroadcastChannel.php | 75 + .../Channels/Hm_NotificationChannel.php | 2 +- .../Channels/Hm_SlackChannel.php | 52 +- .../Channels/Hm_TelegramChannel.php | 51 +- .../Channels/Hm_TwilioChannel.php | 44 +- .../Channels/Hm_VonageChannel.php | 47 + .../Core/Notifications/Hm_Notification.php | 149 +- .../Hm_NotificationDispatcher.php | 83 + .../Core/Notifications/Hm_WebSocketServer.php | 51 + .../Core/Queue/Drivers/Hm_AmazonSQSQueue.php | 65 +- .../Core/Queue/Drivers/Hm_DatabaseQueue.php | 73 +- services/Core/Queue/Drivers/Hm_RedisQueue.php | 63 +- services/Core/Queue/Hm_QueueWorker.php | 4 +- services/Core/Queue/Hm_Queueable.php | 80 + .../Notifications/Hm_NewMailNotification.php | 17 +- .../Providers/Hm_QueueServiceProvider.php | 2 +- services/Traits/Hm_Dispatchable.php | 6 +- services/Traits/Hm_InteractsWithQueue.php | 21 +- 29 files changed, 2869 insertions(+), 671 deletions(-) create mode 100644 config/broadcasting.php create mode 100644 config/services.php create mode 100644 services/Contracts/Notifications/Hm_Dispatcher.php create mode 100644 services/Contracts/Notifications/Hm_Factory.php create mode 100644 services/Core/Commands/Hm_WebSocketServerCommand.php rename services/Core/{Queue => Jobs}/Hm_JobDispatcher.php (94%) create mode 100644 services/Core/Notifications/Channels/Hm_BroadcastChannel.php create mode 100644 services/Core/Notifications/Channels/Hm_VonageChannel.php create mode 100644 services/Core/Notifications/Hm_NotificationDispatcher.php create mode 100644 services/Core/Notifications/Hm_WebSocketServer.php create mode 100644 services/Core/Queue/Hm_Queueable.php diff --git a/.env.example b/.env.example index b39df92243..0b39f22481 100644 --- a/.env.example +++ b/.env.example @@ -228,3 +228,17 @@ REDIS_PASS=null REDIS_SOCKET=/var/run/redis/redis-server.sock REDIS_PREFIX= +TELEGRAM_BOT_TOKEN=your-telegram-bot-token +TELEGRAM_CHAT_ID=your-chat-id + +TWILIO_SID=your-twilio-sid +TWILIO_TOKEN=your-twilio-token +TWILIO_FROM=your-twilio-from-number + +#Nexmo is now Vonage +VONAGE_API_KEY=your-nexmo-api-key +VONAGE_API_SECRET=your-nexmo-api-secret +VONAGE_FROM_NUMBER=your-nexmo-from-number + +SLACK_TOKEN=your-slack-token +SLACK_CHANNEL=your-slack-channel diff --git a/composer.json b/composer.json index 16ee41fe05..24d410069b 100644 --- a/composer.json +++ b/composer.json @@ -40,10 +40,7 @@ "docs": "https://cypht.org/documentation.html" }, "require": { - "aws/aws-sdk-php": "*", - "bacon/bacon-qr-code": "^1.0.3 || ^2.0.0", - "christian-riesen/base32": "^1.3.2", - "composer": "^2.0.0", + "php": ">=8.1", "ext-curl": "*", "ext-fileinfo": "*", "ext-iconv": "*", @@ -51,16 +48,25 @@ "ext-mbstring": "*", "ext-openssl": "*", "ext-session": "*", + "composer": "^2.0.0", + "aws/aws-sdk-php": "*", + "bacon/bacon-qr-code": "^1.0.3 || ^2.0.0", + "cboden/ratchet": "^0.4.4", + "christian-riesen/base32": "^1.3.2", "ezyang/htmlpurifier": "^4.17", "henrique-borba/php-sieve-manager": "^1.0", "league/commonmark": "^2.4", "paragonie/random_compat": "^2.0.18", - "php": ">=8.1", + "ratchet/pawl": "^0.4.1", "symfony/console": "^6.4", "symfony/dependency-injection": "*", "symfony/dotenv": "^4.3 || 5.4", "symfony/error-handler": "^6.4", "symfony/notifier": "*", + "symfony/slack-notifier": "^6.4", + "symfony/telegram-notifier": "^6.4", + "symfony/twilio-notifier": "^6.4", + "symfony/vonage-notifier": "^6.4", "symfony/yaml": "~6.4.3", "thomaspark/bootswatch": "^5.3", "twbs/bootstrap": "^5.3", diff --git a/composer.lock b/composer.lock index 76a9d8f248..fec9bb0e30 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4be9f7ab25b2c435f315264408b9b27c", + "content-hash": "84d22f476f36e1cea793f78c5c5817ef", "packages": [ { "name": "aws/aws-crt-php", @@ -212,6 +212,69 @@ }, "time": "2022-12-07T17:46:57+00:00" }, + { + "name": "cboden/ratchet", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "5012dc954541b40c5599d286fd40653f5716a38f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/5012dc954541b40c5599d286fd40653f5716a38f", + "reference": "5012dc954541b40c5599d286fd40653f5716a38f", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.7|^2.0", + "php": ">=5.4.2", + "ratchet/rfc6455": "^0.3.1", + "react/event-loop": ">=0.4", + "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", + "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0", + "symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/Ratchet/issues", + "source": "https://github.com/ratchetphp/Ratchet/tree/v0.4.4" + }, + "time": "2021-12-14T00:20:41+00:00" + }, { "name": "christian-riesen/base32", "version": "1.6.0", @@ -396,6 +459,53 @@ }, "time": "2024-07-08T12:26:09+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, { "name": "ezyang/htmlpurifier", "version": "v4.17.0", @@ -1718,144 +1828,86 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "symfony/console", - "version": "v6.4.13", + "name": "ratchet/pawl", + "version": "v0.4.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79" + "url": "https://github.com/ratchetphp/Pawl.git", + "reference": "af70198bab77a582b31169d3cc3982bed25c161f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f793dd5a7d9ae9923e35d0503d08ba734cec1d79", - "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/af70198bab77a582b31169d3cc3982bed25c161f", + "reference": "af70198bab77a582b31169d3cc3982bed25c161f", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "evenement/evenement": "^3.0 || ^2.0", + "guzzlehttp/psr7": "^2.0 || ^1.7", + "php": ">=5.4", + "ratchet/rfc6455": "^0.3.1", + "react/socket": "^1.9" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8" + }, + "suggest": { + "reactivex/rxphp": "~2.0" }, "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Ratchet\\Client\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", + "description": "Asynchronous WebSocket client", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "Ratchet", + "async", + "client", + "websocket", + "websocket client" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.13" + "issues": "https://github.com/ratchetphp/Pawl/issues", + "source": "https://github.com/ratchetphp/Pawl/tree/v0.4.1" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-09T08:40:40+00:00" + "time": "2021-12-10T14:32:34+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v6.4.13", + "name": "ratchet/rfc6455", + "version": "v0.3.1", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", - "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.2.10|^7.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.1", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<6.3", - "symfony/yaml": "<5.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" + "guzzlehttp/psr7": "^2 || ^1.7", + "php": ">=5.4.2" }, "require-dev": { - "symfony/config": "^6.1|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "phpunit/phpunit": "^5.7", + "react/socket": "^1.3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Ratchet\\RFC6455\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1863,66 +1915,55 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Matt Bonneau", + "role": "Developer" } ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2021-12-09T23:20:49+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, + "type": "library", "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "React\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1930,61 +1971,1179 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2022-11-30T15:59:55+00:00" }, { - "name": "symfony/dotenv", - "version": "v5.4.0", + "name": "react/dns", + "version": "v1.13.0", "source": { "type": "git", - "url": "https://github.com/symfony/dotenv.git", - "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0" + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/9bd173ff68fa90d39c59d91a42ae42b7f11713a0", - "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Dotenv\\": "" + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "reference": "f793dd5a7d9ae9923e35d0503d08ba734cec1d79", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-09T08:40:40+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:07:50+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/9bd173ff68fa90d39c59d91a42ae42b7f11713a0", + "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T10:19:22+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", + "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-05T16:39:55+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "20414d96f391677bf80078aa55baece78b82647d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "ba020a321a95519303a3f09ec2824d34d601c388" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ba020a321a95519303a3f09ec2824d34d601c388", + "reference": "ba020a321a95519303a3f09ec2824d34d601c388", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.3" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-05T16:39:55+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:07:50+00:00" + }, + { + "name": "symfony/notifier", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/notifier.git", + "reference": "c46321b53391088861bf627cd9e24873d216cf00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/notifier/zipball/c46321b53391088861bf627cd9e24873d216cf00", + "reference": "c46321b53391088861bf627cd9e24873d216cf00", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<5.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4" + }, + "require-dev": { + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1996,23 +3155,101 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Sends notifications via one or more channels (email, SMS, ...)", + "homepage": "https://symfony.com", + "keywords": [ + "notification", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/notifier/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Registers environment variables from a .env file", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ - "dotenv", - "env", - "environment" + "compatibility", + "ctype", + "polyfill", + "portable" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.4.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -2028,47 +3265,201 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/error-handler", - "version": "v6.4.13", + "name": "symfony/polyfill-iconv", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c" + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", - "reference": "e3c78742f86a5b65fe2ac4c4b6b776d92fd7ca0c", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "php": ">=7.2" }, - "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" + "provide": { + "ext-iconv": "*" }, - "require-dev": { - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^5.4|^6.0|^7.0" + "suggest": { + "ext-iconv": "For best performance" }, - "bin": [ - "Resources/bin/patch-type-declarations" + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, - "exclude-from-classmap": [ - "/Tests/" - ] + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2076,18 +3467,30 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to manage errors and ease debugging PHP code", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.13" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -2103,45 +3506,44 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/notifier", - "version": "v6.4.13", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/notifier.git", - "reference": "c46321b53391088861bf627cd9e24873d216cf00" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/notifier/zipball/c46321b53391088861bf627cd9e24873d216cf00", - "reference": "c46321b53391088861bf627cd9e24873d216cf00", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/event-dispatcher": "<5.4", - "symfony/event-dispatcher-contracts": "<2.5", - "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4" + "php": ">=7.2" }, - "require-dev": { - "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^2.5|^3", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Notifier\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2150,22 +3552,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Sends notifications via one or more channels (email, SMS, ...)", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ - "notification", - "notifier" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/notifier/tree/v6.4.13" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -2181,30 +3587,30 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-ctype", + "name": "symfony/polyfill-mbstring", "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { "php": ">=7.2" }, "provide": { - "ext-ctype": "*" + "ext-mbstring": "*" }, "suggest": { - "ext-ctype": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { @@ -2218,7 +3624,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2227,24 +3633,25 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", + "mbstring", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -2263,28 +3670,22 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-iconv", + "name": "symfony/polyfill-php80", "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", - "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { "php": ">=7.2" }, - "provide": { - "ext-iconv": "*" - }, - "suggest": { - "ext-iconv": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -2297,14 +3698,21 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2314,17 +3722,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Iconv extension", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "iconv", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -2343,25 +3750,22 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", + "name": "symfony/polyfill-php83", "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -2374,8 +3778,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2391,18 +3798,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -2421,41 +3826,126 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "name": "symfony/routing", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/routing.git", + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-01T08:30:56+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2472,18 +3962,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -2499,45 +3989,35 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "name": "symfony/slack-notifier", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "url": "https://github.com/symfony/slack-notifier.git", + "reference": "6cb052d7db1125abc4fba5f00b17523e41086852" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/slack-notifier/zipball/6cb052d7db1125abc4fba5f00b17523e41086852", + "reference": "6cb052d7db1125abc4fba5f00b17523e41086852", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/notifier": "^6.2.7|^7.0" }, + "type": "symfony-notifier-bridge", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Component\\Notifier\\Bridge\\Slack\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2545,25 +4025,22 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony Slack Notifier Bridge", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "notifier", + "slack" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/slack-notifier/tree/v6.4.13" }, "funding": [ { @@ -2579,41 +4056,49 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "name": "symfony/string", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "url": "https://github.com/symfony/string.git", + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, + "type": "library", "autoload": { "files": [ - "bootstrap.php" + "Resources/functions.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Component\\String\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2621,10 +4106,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2634,16 +4115,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/string/tree/v6.4.13" }, "funding": [ { @@ -2659,46 +4142,35 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.5.0", + "name": "symfony/telegram-notifier", + "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "url": "https://github.com/symfony/telegram-notifier.git", + "reference": "c26f425d3a76db76c1b5921e8f7cef75a6309cdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/telegram-notifier/zipball/c26f425d3a76db76c1b5921e8f7cef75a6309cdc", + "reference": "c26f425d3a76db76c1b5921e8f7cef75a6309cdc", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "symfony/http-client": "^6.3|^7.0", + "symfony/mime": "^5.4|^6.3|^7.0", + "symfony/notifier": "^6.4|^7.0" }, + "type": "symfony-notifier-bridge", "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Component\\Notifier\\Bridge\\Telegram\\": "" }, "exclude-from-classmap": [ - "/Test/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2707,26 +4179,22 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Symfony Telegram Notifier Bridge", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "notifier", + "telegram" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/telegram-notifier/tree/v6.4.13" }, "funding": [ { @@ -2742,46 +4210,37 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-10-23T08:05:28+00:00" }, { - "name": "symfony/string", + "name": "symfony/twilio-notifier", "version": "v6.4.13", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627" + "url": "https://github.com/symfony/twilio-notifier.git", + "reference": "14cb32ee1680112f13bbc7987356e824609444c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", - "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", + "url": "https://api.github.com/repos/symfony/twilio-notifier/zipball/14cb32ee1680112f13bbc7987356e824609444c8", + "reference": "14cb32ee1680112f13bbc7987356e824609444c8", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/notifier": "^6.2.7|^7.0" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "symfony/http-foundation": "<6.2" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/webhook": "^6.3|^7.0" }, - "type": "library", + "type": "symfony-notifier-bridge", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Component\\Notifier\\Bridge\\Twilio\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2793,26 +4252,23 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "description": "Symfony Twilio Notifier Bridge", "homepage": "https://symfony.com", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "notifier", + "sms", + "twilio" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.13" + "source": "https://github.com/symfony/twilio-notifier/tree/v6.4.13" }, "funding": [ { @@ -2992,6 +4448,77 @@ ], "time": "2024-09-25T14:18:03+00:00" }, + { + "name": "symfony/vonage-notifier", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/vonage-notifier.git", + "reference": "f18cbdd0f4b95308c606cae715922698d7ed8ded" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/vonage-notifier/zipball/f18cbdd0f4b95308c606cae715922698d7ed8ded", + "reference": "f18cbdd0f4b95308c606cae715922698d7ed8ded", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/notifier": "^6.2.7|^7.0" + }, + "require-dev": { + "symfony/webhook": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Vonage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Vonage Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "sms", + "vonage" + ], + "support": { + "source": "https://github.com/symfony/vonage-notifier/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:18:03+00:00" + }, { "name": "symfony/yaml", "version": "v6.4.13", @@ -5091,7 +6618,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "composer": "^2.0.0", + "php": ">=8.1", "ext-curl": "*", "ext-fileinfo": "*", "ext-iconv": "*", @@ -5099,7 +6626,7 @@ "ext-mbstring": "*", "ext-openssl": "*", "ext-session": "*", - "php": ">=8.1" + "composer": "^2.0.0" }, "platform-dev": [], "platform-overrides": { diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000000..aec52105b0 --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,7 @@ + [ + 'port' => '8080', + ], +]; \ No newline at end of file diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000000..3a1ff3e232 --- /dev/null +++ b/config/services.php @@ -0,0 +1,22 @@ + [ + 'sid' => env('TWILIO_SID'), + 'token' => env('TWILIO_TOKEN'), + 'from' => env('TWILIO_FROM'), + ], + 'vonage' => [ + 'api_key' => env('VONAGE_API_KEY'), + 'api_secret' => env('VONAGE_API_SECRET'), + 'from' => env('VONAGE_FROM'), + ], + 'slack' => [ + 'channel' => env('SLACK_CHANNEL'), + 'token' => env('SLACK_TOKEN') + ], + 'telegram' => [ + 'bot_token' => env('TELEGRAM_BOT_TOKEN'), + 'chat_id' => env('TELEGRAM_CHAT_ID'), + ], +]; \ No newline at end of file diff --git a/services/Contracts/Notifications/Hm_Dispatcher.php b/services/Contracts/Notifications/Hm_Dispatcher.php new file mode 100644 index 0000000000..ece9939759 --- /dev/null +++ b/services/Contracts/Notifications/Hm_Dispatcher.php @@ -0,0 +1,27 @@ +setDescription('Start the WebSocket server to handle real-time notifications'); + } + + /** + * Execute the console command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command exit code. + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = Hm_Container::getContainer()->get('config'); + $broadcastConfig = $config->get('broadcast'); + $output->writeln('Starting WebSocket server on '.$broadcastConfig['port'].'...'); + + $server = IoServer::factory( + new WsServer( + new Hm_WebSocketServer() + ), + $broadcastConfig['port'] + ); + + // Handle SIGINT (Ctrl+C) and SIGTERM (kill) to shut down gracefully + pcntl_signal(SIGINT, function() use ($server, $output) { + $output->writeln("Gracefully shutting down the WebSocket server..."); + exit; + }); + + $output->writeln('WebSocket server started on ws://localhost:'.$broadcastConfig['port']); + + // Run the WebSocket server + $server->run(); + + return Command::SUCCESS; + } +} diff --git a/services/Core/Jobs/Hm_BaseJob.php b/services/Core/Jobs/Hm_BaseJob.php index 6e10737542..e6597d87f9 100644 --- a/services/Core/Jobs/Hm_BaseJob.php +++ b/services/Core/Jobs/Hm_BaseJob.php @@ -3,67 +3,11 @@ namespace Services\Core\Jobs; use Services\Contracts\Hm_Job; +use Services\Core\Queue\Hm_Queueable; -abstract class Hm_BaseJob implements Hm_Job +abstract class Hm_BaseJob extends Hm_Queueable implements Hm_Job { - public string $driver = ''; - public int $tries = 3; - protected int $attempts = 0; - public function __construct(protected array $data = []) { $this->data = $data; } - - /** - * Execute the job. - * - * @return void - */ - public function handle(): void {} - /** - * Handle a job failure. - * - * @return void - */ - public function failed(): void {} - - /** - * Get the driver name for the job. - * - * @return int - */ - public function getDriver(): string - { - return $this->driver; - } - - /** - * Get the number of times the job has been attempted. - * - * @return int - */ - public function getAttempts(): int - { - return $this->attempts; - } - - /** - * Method to increment the attempt count - * - */ - public function incrementAttempts(): void - { - $this->attempts++; - } - - /** - * Determine if the job has exceeded the maximum number of attempts. - * - * @return bool - */ - public function hasExceededMaxAttempts(): bool - { - return $this->attempts >= $this->tries; - } - } diff --git a/services/Core/Queue/Hm_JobDispatcher.php b/services/Core/Jobs/Hm_JobDispatcher.php similarity index 94% rename from services/Core/Queue/Hm_JobDispatcher.php rename to services/Core/Jobs/Hm_JobDispatcher.php index 30d929c14a..db6af8f369 100644 --- a/services/Core/Queue/Hm_JobDispatcher.php +++ b/services/Core/Jobs/Hm_JobDispatcher.php @@ -1,10 +1,10 @@ driver; - dd($driver); $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); if ($queueDriver) { $queueDriver->push($job); diff --git a/services/Core/Notifications/Channels/Hm_BroadcastChannel.php b/services/Core/Notifications/Channels/Hm_BroadcastChannel.php new file mode 100644 index 0000000000..7dd3797f73 --- /dev/null +++ b/services/Core/Notifications/Channels/Hm_BroadcastChannel.php @@ -0,0 +1,75 @@ +get('config'); + $broadcastConfig = $config->get('broadcast'); + $this->port = $broadcastConfig['port']; + $this->connector = new Connector(); + $this->connect(); + } + + /** + * Attempt to connect to the WebSocket server. + */ + protected function connect(): void + { + $this->connector('ws://localhost:'.$this->port)->then(function(WebSocket $conn) { + $this->wsClient = $conn; + $this->connected = true; + + $conn->on('message', function($msg) { + echo "Received message: $msg\n"; + }); + + $conn->on('close', function($code, $reason) { + $this->connected = false; + echo "Connection closed: $code, $reason\n"; + // TODO: Optionally, implement reconnect logic here + }); + + }, function(\Exception $e) { + $this->connected = false; + echo "Could not connect to WebSocket: {$e->getMessage()}\n"; + //TODO: Implement retry logic if needed + }); + } + + /** + * Send a notification via WebSocket. + * + * @param Hm_Notification $notification + * @return void + */ + public function send($notification): void + { + if ($this->connected && $this->wsClient) { + // Prepare the message for the WebSocket + $message = [ + 'title' => $notification->getTitle(), + 'message' => $notification->getMessageText(), + 'recipient' => $notification->getRecipient(), + ]; + + // Send the message to the connected WebSocket client + $this->wsClient->send(json_encode($message)); + + echo "Message broadcasted via WebSocket!"; + } else { + echo "WebSocket is not connected. Message not sent.\n"; + } + } +} diff --git a/services/Core/Notifications/Channels/Hm_NotificationChannel.php b/services/Core/Notifications/Channels/Hm_NotificationChannel.php index 8abe0bd1db..43bd2f4609 100644 --- a/services/Core/Notifications/Channels/Hm_NotificationChannel.php +++ b/services/Core/Notifications/Channels/Hm_NotificationChannel.php @@ -4,5 +4,5 @@ abstract class Hm_NotificationChannel { - abstract public function send($notifiable, string $message): void; + abstract public function send($notification): void; } diff --git a/services/Core/Notifications/Channels/Hm_SlackChannel.php b/services/Core/Notifications/Channels/Hm_SlackChannel.php index 9e4ecd0b50..839b7888eb 100644 --- a/services/Core/Notifications/Channels/Hm_SlackChannel.php +++ b/services/Core/Notifications/Channels/Hm_SlackChannel.php @@ -2,24 +2,56 @@ namespace Services\Core\Notifications\Channels; -use Symfony\Component\Notifier\Notification\Notification; +use Services\Core\Hm_Container; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; use Symfony\Component\Notifier\Bridge\Slack\SlackTransport; +use Symfony\Component\Notifier\Bridge\Slack\Block\SlackContextBlock; +/** + * Class Hm_SlackChannel + * @package Services\Core\Notifications\Channels + */ class Hm_SlackChannel extends Hm_NotificationChannel { - private SlackTransport $transport; + /** + * The Notifier instance. + * + * @var Notifier + */ + private $notifier; - public function __construct(SlackTransport $transport) + /** + * Constructor. + * + */ + public function __construct() { - $this->transport = $transport; + $config = Hm_Container::getContainer()->get('config'); + $slackConfig = $config->get('slack'); + $slackToken = $slackConfig['token']; + $slackChannel = $slackConfig['channel']; + $slackTransport = new SlackTransport($slackToken, $slackChannel); + $this->notifier = new Notifier([$slackTransport]); } - public function send($notifiable, string $message): void + /** + * Send a Slack message. + * + * @param Hm_Notification $notification The notification object. + */ + public function send($notification): void { - // Assuming $notifiable has a method to get the Slack channel/user ID - $notification = (new Notification('Slack Notification')) - ->content($message); - - $this->transport->send($notification); + $slackMessage = new ChatMessage($notification->getTitle()); + $contextBlock = (new SlackContextBlock()) + ->text($notification->getMessageText()); + // Optionally, configure options (e.g., set icon, etc.) + $slackMessage->options((new SlackOptions())->block($contextBlock)); + + // Send the message to Slack + $this->notifier->send($slackMessage->getNotification()); + + echo "Message sent to Slack!"; } } diff --git a/services/Core/Notifications/Channels/Hm_TelegramChannel.php b/services/Core/Notifications/Channels/Hm_TelegramChannel.php index ea8a8af63e..30ff324a27 100644 --- a/services/Core/Notifications/Channels/Hm_TelegramChannel.php +++ b/services/Core/Notifications/Channels/Hm_TelegramChannel.php @@ -2,23 +2,56 @@ namespace Services\Core\Notifications\Channels; -use Symfony\Component\Notifier\Notification\Notification; +use Services\Core\Hm_Container; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransport; +/** + * Class Hm_TelegramChannel + * @package Services\Core\Notifications\Channels + */ class Hm_TelegramChannel extends Hm_NotificationChannel { - private TelegramTransport $transport; + /** + * The Notifier instance. + * + * @var Notifier + */ + private $notifier; - public function __construct(TelegramTransport $transport) + /** + * Constructor. + * + */ + public function __construct() { - $this->transport = $transport; + $config = Hm_Container::getContainer()->get('config'); + $telegramConfig = $config->get('telegram'); + $telegramBotToken = $telegramConfig['bot_token']; + $telegramChatId = $telegramConfig['chat_id']; + $telegramTransport = new TelegramTransport($telegramBotToken, $telegramChatId); + $this->notifier = new Notifier([$telegramTransport]); } - public function send($notifiable, string $message): void + /** + * Send a Telegram message using the Telegram Bot. + * + * @param Hm_Notification $notification The notification object. + */ + public function send($notification): void { - $notification = (new Notification('Telegram Notification')) - ->content($message); - - $this->transport->send($notification); + $telegramMessage = new ChatMessage($notification->getMessageText()); + + // Optionally add more Telegram message options (like parsing mode) + $telegramMessage->options( + (new TelegramOptions()) + ->parseMode(TelegramOptions::PARSE_MODE_MARKDOWN_V2) + ); + + $this->notifier->send($telegramMessage->getNotification()); + + echo "Message sent via Telegram!"; } } diff --git a/services/Core/Notifications/Channels/Hm_TwilioChannel.php b/services/Core/Notifications/Channels/Hm_TwilioChannel.php index 08360f2bf9..475c1b3738 100644 --- a/services/Core/Notifications/Channels/Hm_TwilioChannel.php +++ b/services/Core/Notifications/Channels/Hm_TwilioChannel.php @@ -2,23 +2,49 @@ namespace Services\Core\Notifications\Channels; -use Symfony\Component\Notifier\Notification\Notification; +use Services\Core\Hm_Container; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransport; +/** + * Class Hm_TwilioChannel + * @package Services\Core\Notifications\Channels + */ class Hm_TwilioChannel extends Hm_NotificationChannel { - private TwilioTransport $transport; + /** + * The Notifier instance. + * + * @var Notifier + */ + private $notifier; - public function __construct(TwilioTransport $transport) + /** + * Constructor. + * + */ + public function __construct() { - $this->transport = $transport; + $config = Hm_Container::getContainer()->get('config'); + $twilioConfig = $config->get('twilio'); + $twilioTransport = new TwilioTransport($twilioConfig['sid'], $twilioConfig['token'], $twilioConfig['from']); + $this->notifier = new Notifier([$twilioTransport]); } - public function send($notifiable, string $message): void + /** + * Send an SMS message using Twilio. + * + * @param string $notifiable The recipient's phone number. + * @param string $message The message content. + * @param string $title The title or subject of the notification (optional). + */ + public function send($notification): void { - $notification = (new Notification('SMS Notification')) - ->content($message); - - $this->transport->send($notification); + $smsMessage = new SmsMessage($notification->getRecipient(), $notification->getMessageText()); + // Send the message via Twilio + $this->notifier->send($smsMessage->getNotification()); + + echo "Message sent via Twilio!"; } } diff --git a/services/Core/Notifications/Channels/Hm_VonageChannel.php b/services/Core/Notifications/Channels/Hm_VonageChannel.php new file mode 100644 index 0000000000..b2500b18a9 --- /dev/null +++ b/services/Core/Notifications/Channels/Hm_VonageChannel.php @@ -0,0 +1,47 @@ +get('config'); + $vonageConfig = $config->get('vonage'); + $vonageApiKey = $vonageConfig['api_key']; + $vonageApiSecret = $vonageConfig['api_secret']; + $vonageFromNumber = $vonageConfig['from']; + $vonageTransport = new VonageTransport($vonageApiKey, $vonageApiSecret, $vonageFromNumber); + $this->notifier = new Notifier([$vonageTransport]); + } + + /** + * Send an SMS message using Vonage. + * + * @param Hm_Notification $notification The notification object. + */ + public function send($notification): void + { + $smsMessage = new SmsMessage($notification->getRecipient(), $notification->getMessageText()); + + // Optionally, you can configure options for Vonage (like TTL, etc.) + // $smsMessage->options(new VonageOptions()); + + // Send the message via Vonage + $this->notifier->send($smsMessage->getNotification()); + + echo "Message sent via Vonage (Nexmo)!"; + } +} diff --git a/services/Core/Notifications/Hm_Notification.php b/services/Core/Notifications/Hm_Notification.php index 9bc49f5f8a..ab140f4aab 100644 --- a/services/Core/Notifications/Hm_Notification.php +++ b/services/Core/Notifications/Hm_Notification.php @@ -3,43 +3,146 @@ namespace Services\Core\Notifications; use Symfony\Component\Notifier\Notifier; -use Services\Core\Notifications\Channels\Hm_SlackChannel; -use Services\Core\Notifications\Channels\Hm_TwilioChannel; -use Services\Core\Notifications\Channels\Hm_TelegramChannel; +use Services\Traits\Hm_Dispatchable; +use Services\Core\Queue\Hm_Queueable; +use Services\Contracts\Notifications\Hm_Dispatcher; +use Symfony\Component\Notifier\Recipient\Recipient; -class Hm_Notification +/** + * Notification class + * package: Services\Core\Notifications + */ +class Hm_Notification extends Hm_Queueable implements Hm_Dispatcher { - public function __construct(private array $config = []) + use Hm_Dispatchable; + /** + * The notification driver. + * + * @var string + */ + public string $driver; + /** + * The notification title. + * + * @var string + */ + public string $title; + + /** + * The notification lines. + * + * @var array + */ + public array $lines = []; + + /** + * The recipient of the notification. + * + * @var Recipient + */ + protected Recipient $recipient; + + /** + * Set the title of the notification. + * + * @param string $title + * @return self + */ + public function greeting(string $title): self + { + $this->title = $title; + return $this; + } + + /** + * Add a line to the message of the notification. + * + * @param string $line + * @return self + */ + public function line(string $line): self { - $this->config = $config; // Set configuration in the constructor + $this->lines[] = $line; + return $this; } + /** + * Get the full message text, combining all lines. + * + * @return string + */ + public function getMessageText(): string + { + return implode("\n", $this->lines); + } + /** + * Notifcations can be sent through multiple channels. + * + * @return array + */ public function via(): array { - return $this->config['channels'] ?? ['slack', 'telegram']; + return []; } - public function send($notifiable, string $title, string $content): void + /** + * Get the recipient of the notification. + * + * @return Recipient + */ + public function getRecipient(): Recipient { - $channels = $this->via(); - foreach ($channels as $channel) { - $this->sendThroughChannel($channel, $notifiable, $content); + return $this->recipient; + } + + /** + * Get the title of the notification. + * + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * Set the recipient for the notification. + * + * @param mixed $recipient + * @return self + */ + static public function to(mixed $recipient): string + { + self::$recipient = new Recipient( + is_array($recipient) ? implode(',', $recipient) : $recipient + ); + return static::class; + } + + public function send(): void + { + if (method_exists($this, 'dispatch')) { + self::dispatch(); + } else { + throw new \Exception("Queueing functionality is unavailable."); } } - private function sendThroughChannel(string $channel, $notifiable, string $message): void + public function sendNow(): void { - switch ($channel) { - case 'slack': - (new Hm_SlackChannel(new SlackTransport()))->send($notifiable, $message); - break; - case 'telegram': - (new Hm_TelegramChannel(new TelegramTransport()))->send($notifiable, $message); - break; - case 'twilio': - (new Hm_TwilioChannel(new TwilioTransport()))->send($notifiable, $message); - break; - // Add more channels as necessary + $channels = $this->via(); + foreach ($channels as $channel) { + $channelClass = "\\Services\\Core\\Notifications\\Channels\\Hm_" . ucfirst($channel) . "Channel"; + if (class_exists($channelClass)) { + $channelInstance = new $channelClass(); + if (method_exists($channelInstance, 'send')) { + $channelInstance->send($this->recipient, $this->title, $this->getMessageText()); + } else { + throw new \Exception("The channel {$channel} does not have a send method."); + } + }else { + throw new \Exception("Channel {$channel} not found."); + } } } } diff --git a/services/Core/Notifications/Hm_NotificationDispatcher.php b/services/Core/Notifications/Hm_NotificationDispatcher.php new file mode 100644 index 0000000000..90cce3d03e --- /dev/null +++ b/services/Core/Notifications/Hm_NotificationDispatcher.php @@ -0,0 +1,83 @@ + Hm_SlackChannel::class, + 'vonage' => Hm_VonageChannel::class, + 'twilio' => Hm_TwilioChannel::class, + 'telegram' => Hm_TelegramChannel::class, + 'broadcast' => Hm_BroadcastChannel::class, + ]; + /** + * Dispatch a notification to all registered channels. + * + * @param Hm_Notification $notification The notification instance + */ + public static function send(Hm_Notification $notification): void + { + $channels = $notification->via(); + foreach ($channels as $channelName) { + if (isset(self::$channelClasses[$channelName])) { + $channelClass = self::channel($channelName); + $channel = new $channelClass(); + if (is_subclass_of($notification, Hm_ShouldQueue::class)) { + $driver = $notification->driver; + $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); + if ($queueDriver) { + $queueDriver->push($notification); + } else { + throw new \Exception("Queue driver {$driver} not found."); + } + } else { + // Send notification immediately if not queued + $channel->send($notification); + } + } else { + throw new \Exception("Channel {$channelName} is not registered or implemented."); + } + } + } + + /** + * Dispatch a notification immediately, bypassing the queue. + * + * @param BaseNotification $notification The notification instance + */ + public static function sendNow(Hm_Notification $notification): void + { + $channels = $notification->via(); + foreach ($channels as $channelName) { + if (isset(self::$channelClasses[$channelName])) { + $channelClass = self::channel($channelName); + $channel = new $channelClass(); + $channel->send($notification); + } else { + throw new \Exception("Channel {$channelName} is not registered or implemented."); + } + } + } + + static public function channel($name = null) { + if ($name) { + return self::$channelClasses[$name]; + } + return self::$channelClasses; + } +} diff --git a/services/Core/Notifications/Hm_WebSocketServer.php b/services/Core/Notifications/Hm_WebSocketServer.php new file mode 100644 index 0000000000..1dc0901b45 --- /dev/null +++ b/services/Core/Notifications/Hm_WebSocketServer.php @@ -0,0 +1,51 @@ +clients = new \SplObjectStorage; + } + + // Called when a client connects + public function onOpen(ConnectionInterface $conn) + { + echo "New connection: ({$conn->resourceId})\n"; + $this->clients->attach($conn); + } + + // Called when a message is received from a client + public function onMessage(ConnectionInterface $from, $msg) + { + echo "Message from ({$from->resourceId}): $msg\n"; + + // Broadcast the message to all connected clients + foreach ($this->clients as $client) { + // Don't send the message back to the sender + if ($client !== $from) { + $client->send($msg); + } + } + } + + // Called when a client disconnects + public function onClose(ConnectionInterface $conn) + { + $this->clients->detach($conn); + echo "Connection {$conn->resourceId} has disconnected\n"; + } + + // Called on error + public function onError(ConnectionInterface $conn, \Exception $e) + { + echo "Error: {$e->getMessage()}\n"; + $conn->close(); + } +} diff --git a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php index 478bda6e64..a27e796997 100644 --- a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php +++ b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php @@ -5,8 +5,9 @@ use Hm_AmazonSQS; use Aws\Sqs\SqsClient; use Services\Contracts\Queue\Hm_Queueable; -use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Notifications\Hm_Notification; +use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; /** * Amazon SQS Queue @@ -46,20 +47,20 @@ public function __construct(Hm_AmazonSQS $amazonSQS, SqsClient $sqsConnection) /** * Push the job to the queue * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @return void */ - public function push(Hm_BaseJob $job): void + public function push(Hm_QueueableClass $item): void { - $this->amazonSQS->sendMessage($this->sqsConnection, serialize($job)); + $this->amazonSQS->sendMessage($this->sqsConnection, serialize($item)); } /** * Pop the job from the queue * - * @return Hm_BaseJob|null + * @return Hm_QueueableClass|null */ - public function pop(): ?Hm_BaseJob + public function pop(): ?Hm_QueueableClass { $messages = $this->amazonSQS->receiveMessages($this->sqsConnection); if (!empty($messages)) { @@ -68,16 +69,22 @@ public function pop(): ?Hm_BaseJob $body = $message['Body']; $this->amazonSQS->deleteMessage($this->sqsConnection, $receiptHandle); - $job = unserialize($body); + $item = unserialize($body); try { - $job->handle(); + // Check if the item is a notification, if so send it + if($item instanceof Hm_Notification) { + $item->send(); + }else { + // Otherwise handle the job + $item->handle(); + } } catch (\Exception $e) { - $this->fail($job, $e); // Log the failure + $this->fail($item, $e); // Log the failure throw new \Exception("Failed to process job: " . $e->getMessage()); } - return $job; // Return the job if it was processed successfully + return $item; // Return the job if it was processed successfully } return null; @@ -86,33 +93,33 @@ public function pop(): ?Hm_BaseJob /** * Release the job back to the queue * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @param int $delay * @return void */ - public function release(Hm_BaseJob $job, int $delay = 0): void + public function release(Hm_QueueableClass $item, int $delay = 0): void { - $messageBody = serialize($job); + $messageBody = serialize($item); $this->amazonSQS->sendMessage($this->sqsConnection, $messageBody, $delay); } /** * Process the job and handle failures. * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @param int $maxAttempts * @return void */ - public function process(Hm_BaseJob $job): void + public function process(Hm_QueueableClass $item): void { try { - $job->handle(); + $item->handle(); } catch (\Exception $e) { - $job->incrementAttempts(); - if ($job->getAttempts() >= $job->tries) { - $this->fail($job, $e); + $item->incrementAttempts(); + if ($item->getAttempts() >= $item->tries) { + $this->fail($item, $e); } else { - $this->release($job, 5); + $this->release($item, 5); } } } @@ -120,14 +127,14 @@ public function process(Hm_BaseJob $job): void /** * Move a job to the failed jobs queue after max attempts * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $iten * @param \Exception $exception * @return void */ - protected function fail(Hm_BaseJob $job, \Exception $exception): void + protected function fail(Hm_QueueableClass $item, \Exception $exception): void { - $failedJobData = [ - 'job' => serialize($job), + $failedItemData = [ + 'item' => serialize($item), 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), 'exception' => $exception->getMessage() ]; @@ -135,7 +142,7 @@ protected function fail(Hm_BaseJob $job, \Exception $exception): void // You may want to handle how you store failed jobs. // For simplicity, we will just serialize and log them here, // but ideally, you would want to use a persistent store. - $this->amazonSQS->sendMessage($this->sqsConnection, serialize($failedJobData), 0, $this->failedQueue); + $this->amazonSQS->sendMessage($this->sqsConnection, serialize($failedItemData), 0, $this->failedQueue); } /** @@ -148,12 +155,12 @@ public function retryFailedJobs(): void $messages = $this->amazonSQS->receiveMessages($this->sqsConnection, $this->failedQueue); foreach ($messages as $message) { $body = $message['Body']; - $failedJobData = unserialize($body); - $job = unserialize($failedJobData['job']); + $failedItemData = unserialize($body); + $item = unserialize($failedItemData['item']); // Optionally reset attempts if your job has that logic - $job->resetAttempts(); - $this->push($job); // Push back to current queue + $item->resetAttempts(); + $this->push($item); // Push back to current queue // Optionally delete the failed job message $this->amazonSQS->deleteMessage($this->sqsConnection, $message['ReceiptHandle']); diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php index c9ba0249b2..030698a732 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php @@ -4,9 +4,10 @@ use PDO; use Hm_DB; -use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_Queueable; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Notifications\Hm_Notification; +use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; /** * Class Hm_DatabaseQueue @@ -26,14 +27,14 @@ public function __construct(private Hm_DB $db, protected PDO $dbConnection) {} /** * Push the job to the queue * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @return void */ - public function push(Hm_BaseJob $job): void { + public function push(Hm_QueueableClass $item): void { $sql = "INSERT INTO hm_jobs (payload) VALUES (:payload)"; try { // Use the __serialize method from the Serializer trait - $this->db->execute($this->dbConnection, $sql, ['payload' => serialize($job)], 'insert'); + $this->db->execute($this->dbConnection, $sql, ['payload' => serialize($item)], 'insert'); } catch (\Throwable $th) { throw new \Exception("Failed to push job to the queue: " . $th->getMessage()); } @@ -42,18 +43,18 @@ public function push(Hm_BaseJob $job): void { /** * Pop the job from the queue * - * @return Hm_BaseJob|null + * @return Hm_QueueableClass|null */ - public function pop(): ?Hm_BaseJob { + public function pop(): ?Hm_QueueableClass { $sql = "SELECT * FROM hm_jobs ORDER BY id ASC LIMIT 1"; - $jobRecord = $this->db->execute($this->dbConnection, $sql, [], 'select'); + $itemRecord = $this->db->execute($this->dbConnection, $sql, [], 'select'); - if ($jobRecord) { + if ($itemRecord) { $deleteSql = "DELETE FROM hm_jobs WHERE id = :id"; - $this->db->execute($this->dbConnection, $deleteSql, ['id' => $jobRecord['id']], 'modify'); + $this->db->execute($this->dbConnection, $deleteSql, ['id' => $itemRecord['id']], 'modify'); // Use the __unserialize method from the Serializer trait - $job = unserialize($jobRecord['payload']); + $job = unserialize($itemRecord['payload']); $job->incrementAttempts(); return $job; } @@ -64,34 +65,40 @@ public function pop(): ?Hm_BaseJob { /** * Release the job back into the queue * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @param int $delay * @return void */ - public function release(Hm_BaseJob $job, int $delay = 0): void { + public function release(Hm_QueueableClass $item, int $delay = 0): void { if ($delay > 0) { sleep($delay); } - $this->push($job); + $this->push($item); } /** * Process the job and handle failures. * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @param int $maxAttempts * @return void */ - public function process(Hm_BaseJob $job): void + public function process(Hm_QueueableClass $item): void { try { - $job->handle(); + // Check if the item is a notification, if so send it + if($item instanceof Hm_Notification) { + $item->send(); + }else { + // Otherwise handle the job + $item->handle(); + } } catch (\Exception $e) { - $job->incrementAttempts(); - if ($job->getAttempts() >= $job->tries) { - $this->fail($job, $e); + $item->incrementAttempts(); + if ($item->getAttempts() >= $item->tries) { + $this->fail($item, $e); } else { - $this->release($job, 5); + $this->release($item, 5); } } } @@ -99,18 +106,18 @@ public function process(Hm_BaseJob $job): void /** * Move job to failed jobs table after max attempts. * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @param Exception $exception * @return void */ - public function fail(Hm_BaseJob $job, \Exception $exception): void + public function fail(Hm_QueueableClass $item, \Exception $exception): void { $sql = "INSERT INTO " . self::FAILED_JOBS_TABLE . " (payload, failed_at, exception) VALUES (:payload, :failed_at, :exception)"; $this->db->execute( $this->dbConnection, $sql, [ - 'payload' => serialize($job), // This still requires serialization, keep in mind + 'payload' => serialize($item), // This still requires serialization, keep in mind 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), 'exception' => $exception->getMessage() ], @@ -124,34 +131,34 @@ public function fail(Hm_BaseJob $job, \Exception $exception): void * @param int $failedJobId * @return void */ - public function retry(int $failedJobId): void + public function retry(int $failedItemId): void { $sql = "SELECT * FROM " . self::FAILED_JOBS_TABLE . " WHERE id = :id"; - $failedJobRecord = $this->db->execute($this->dbConnection, $sql, ['id' => $failedJobId], 'select'); + $failedItemRecord = $this->db->execute($this->dbConnection, $sql, ['id' => $failedItemId], 'select'); - if ($failedJobRecord) { - $job = unserialize($failedJobRecord['payload']); + if ($failedItemRecord) { + $item = unserialize($failedItemRecord['payload']); // Remove from failed jobs table $deleteSql = "DELETE FROM " . self::FAILED_JOBS_TABLE . " WHERE id = :id"; - $this->db->execute($this->dbConnection, $deleteSql, ['id' => $failedJobId], 'modify'); + $this->db->execute($this->dbConnection, $deleteSql, ['id' => $failedItemId], 'modify'); // Push back to the main queue - $this->push($job); + $this->push($item); } } /** * Log the failed job. * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @return void */ - protected function logFailedJob(Hm_BaseJob $job): void { + protected function logFailedJob(Hm_QueueableClass $item): void { $sql = "INSERT INTO failed_jobs (payload, attempts) VALUES (:payload, :attempts)"; $this->db->execute($this->dbConnection, $sql, [ - 'payload' => serialize($job), // This still requires serialization - 'attempts' => $job->getAttempts() + 'payload' => serialize($item), // This still requires serialization + 'attempts' => $item->getAttempts() ], 'insert'); } } diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/Hm_RedisQueue.php index a8b11fcf25..a9e22f3598 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/Hm_RedisQueue.php @@ -2,12 +2,13 @@ namespace Services\Core\Queue\Drivers; +use Redis; use Hm_Redis; use Exception; -use Redis; use Services\Contracts\Queue\Hm_Queueable; -use Services\Core\Jobs\Hm_BaseJob; use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Notifications\Hm_Notification; +use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; /** * Redis Queue using Hm_Redis with Hm_Cache_Base trait Hm_RedisQueue @@ -47,15 +48,15 @@ public function __construct(Hm_Redis $redis, Redis $redisConnection, string $que /** * Push a job to the current queue * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @return void */ - public function push(Hm_BaseJob $job): void + public function push($item): void { if ($this->redis->is_active()) { try { - $serializedJob = serialize($job); - $this->redisConnection->rpush($this->currentQueue, $serializedJob); + $serializedItem = serialize($item); + $this->redisConnection->rpush($this->currentQueue, $serializedItem); } catch (Exception $e) { throw new Exception("Failed to push job to the queue: " . $e->getMessage()); } @@ -66,9 +67,9 @@ public function push(Hm_BaseJob $job): void * Pop a job from the current queue * we are usig lpop to get the first job in the queue and remove it * - * @return Hm_BaseJob|null + * @return Hm_QueueableClass|null */ - public function pop(): ?Hm_BaseJob { + public function pop(): ?Hm_QueueableClass { if ($this->redis->is_active()) { $jobData = $this->redisConnection->lpop($this->currentQueue); if ($jobData) { @@ -84,34 +85,40 @@ public function pop(): ?Hm_BaseJob { /** * Release a job back into the current queue after a delay * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $job * @param int $delay * @return void */ - public function release(Hm_BaseJob $job, int $delay = 0): void { + public function release(Hm_QueueableClass $item, int $delay = 0): void { if ($this->redis->is_active()) { if ($delay > 0) { sleep($delay); } - $this->push($job); + $this->push($item); } } /** * Process a job with failure handling * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $item * @return void */ - public function process(Hm_BaseJob $job): void { + public function process(Hm_QueueableClass $item): void { try { - $job->handle(); + // Check if the item is a notification, if so send it + if($item instanceof Hm_Notification) { + $item->send(); + }else { + // Otherwise handle the job + $item->handle(); + } } catch (Exception $e) { - $job->incrementAttempts(); - if ($job->getAttempts() >= $job->tries) { - $this->fail($job, $e); + $item->incrementAttempts(); + if ($item->getAttempts() >= $item->tries) { + $this->fail($item, $e); } else { - $this->release($job, 5); + $this->release($item, 5); } } } @@ -119,18 +126,18 @@ public function process(Hm_BaseJob $job): void { /** * Move a job to the failed jobs queue after max attempts * - * @param Hm_BaseJob $job + * @param Hm_QueueableClass $job * @param Exception $exception * @return void */ - public function fail(Hm_BaseJob $job, Exception $exception): void { + public function fail(Hm_QueueableClass $item, Exception $exception): void { if ($this->redis->is_active()) { - $failedJobData = [ - 'job' => serialize($job), + $failedItemData = [ + 'payload' => serialize($item), 'failed_at' => (new \DateTime())->format('Y-m-d H:i:s'), 'exception' => $exception->getMessage() ]; - $this->redisConnection->rpush($this->failedQueue, serialize($failedJobData)); + $this->redisConnection->rpush($this->failedQueue, serialize($failedItemData)); } } @@ -141,13 +148,13 @@ public function fail(Hm_BaseJob $job, Exception $exception): void { */ public function retryFailedJobs(): void { if ($this->redis->is_active()) { - while ($failedJobData = $this->redisConnection->lpop($this->failedQueue)) { - $failedJobRecord = unserialize($failedJobData); - $job = unserialize($failedJobRecord['job']); + while ($failedItemData = $this->redisConnection->lpop($this->failedQueue)) { + $failedItemRecord = unserialize($failedItemData); + $item = unserialize($failedItemRecord['payload']); // Reset attempts and move back to current queue - $job->resetAttempts(); - $this->push($job); + $item->resetAttempts(); + $this->push($item); } } } diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/Hm_QueueWorker.php index e70cd0181b..4692f305cf 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/Hm_QueueWorker.php @@ -27,10 +27,10 @@ public function __construct(Hm_ShouldQueue $queue) * @return void */ public function work(): void { - while ($job = $this->queue->pop()) + while ($item = $this->queue->pop()) { try { - $this->queue->process($job); + $this->queue->process($item); } catch (\Exception $e) { // $job->failed(); // // Optionally release the job back to the queue with a delay diff --git a/services/Core/Queue/Hm_Queueable.php b/services/Core/Queue/Hm_Queueable.php new file mode 100644 index 0000000000..fc66c89080 --- /dev/null +++ b/services/Core/Queue/Hm_Queueable.php @@ -0,0 +1,80 @@ +driver; + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function getAttempts(): int + { + return $this->attempts; + } + + /** + * Method to increment the attempt count + * + */ + public function incrementAttempts(): void + { + $this->attempts++; + } + + /** + * Determine if the job has exceeded the maximum number of attempts. + * + * @return bool + */ + public function hasExceededMaxAttempts(): bool + { + return $this->attempts >= $this->tries; + } +} diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php index 35805854f9..895ec32317 100644 --- a/services/Notifications/Hm_NewMailNotification.php +++ b/services/Notifications/Hm_NewMailNotification.php @@ -2,10 +2,15 @@ namespace Services\Notifications; +use Services\Traits\Hm_Dispatchable; +use Services\Traits\Hm_InteractsWithQueue; +use Services\Contracts\Queue\Hm_ShouldQueue; use Services\Core\Notifications\Hm_Notification; -class Hm_NewMailNotification extends Hm_Notification +class Hm_NewMailNotification extends Hm_Notification implements Hm_ShouldQueue { + use Hm_Dispatchable, Hm_InteractsWithQueue; + public function __construct(array $config = []) { parent::__construct($config); @@ -13,14 +18,6 @@ public function __construct(array $config = []) public function via(): array { - // Specify which channels this notification should use - return ['slack', 'telegram']; - } - - public function sendNotification($notifiable, string $message): void - { - // You can define a title and content as needed - $title = "New Notification"; - $this->send($notifiable, $title, $message); + return ['slack', 'telegram','broadcast']; } } diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index 822af80eee..189a8192f2 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -4,7 +4,7 @@ use Services\Core\Hm_Container; use Services\Core\Queue\Hm_QueueWorker; use Services\Core\Queue\Hm_QueueManager; -use Services\Core\Queue\Hm_JobDispatcher; +use Services\Core\Jobs\Hm_JobDispatcher; use Services\Core\Queue\Drivers\Hm_RedisQueue; use Services\Core\Queue\Drivers\Hm_DatabaseQueue; use Services\Core\Queue\Drivers\Hm_AmazonSQSQueue; diff --git a/services/Traits/Hm_Dispatchable.php b/services/Traits/Hm_Dispatchable.php index d9794d488b..4d32dcfe29 100644 --- a/services/Traits/Hm_Dispatchable.php +++ b/services/Traits/Hm_Dispatchable.php @@ -4,8 +4,10 @@ use Services\Core\Jobs\Hm_BaseJob; use Services\Core\Events\Hm_BaseEvent; -use Services\Core\Queue\Hm_JobDispatcher; +use Services\Core\Jobs\Hm_JobDispatcher; use Services\Core\Events\Hm_EventDispatcher; +use Services\Core\Notifications\Hm_Notification; +use Services\Core\Notifications\Hm_NotificationDispatcher; trait Hm_Dispatchable { @@ -21,6 +23,8 @@ public static function dispatch(...$arguments) Hm_JobDispatcher::dispatch($instance); }elseif(is_subclass_of($instance, Hm_BaseEvent::class)){ Hm_EventDispatcher::dispatch($instance); + }elseif(is_subclass_of($instance, Hm_Notification::class)){ + Hm_NotificationDispatcher::send($instance); }else{ throw new \Exception("Class must be an instance of Hm_BaseJob or Hm_BaseEvent"); } diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/Hm_InteractsWithQueue.php index 5a5cfa1dd9..54b4e1846a 100644 --- a/services/Traits/Hm_InteractsWithQueue.php +++ b/services/Traits/Hm_InteractsWithQueue.php @@ -2,8 +2,7 @@ namespace Services\Traits; -use Services\Contracts\Hm_Job; -use Services\Core\Jobs\Hm_BaseJob; +use Services\Core\Queue\Hm_Queueable; use Services\Core\Queue\Hm_QueueManager; use Services\Contracts\Queue\Hm_ShouldQueue; @@ -20,19 +19,19 @@ trait Hm_InteractsWithQueue /** * Push a job onto the queue. * - * @param Hm_ShouldQueue $job The job to be pushed onto the queue. + * @param Hm_ShouldQueue $item The item to be pushed onto the queue. * @param mixed $data Optional data to pass with the job. * @param string|null $queue Optional name of the queue. * @return void */ - public function push(Hm_Job $job, $data = '', $queue = null): void + public function push(Hm_Queueable $item, $data = '', $queue = null): void { $driver = $this->getDriver(); // Call the appropriate method from the QueueManager to push the job (new Hm_QueueManager)->getDriver($driver)->push($this, $data, $queue); - echo "Job of type " . get_class($job) . " pushed to the queue successfully.\n"; + echo "Job of type " . get_class($item) . " pushed to the queue successfully.\n"; } /** @@ -41,24 +40,24 @@ public function push(Hm_Job $job, $data = '', $queue = null): void * @param string|null $queue Optional name of the queue. * @return mixed The job from the queue or null if the queue is empty. */ - public function pop($queue = null): ?Hm_BaseJob + public function pop($queue = null): ?Hm_Queueable { $driver = $this->getDriver(); // Call the appropriate method from the QueueManager to pop a job - $job = (new Hm_QueueManager)->getDriver($driver)->pop($queue); + $item = (new Hm_QueueManager)->getDriver($driver)->pop($queue); - if ($job) { - echo "Job of type " . get_class($job) . " popped from the queue.\n"; + if ($item) { + echo "Job of type " . get_class($item) . " popped from the queue.\n"; } else { echo "No job available in the queue.\n"; } - return $job; + return $item; } /** - * Release a job back onto the queue with an optional delay. + * Release a item back onto the queue with an optional delay. * * @param string|null $queue Optional name of the queue. * @param int $delay The number of seconds to delay the release. From 5a888b483521f3e3a5592222471b11c0705b3765 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Tue, 3 Dec 2024 00:55:02 +0300 Subject: [PATCH 17/21] refactor(dep inj): implementation in service privider --- services/Core/Hm_Container.php | 14 +++---- services/Hm_ConsoleKernel.php | 38 ------------------- services/Hm_bootstrap.php | 4 +- .../Providers/Hm_QueueServiceProvider.php | 15 +++++--- services/readme.rd | 2 +- 5 files changed, 19 insertions(+), 54 deletions(-) delete mode 100644 services/Hm_ConsoleKernel.php diff --git a/services/Core/Hm_Container.php b/services/Core/Hm_Container.php index 3805f348b0..8e549cbe5b 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Hm_Container.php @@ -50,34 +50,34 @@ public static function bind(): ContainerBuilder // Register Hm_DB self::$container->set('db.connection', Hm_DB::connect(self::$container->get('config'))); - self::$container->register('db', Hm_DB::class)->setShared(true); + self::$container->register('db', Hm_DB::class)->setPublic(true); } else if ($config->get('queue_driver') === 'redis') { // Register Hm_Redis $redis = new Hm_Redis($config); $redis->connect(); self::$container->set('redis.connection', $redis->getInstance()); - self::$container->register('redis', Hm_Redis::class)->setArgument(0, self::$container->get('config'))->setShared(true); + self::$container->register('redis', Hm_Redis::class)->setArgument(0, self::$container->get('config'))->setPublic(true); } else if ($config->get('queue_enabled') && $config->get('queue_driver') === 'sqs') { // Register Hm_AmazonSQS self::$container->set('amazon.sqs.connection', Hm_AmazonSQS::connect(self::$container->get('config'))); self::$container->register('amazon.sqs', Hm_AmazonSQS::class) - ->setShared(true); + ->setPublic(true); } } // Register Hm_CommandServiceProvider self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) - ->setShared(true); + ->setPublic(true); // Register Hm_QueueServiceProvider self::$container->register('queue.ServiceProvider', Hm_QueueServiceProvider::class) - ->setShared(true); + ->setPublic(true); self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) - ->setShared(true); + ->setPublic(true); self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) - ->setShared(true); + ->setPublic(true); return self::$container; } diff --git a/services/Hm_ConsoleKernel.php b/services/Hm_ConsoleKernel.php deleted file mode 100644 index a9f9e21edd..0000000000 --- a/services/Hm_ConsoleKernel.php +++ /dev/null @@ -1,38 +0,0 @@ -scheduler = $scheduler; - - } - - /** - * Define the application's command schedule. - */ - public function schedule() - { - // Register tasks with the scheduler - $this->scheduler->command('check:mail') - ->everyMinute() - ->withoutOverlapping(); - } -} diff --git a/services/Hm_bootstrap.php b/services/Hm_bootstrap.php index 0faf8d0995..b217e11070 100644 --- a/services/Hm_bootstrap.php +++ b/services/Hm_bootstrap.php @@ -1,6 +1,6 @@ register($config, $session); // Create a new Kernel instance -$kernel = (new Hm_ConsoleKernel($containerBuilder->get('scheduler')))->schedule(); +$kernel = (new Hm_Kernel($containerBuilder->get('scheduler')))->schedule(); return [$containerBuilder, $config]; diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/Hm_QueueServiceProvider.php index 189a8192f2..0a937ca483 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/Hm_QueueServiceProvider.php @@ -31,36 +31,39 @@ public function register() $queueConnection = $config->get('queue_driver'); $containerBuilder->register('queue.manager', Hm_QueueManager::class) - ->setShared(true); + ->setPublic(true); $containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) ->addArgument(new Reference('queue.manager')) - ->setShared(true); + ->setPublic(true); $containerBuilder->register('queue.worker', Hm_QueueWorker::class) ->addArgument(new Reference('queue.driver.' . $queueConnection)) - ->setShared(true); + ->setPublic(true); switch ($queueConnection) { case 'redis': $containerBuilder->register('queue.driver.redis', Hm_RedisQueue::class) ->addArgument(new Reference('redis')) ->addArgument(new Reference('redis.connection')) - ->addArgument('default'); + ->addArgument('default') + ->setPublic(true); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['redis', new Reference('queue.driver.redis')]); break; case 'sqs': $containerBuilder->register('queue.driver.sqs', Hm_AmazonSQSQueue::class) ->addArgument(new Reference('amazon.sqs')) - ->addArgument(new Reference('amazon.sqs.connection')); + ->addArgument(new Reference('amazon.sqs.connection')) + ->setPublic(true); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.sqs')]); break; case 'database': $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) ->addArgument(new Reference('db')) - ->addArgument(new Reference('db.connection')); + ->addArgument(new Reference('db.connection')) + ->setPublic(true); $containerBuilder->getDefinition('queue.manager') ->addMethodCall('addDriver', ['database', new Reference('queue.driver.database')]); break; diff --git a/services/readme.rd b/services/readme.rd index 55c86e8c79..0714e9af07 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -16,7 +16,7 @@ $worker->work(); ``` #Adding scheduler -you can use register or command on `$scheduler`, we prepared the class `Hm_ConsoleKernel` for that: +you can use register or command on `$scheduler`, we prepared the class `Hm_Kernel` for that: ``` Date: Mon, 9 Dec 2024 18:30:34 +0300 Subject: [PATCH 18/21] feat(slack): fixed and tested with queue work --- services/Commands/Hm_CheckMailCommand.php | 10 +- .../Channels/Hm_SlackChannel.php | 33 +++--- .../Core/Notifications/Hm_Notification.php | 101 ++++-------------- .../Core/Queue/Drivers/Hm_AmazonSQSQueue.php | 13 +-- .../Core/Queue/Drivers/Hm_DatabaseQueue.php | 8 +- services/Core/Queue/Drivers/Hm_RedisQueue.php | 9 +- services/Core/Queue/Hm_QueueManager.php | 1 - services/Core/Queue/Hm_QueueWorker.php | 3 +- services/Hm_Kernel.php | 27 +++++ .../Notifications/Hm_NewMailNotification.php | 12 ++- 10 files changed, 83 insertions(+), 134 deletions(-) create mode 100644 services/Hm_Kernel.php diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index dc201d6a8e..74e6681600 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -4,6 +4,7 @@ use Services\Jobs\Hm_ProcessNewEmail; use Services\Core\Commands\Hm_BaseCommand; +use Services\Notifications\Hm_NewMailNotification; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -32,7 +33,14 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { $this->info("Checking for new mail..."); - Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); + $message = "Hello! You have a new mail on test@entreprise server."; + + // $notification = (new Hm_NewMailNotification()) + // ->greeting('New Mail Alert') + // ->line($message); + // $notification->sendNow(); + Hm_NewMailNotification::dispatch($message); + // Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); return Command::SUCCESS; } } diff --git a/services/Core/Notifications/Channels/Hm_SlackChannel.php b/services/Core/Notifications/Channels/Hm_SlackChannel.php index 839b7888eb..f38da64be2 100644 --- a/services/Core/Notifications/Channels/Hm_SlackChannel.php +++ b/services/Core/Notifications/Channels/Hm_SlackChannel.php @@ -3,11 +3,10 @@ namespace Services\Core\Notifications\Channels; use Services\Core\Hm_Container; -use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\ChatMessage; -use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; -use Symfony\Component\Notifier\Bridge\Slack\SlackTransport; -use Symfony\Component\Notifier\Bridge\Slack\Block\SlackContextBlock; +use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; /** * Class Hm_SlackChannel @@ -16,11 +15,11 @@ class Hm_SlackChannel extends Hm_NotificationChannel { /** - * The Notifier instance. + * The Chatter instance. * - * @var Notifier + * @var Chatter */ - private $notifier; + private $chatter; /** * Constructor. @@ -31,9 +30,12 @@ public function __construct() $config = Hm_Container::getContainer()->get('config'); $slackConfig = $config->get('slack'); $slackToken = $slackConfig['token']; - $slackChannel = $slackConfig['channel']; - $slackTransport = new SlackTransport($slackToken, $slackChannel); - $this->notifier = new Notifier([$slackTransport]); + $slackChannel = $slackConfig['channel']; + $dsnString = sprintf('slack://%s@default?channel=%s', $slackToken, $slackChannel); + $dsn = new Dsn($dsnString); + $factory = new SlackTransportFactory(); + $transport = $factory->create($dsn); + $this->chatter = new Chatter($transport); } /** @@ -43,15 +45,8 @@ public function __construct() */ public function send($notification): void { - $slackMessage = new ChatMessage($notification->getTitle()); - $contextBlock = (new SlackContextBlock()) - ->text($notification->getMessageText()); - // Optionally, configure options (e.g., set icon, etc.) - $slackMessage->options((new SlackOptions())->block($contextBlock)); - - // Send the message to Slack - $this->notifier->send($slackMessage->getNotification()); - + $chatMessage = new ChatMessage($notification->getContent()); + $this->chatter->send($chatMessage); echo "Message sent to Slack!"; } } diff --git a/services/Core/Notifications/Hm_Notification.php b/services/Core/Notifications/Hm_Notification.php index ab140f4aab..6d2e04a3e8 100644 --- a/services/Core/Notifications/Hm_Notification.php +++ b/services/Core/Notifications/Hm_Notification.php @@ -2,11 +2,9 @@ namespace Services\Core\Notifications; -use Symfony\Component\Notifier\Notifier; use Services\Traits\Hm_Dispatchable; use Services\Core\Queue\Hm_Queueable; use Services\Contracts\Notifications\Hm_Dispatcher; -use Symfony\Component\Notifier\Recipient\Recipient; /** * Notification class @@ -15,65 +13,22 @@ class Hm_Notification extends Hm_Queueable implements Hm_Dispatcher { use Hm_Dispatchable; - /** - * The notification driver. - * - * @var string - */ - public string $driver; + /** - * The notification title. + * The notification content(message). * * @var string */ - public string $title; - - /** - * The notification lines. - * - * @var array - */ - public array $lines = []; + public string $content = ''; /** - * The recipient of the notification. + * Constructor. * - * @var Recipient - */ - protected Recipient $recipient; - - /** - * Set the title of the notification. - * - * @param string $title - * @return self - */ - public function greeting(string $title): self - { - $this->title = $title; - return $this; - } - - /** - * Add a line to the message of the notification. - * - * @param string $line - * @return self - */ - public function line(string $line): self - { - $this->lines[] = $line; - return $this; - } - - /** - * Get the full message text, combining all lines. - * - * @return string + * @param string $content The notification content. */ - public function getMessageText(): string + public function __construct($content = '') { - return implode("\n", $this->lines); + $this->content = $content; } /** * Notifcations can be sent through multiple channels. @@ -85,38 +40,15 @@ public function via(): array return []; } - /** - * Get the recipient of the notification. - * - * @return Recipient - */ - public function getRecipient(): Recipient + public function handle(): void { - return $this->recipient; + dump("Processing ".self::class); + + $this->sendNow(); } - - /** - * Get the title of the notification. - * - * @return string - */ - public function getTitle(): string + public function failed(): void { - return $this->title; - } - - /** - * Set the recipient for the notification. - * - * @param mixed $recipient - * @return self - */ - static public function to(mixed $recipient): string - { - self::$recipient = new Recipient( - is_array($recipient) ? implode(',', $recipient) : $recipient - ); - return static::class; + echo "Notification failed to send!"; } public function send(): void @@ -136,7 +68,7 @@ public function sendNow(): void if (class_exists($channelClass)) { $channelInstance = new $channelClass(); if (method_exists($channelInstance, 'send')) { - $channelInstance->send($this->recipient, $this->title, $this->getMessageText()); + $channelInstance->send($this); } else { throw new \Exception("The channel {$channel} does not have a send method."); } @@ -145,5 +77,10 @@ public function sendNow(): void } } } + + public function getContent(): string + { + return $this->content; + } } diff --git a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php index a27e796997..d64f71bec4 100644 --- a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php +++ b/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php @@ -6,7 +6,6 @@ use Aws\Sqs\SqsClient; use Services\Contracts\Queue\Hm_Queueable; use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Notifications\Hm_Notification; use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; /** @@ -72,19 +71,13 @@ public function pop(): ?Hm_QueueableClass $item = unserialize($body); try { - // Check if the item is a notification, if so send it - if($item instanceof Hm_Notification) { - $item->send(); - }else { - // Otherwise handle the job - $item->handle(); - } + $item->handle(); } catch (\Exception $e) { - $this->fail($item, $e); // Log the failure + $this->fail($item, $e); throw new \Exception("Failed to process job: " . $e->getMessage()); } - return $item; // Return the job if it was processed successfully + return $item; } return null; diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php index 030698a732..03a4aaea41 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/Hm_DatabaseQueue.php @@ -86,13 +86,7 @@ public function release(Hm_QueueableClass $item, int $delay = 0): void { public function process(Hm_QueueableClass $item): void { try { - // Check if the item is a notification, if so send it - if($item instanceof Hm_Notification) { - $item->send(); - }else { - // Otherwise handle the job - $item->handle(); - } + $item->handle(); } catch (\Exception $e) { $item->incrementAttempts(); if ($item->getAttempts() >= $item->tries) { diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/Hm_RedisQueue.php index a9e22f3598..a3568fa482 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/Hm_RedisQueue.php @@ -7,7 +7,6 @@ use Exception; use Services\Contracts\Queue\Hm_Queueable; use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Notifications\Hm_Notification; use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; /** @@ -106,13 +105,7 @@ public function release(Hm_QueueableClass $item, int $delay = 0): void { */ public function process(Hm_QueueableClass $item): void { try { - // Check if the item is a notification, if so send it - if($item instanceof Hm_Notification) { - $item->send(); - }else { - // Otherwise handle the job - $item->handle(); - } + $item->handle(); } catch (Exception $e) { $item->incrementAttempts(); if ($item->getAttempts() >= $item->tries) { diff --git a/services/Core/Queue/Hm_QueueManager.php b/services/Core/Queue/Hm_QueueManager.php index 9027f798fd..6afb42692b 100644 --- a/services/Core/Queue/Hm_QueueManager.php +++ b/services/Core/Queue/Hm_QueueManager.php @@ -23,7 +23,6 @@ public function addDriver(string $name, Hm_ShouldQueue $driver): void public function getDriver(string $name): Hm_ShouldQueue { - dump("Getting driver $name"); return $this->drivers[$name]; } } diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/Hm_QueueWorker.php index 4692f305cf..19eccea8f8 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/Hm_QueueWorker.php @@ -30,9 +30,10 @@ public function work(): void { while ($item = $this->queue->pop()) { try { + // exit(var_dump($this->queue)); $this->queue->process($item); } catch (\Exception $e) { - // $job->failed(); + $item->failed(); // // Optionally release the job back to the queue with a delay // $this->queue->release($job, 30); } diff --git a/services/Hm_Kernel.php b/services/Hm_Kernel.php new file mode 100644 index 0000000000..180320c7cb --- /dev/null +++ b/services/Hm_Kernel.php @@ -0,0 +1,27 @@ +scheduler = $scheduler; + + } + + /** + * Define the application's command schedule. + */ + public function schedule() + { + // Register tasks with the scheduler + $this->scheduler->command('check:mail') + ->everyMinute() + ->withoutOverlapping(); + } +} diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php index 895ec32317..291eaa5288 100644 --- a/services/Notifications/Hm_NewMailNotification.php +++ b/services/Notifications/Hm_NewMailNotification.php @@ -11,13 +11,15 @@ class Hm_NewMailNotification extends Hm_Notification implements Hm_ShouldQueue { use Hm_Dispatchable, Hm_InteractsWithQueue; - public function __construct(array $config = []) - { - parent::__construct($config); - } + public string $driver = 'redis'; + + // public function __construct(array $config = []) + // { + // parent::__construct($config); + // } public function via(): array { - return ['slack', 'telegram','broadcast']; + return ['slack'];//, 'telegram','broadcast' } } From 9faa6f5d2aa605385c5a3529e4b8678ca72c80ba Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Mon, 9 Dec 2024 19:20:38 +0300 Subject: [PATCH 19/21] feat(telegram/vonage/twilio): fixed and tested telegram with queue work --- services/Commands/Hm_CheckMailCommand.php | 4 +- .../Channels/Hm_TelegramChannel.php | 42 ++++++++---------- .../Channels/Hm_TwilioChannel.php | 38 +++++++++++----- .../Channels/Hm_VonageChannel.php | 44 ++++++++++++------- .../Notifications/Hm_NewMailNotification.php | 2 +- 5 files changed, 77 insertions(+), 53 deletions(-) diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/Hm_CheckMailCommand.php index 74e6681600..7a8eb5ee84 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/Hm_CheckMailCommand.php @@ -35,9 +35,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->info("Checking for new mail..."); $message = "Hello! You have a new mail on test@entreprise server."; - // $notification = (new Hm_NewMailNotification()) - // ->greeting('New Mail Alert') - // ->line($message); + // $notification = new Hm_NewMailNotification($message); // $notification->sendNow(); Hm_NewMailNotification::dispatch($message); // Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); diff --git a/services/Core/Notifications/Channels/Hm_TelegramChannel.php b/services/Core/Notifications/Channels/Hm_TelegramChannel.php index 30ff324a27..c53fc3293a 100644 --- a/services/Core/Notifications/Channels/Hm_TelegramChannel.php +++ b/services/Core/Notifications/Channels/Hm_TelegramChannel.php @@ -3,10 +3,10 @@ namespace Services\Core\Notifications\Channels; use Services\Core\Hm_Container; -use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\ChatMessage; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransport; +use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; /** * Class Hm_TelegramChannel @@ -15,11 +15,11 @@ class Hm_TelegramChannel extends Hm_NotificationChannel { /** - * The Notifier instance. + * The Chatter instance. * - * @var Notifier + * @var Chatter */ - private $notifier; + private $chatter; /** * Constructor. @@ -29,29 +29,25 @@ public function __construct() { $config = Hm_Container::getContainer()->get('config'); $telegramConfig = $config->get('telegram'); - $telegramBotToken = $telegramConfig['bot_token']; - $telegramChatId = $telegramConfig['chat_id']; - $telegramTransport = new TelegramTransport($telegramBotToken, $telegramChatId); - $this->notifier = new Notifier([$telegramTransport]); + $telegramToken = $telegramConfig['bot_token']; + $telegramChatId = $telegramConfig['chat_id']; // ID du chat ou username (@username) + + $dsnString = sprintf('telegram://%s@default?channel=%s', $telegramToken, $telegramChatId); + $dsn = new Dsn($dsnString); + $factory = new TelegramTransportFactory(); + $transport = $factory->create($dsn); + $this->chatter = new Chatter($transport); } /** - * Send a Telegram message using the Telegram Bot. + * Send a Telegram message. * * @param Hm_Notification $notification The notification object. */ public function send($notification): void { - $telegramMessage = new ChatMessage($notification->getMessageText()); - - // Optionally add more Telegram message options (like parsing mode) - $telegramMessage->options( - (new TelegramOptions()) - ->parseMode(TelegramOptions::PARSE_MODE_MARKDOWN_V2) - ); - - $this->notifier->send($telegramMessage->getNotification()); - - echo "Message sent via Telegram!"; + $chatMessage = new ChatMessage($notification->getContent()); + $this->chatter->send($chatMessage); + echo "Message sent to Telegram!"; } -} +} \ No newline at end of file diff --git a/services/Core/Notifications/Channels/Hm_TwilioChannel.php b/services/Core/Notifications/Channels/Hm_TwilioChannel.php index 475c1b3738..7075fb1d0c 100644 --- a/services/Core/Notifications/Channels/Hm_TwilioChannel.php +++ b/services/Core/Notifications/Channels/Hm_TwilioChannel.php @@ -3,9 +3,10 @@ namespace Services\Core\Notifications\Channels; use Services\Core\Hm_Container; -use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\SmsMessage; -use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransport; +use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; /** * Class Hm_TwilioChannel @@ -14,11 +15,11 @@ class Hm_TwilioChannel extends Hm_NotificationChannel { /** - * The Notifier instance. + * The Chatter instance. * - * @var Notifier + * @var Chatter */ - private $notifier; + private $chatter; /** * Constructor. @@ -28,8 +29,19 @@ public function __construct() { $config = Hm_Container::getContainer()->get('config'); $twilioConfig = $config->get('twilio'); - $twilioTransport = new TwilioTransport($twilioConfig['sid'], $twilioConfig['token'], $twilioConfig['from']); - $this->notifier = new Notifier([$twilioTransport]); + $twilioAccountSid = $twilioConfig['sid']; + $twilioAuthToken = $twilioConfig['token']; + $twilioFrom = $twilioConfig['from']; + $dsnString = sprintf( + 'twilio://%s:%s@default?from=%s', + $twilioAccountSid, + $twilioAuthToken, + $twilioFrom + ); + $dsn = new Dsn($dsnString); + $factory = new TwilioTransportFactory(); + $transport = $factory->create($dsn); + $this->chatter = new Chatter($transport); } /** @@ -39,12 +51,18 @@ public function __construct() * @param string $message The message content. * @param string $title The title or subject of the notification (optional). */ + /** + * Send a Twilio SMS message. + * + * @param Hm_Notification $notification The notification object. + */ public function send($notification): void { - $smsMessage = new SmsMessage($notification->getRecipient(), $notification->getMessageText()); - // Send the message via Twilio - $this->notifier->send($smsMessage->getNotification()); + $config = Hm_Container::getContainer()->get('config'); + $twilioTo = $config->get('twilio')['to']; + $smsMessage = new SmsMessage($twilioTo, $notification->getContent()); + $this->chatter->send($smsMessage); echo "Message sent via Twilio!"; } } diff --git a/services/Core/Notifications/Channels/Hm_VonageChannel.php b/services/Core/Notifications/Channels/Hm_VonageChannel.php index b2500b18a9..063c25ddb9 100644 --- a/services/Core/Notifications/Channels/Hm_VonageChannel.php +++ b/services/Core/Notifications/Channels/Hm_VonageChannel.php @@ -3,14 +3,19 @@ namespace Services\Core\Notifications\Channels; use Services\Core\Hm_Container; -use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\SmsMessage; -use Symfony\Component\Notifier\Bridge\Vonage\VonageOptions; -use Symfony\Component\Notifier\Bridge\Vonage\VonageTransport; +use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; class Hm_VonageChannel extends Hm_NotificationChannel { - private $notifier; + /** + * The Chatter instance. + * + * @var Chatter + */ + private $chatter; /** * Constructor. @@ -20,28 +25,35 @@ public function __construct() { $config = Hm_Container::getContainer()->get('config'); $vonageConfig = $config->get('vonage'); + $vonageApiKey = $vonageConfig['api_key']; $vonageApiSecret = $vonageConfig['api_secret']; - $vonageFromNumber = $vonageConfig['from']; - $vonageTransport = new VonageTransport($vonageApiKey, $vonageApiSecret, $vonageFromNumber); - $this->notifier = new Notifier([$vonageTransport]); + $vonageFrom = $vonageConfig['from']; + + $dsnString = sprintf( + 'vonage://%s:%s@default?from=%s', + $vonageApiKey, + $vonageApiSecret, + $vonageFrom + ); + $dsn = new Dsn($dsnString); + $factory = new VonageTransportFactory(); + $transport = $factory->create($dsn); + $this->chatter = new Chatter($transport); } /** - * Send an SMS message using Vonage. + * Send a Vonage SMS message. * * @param Hm_Notification $notification The notification object. */ public function send($notification): void { - $smsMessage = new SmsMessage($notification->getRecipient(), $notification->getMessageText()); - - // Optionally, you can configure options for Vonage (like TTL, etc.) - // $smsMessage->options(new VonageOptions()); - - // Send the message via Vonage - $this->notifier->send($smsMessage->getNotification()); + $config = Hm_Container::getContainer()->get('config'); + $vonageTo = $config->get('vonage')['to']; - echo "Message sent via Vonage (Nexmo)!"; + $smsMessage = new SmsMessage($vonageTo, $notification->getContent()); + $this->chatter->send($smsMessage); + echo "Message sent via Vonage!"; } } diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php index 291eaa5288..426f5e6f43 100644 --- a/services/Notifications/Hm_NewMailNotification.php +++ b/services/Notifications/Hm_NewMailNotification.php @@ -20,6 +20,6 @@ class Hm_NewMailNotification extends Hm_Notification implements Hm_ShouldQueue public function via(): array { - return ['slack'];//, 'telegram','broadcast' + return ['telegram'];//, 'slack', 'telegram','broadcast' } } From 318da41abf02bf86ae8d7002694fbf8b76a05720 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Mon, 9 Dec 2024 20:35:34 +0300 Subject: [PATCH 20/21] fix undefined key with queue --- services/Core/Jobs/Hm_BaseJob.php | 1 + .../Core/Notifications/Hm_Notification.php | 3 ++- .../Hm_NotificationDispatcher.php | 26 +++++-------------- .../Notifications/Hm_NewMailNotification.php | 9 ++----- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/services/Core/Jobs/Hm_BaseJob.php b/services/Core/Jobs/Hm_BaseJob.php index e6597d87f9..dbced8d9b1 100644 --- a/services/Core/Jobs/Hm_BaseJob.php +++ b/services/Core/Jobs/Hm_BaseJob.php @@ -9,5 +9,6 @@ abstract class Hm_BaseJob extends Hm_Queueable implements Hm_Job { public function __construct(protected array $data = []) { $this->data = $data; + $this->driver = env('QUEUE_DRIVER'); } } diff --git a/services/Core/Notifications/Hm_Notification.php b/services/Core/Notifications/Hm_Notification.php index 6d2e04a3e8..04aa5a223c 100644 --- a/services/Core/Notifications/Hm_Notification.php +++ b/services/Core/Notifications/Hm_Notification.php @@ -29,6 +29,7 @@ class Hm_Notification extends Hm_Queueable implements Hm_Dispatcher public function __construct($content = '') { $this->content = $content; + $this->driver = env('QUEUE_DRIVER'); } /** * Notifcations can be sent through multiple channels. @@ -43,7 +44,7 @@ public function via(): array public function handle(): void { dump("Processing ".self::class); - + $this->sendNow(); } public function failed(): void diff --git a/services/Core/Notifications/Hm_NotificationDispatcher.php b/services/Core/Notifications/Hm_NotificationDispatcher.php index 90cce3d03e..93bfaa7149 100644 --- a/services/Core/Notifications/Hm_NotificationDispatcher.php +++ b/services/Core/Notifications/Hm_NotificationDispatcher.php @@ -32,26 +32,12 @@ class Hm_NotificationDispatcher implements Hm_Factory */ public static function send(Hm_Notification $notification): void { - $channels = $notification->via(); - foreach ($channels as $channelName) { - if (isset(self::$channelClasses[$channelName])) { - $channelClass = self::channel($channelName); - $channel = new $channelClass(); - if (is_subclass_of($notification, Hm_ShouldQueue::class)) { - $driver = $notification->driver; - $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); - if ($queueDriver) { - $queueDriver->push($notification); - } else { - throw new \Exception("Queue driver {$driver} not found."); - } - } else { - // Send notification immediately if not queued - $channel->send($notification); - } - } else { - throw new \Exception("Channel {$channelName} is not registered or implemented."); - } + $driver = $notification->driver; + $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); + if ($queueDriver) { + $queueDriver->push($notification); + } else { + throw new \Exception("Queue driver {$driver} not found."); } } diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php index 426f5e6f43..b7d3dab2b1 100644 --- a/services/Notifications/Hm_NewMailNotification.php +++ b/services/Notifications/Hm_NewMailNotification.php @@ -11,15 +11,10 @@ class Hm_NewMailNotification extends Hm_Notification implements Hm_ShouldQueue { use Hm_Dispatchable, Hm_InteractsWithQueue; - public string $driver = 'redis'; - - // public function __construct(array $config = []) - // { - // parent::__construct($config); - // } + public string $driver = 'database'; public function via(): array { - return ['telegram'];//, 'slack', 'telegram','broadcast' + return ['telegram','slack'];//, 'slack', 'telegram','broadcast' } } From 93beb708cbe4643f5a203f2e5c18972d13762972 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Wed, 23 Apr 2025 15:15:42 +0300 Subject: [PATCH 21/21] Refactor the service removing Hm_ namespace usage --- console | 2 +- ...ckMailCommand.php => CheckMailCommand.php} | 10 +++--- services/Contracts/{Hm_Job.php => Job.php} | 2 +- .../{Hm_Dispatcher.php => Dispatcher.php} | 2 +- .../{Hm_Factory.php => Factory.php} | 8 ++--- services/Contracts/Queue/Hm_Queueable.php | 10 ------ services/Contracts/Queue/Hm_ShouldQueue.php | 12 ------- services/Contracts/Queue/Queueable.php | 10 ++++++ services/Contracts/Queue/ShouldQueue.php | 12 +++++++ .../{Hm_BaseCommand.php => BaseCommand.php} | 2 +- ...ueWorkCommand.php => QueueWorkCommand.php} | 10 +++--- ...eRunCommand.php => ScheduleRunCommand.php} | 8 ++--- ...rkCommand.php => SchedulerWorkCommand.php} | 6 ++-- ...Command.php => WebSocketServerCommand.php} | 12 +++---- .../Core/{Hm_Container.php => Container.php} | 12 +++---- .../{Hm_BaseEvent.php => BaseEvent.php} | 6 ++-- ...ventDispatcher.php => EventDispatcher.php} | 2 +- .../Core/Jobs/{Hm_BaseJob.php => BaseJob.php} | 6 ++-- ...Hm_JobDispatcher.php => JobDispatcher.php} | 14 ++++---- ...adcastChannel.php => BroadcastChannel.php} | 6 ++-- ...ionChannel.php => NotificationChannel.php} | 2 +- .../{Hm_SlackChannel.php => SlackChannel.php} | 6 ++-- ...elegramChannel.php => TelegramChannel.php} | 6 ++-- ...Hm_TwilioChannel.php => TwilioChannel.php} | 8 ++--- ...Hm_VonageChannel.php => VonageChannel.php} | 8 ++--- .../{Hm_Notification.php => Notification.php} | 10 +++--- ...patcher.php => NotificationDispatcher.php} | 26 +++++++-------- ...ebSocketServer.php => WebSocketServer.php} | 2 +- ..._AmazonSQSQueue.php => AmazonSQSQueue.php} | 8 ++--- ...Hm_DatabaseQueue.php => DatabaseQueue.php} | 10 +++--- .../{Hm_RedisQueue.php => RedisQueue.php} | 8 ++--- .../{Hm_QueueManager.php => QueueManager.php} | 8 ++--- .../{Hm_QueueWorker.php => QueueWorker.php} | 8 ++--- .../Queue/{Hm_Queueable.php => Queueable.php} | 2 +- .../{Hm_CacheMutex.php => CacheMutex.php} | 2 +- .../{Hm_CommandTask.php => CommandTask.php} | 4 +-- ...Hm_ScheduledTask.php => ScheduledTask.php} | 6 ++-- .../{Hm_Scheduler.php => Scheduler.php} | 10 +++--- ...edEvent.php => NewEmailProcessedEvent.php} | 8 ++--- ...rocessNewEmail.php => ProcessNewEmail.php} | 22 ++++++------- services/{Hm_Kernel.php => Kernel.php} | 6 ++-- ..._NewMaiListener.php => NewMaiListener.php} | 2 +- .../Notifications/Hm_NewMailNotification.php | 20 ------------ .../Notifications/NewMailNotification.php | 20 ++++++++++++ ...rovider.php => CommandServiceProvider.php} | 6 ++-- ...eProvider.php => EventServiceProvider.php} | 14 ++++---- ...eProvider.php => QueueServiceProvider.php} | 32 +++++++++---------- ...vider.php => SchedulerServiceProvider.php} | 14 ++++---- .../{Hm_Dispatchable.php => Dispatchable.php} | 26 +++++++-------- ...tsWithQueue.php => InteractsWithQueue.php} | 18 +++++------ .../{Hm_Queueable.php => Queueable.php} | 2 +- ...nager.php => ScheduleFrequencyManager.php} | 2 +- .../{Hm_Serializes.php => Serializes.php} | 2 +- services/{Hm_bootstrap.php => bootstrap.php} | 10 +++--- services/readme.rd | 2 +- tests/phpunit/services/mocks.php | 4 +-- 56 files changed, 248 insertions(+), 248 deletions(-) rename services/Commands/{Hm_CheckMailCommand.php => CheckMailCommand.php} (81%) rename services/Contracts/{Hm_Job.php => Job.php} (86%) rename services/Contracts/Notifications/{Hm_Dispatcher.php => Dispatcher.php} (95%) rename services/Contracts/Notifications/{Hm_Factory.php => Factory.php} (70%) delete mode 100644 services/Contracts/Queue/Hm_Queueable.php delete mode 100644 services/Contracts/Queue/Hm_ShouldQueue.php create mode 100644 services/Contracts/Queue/Queueable.php create mode 100644 services/Contracts/Queue/ShouldQueue.php rename services/Core/Commands/{Hm_BaseCommand.php => BaseCommand.php} (98%) rename services/Core/Commands/{Hm_QueueWorkCommand.php => QueueWorkCommand.php} (88%) rename services/Core/Commands/{Hm_ScheduleRunCommand.php => ScheduleRunCommand.php} (84%) rename services/Core/Commands/{Hm_SchedulerWorkCommand.php => SchedulerWorkCommand.php} (94%) rename services/Core/Commands/{Hm_WebSocketServerCommand.php => WebSocketServerCommand.php} (85%) rename services/Core/{Hm_Container.php => Container.php} (84%) rename services/Core/Events/{Hm_BaseEvent.php => BaseEvent.php} (74%) rename services/Core/Events/{Hm_EventDispatcher.php => EventDispatcher.php} (96%) rename services/Core/Jobs/{Hm_BaseJob.php => BaseJob.php} (57%) rename services/Core/Jobs/{Hm_JobDispatcher.php => JobDispatcher.php} (62%) rename services/Core/Notifications/Channels/{Hm_BroadcastChannel.php => BroadcastChannel.php} (93%) rename services/Core/Notifications/Channels/{Hm_NotificationChannel.php => NotificationChannel.php} (75%) rename services/Core/Notifications/Channels/{Hm_SlackChannel.php => SlackChannel.php} (89%) rename services/Core/Notifications/Channels/{Hm_TelegramChannel.php => TelegramChannel.php} (90%) rename services/Core/Notifications/Channels/{Hm_TwilioChannel.php => TwilioChannel.php} (89%) rename services/Core/Notifications/Channels/{Hm_VonageChannel.php => VonageChannel.php} (86%) rename services/Core/Notifications/{Hm_Notification.php => Notification.php} (89%) rename services/Core/Notifications/{Hm_NotificationDispatcher.php => NotificationDispatcher.php} (65%) rename services/Core/Notifications/{Hm_WebSocketServer.php => WebSocketServer.php} (95%) rename services/Core/Queue/Drivers/{Hm_AmazonSQSQueue.php => AmazonSQSQueue.php} (95%) rename services/Core/Queue/Drivers/{Hm_DatabaseQueue.php => DatabaseQueue.php} (94%) rename services/Core/Queue/Drivers/{Hm_RedisQueue.php => RedisQueue.php} (95%) rename services/Core/Queue/{Hm_QueueManager.php => QueueManager.php} (61%) rename services/Core/Queue/{Hm_QueueWorker.php => QueueWorker.php} (82%) rename services/Core/Queue/{Hm_Queueable.php => Queueable.php} (98%) rename services/Core/Scheduling/{Hm_CacheMutex.php => CacheMutex.php} (98%) rename services/Core/Scheduling/{Hm_CommandTask.php => CommandTask.php} (93%) rename services/Core/Scheduling/{Hm_ScheduledTask.php => ScheduledTask.php} (98%) rename services/Core/Scheduling/{Hm_Scheduler.php => Scheduler.php} (88%) rename services/Events/{Hm_NewEmailProcessedEvent.php => NewEmailProcessedEvent.php} (53%) rename services/Jobs/{Hm_ProcessNewEmail.php => ProcessNewEmail.php} (57%) rename services/{Hm_Kernel.php => Kernel.php} (76%) rename services/Listeners/{Hm_NewMaiListener.php => NewMaiListener.php} (91%) delete mode 100644 services/Notifications/Hm_NewMailNotification.php create mode 100644 services/Notifications/NewMailNotification.php rename services/Providers/{Hm_CommandServiceProvider.php => CommandServiceProvider.php} (95%) rename services/Providers/{Hm_EventServiceProvider.php => EventServiceProvider.php} (59%) rename services/Providers/{Hm_QueueServiceProvider.php => QueueServiceProvider.php} (73%) rename services/Providers/{Hm_SchedulerServiceProvider.php => SchedulerServiceProvider.php} (68%) rename services/Traits/{Hm_Dispatchable.php => Dispatchable.php} (62%) rename services/Traits/{Hm_InteractsWithQueue.php => InteractsWithQueue.php} (76%) rename services/Traits/{Hm_Queueable.php => Queueable.php} (67%) rename services/Traits/{Hm_ScheduleFrequencyManager.php => ScheduleFrequencyManager.php} (99%) rename services/Traits/{Hm_Serializes.php => Serializes.php} (99%) rename services/{Hm_bootstrap.php => bootstrap.php} (88%) diff --git a/console b/console index 31c37b1d0c..87f728eff3 100644 --- a/console +++ b/console @@ -6,7 +6,7 @@ use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\ContainerInterface; -list($containerBuilder, $config) = require __DIR__ . '/services/Hm_bootstrap.php'; +list($containerBuilder, $config) = require __DIR__ . '/services/Hbootstrap.php'; $application = new Application('Cypht Console', '1.0'); diff --git a/services/Commands/Hm_CheckMailCommand.php b/services/Commands/CheckMailCommand.php similarity index 81% rename from services/Commands/Hm_CheckMailCommand.php rename to services/Commands/CheckMailCommand.php index 7a8eb5ee84..14956ba8fb 100644 --- a/services/Commands/Hm_CheckMailCommand.php +++ b/services/Commands/CheckMailCommand.php @@ -2,14 +2,14 @@ namespace Services\Commands; -use Services\Jobs\Hm_ProcessNewEmail; -use Services\Core\Commands\Hm_BaseCommand; -use Services\Notifications\Hm_NewMailNotification; +use Services\Jobs\ProcessNewEmail; +use Services\Core\Commands\BaseCommand; +use Services\Notifications\NewMailNotification; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -class Hm_CheckMailCommand extends Hm_BaseCommand +class CheckMailCommand extends BaseCommand { /** * The name of the command. @@ -37,7 +37,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // $notification = new Hm_NewMailNotification($message); // $notification->sendNow(); - Hm_NewMailNotification::dispatch($message); + NewMailNotification::dispatch($message); // Hm_ProcessNewEmail::dispatch(email: 'muhngesteven@gmail.com'); return Command::SUCCESS; } diff --git a/services/Contracts/Hm_Job.php b/services/Contracts/Job.php similarity index 86% rename from services/Contracts/Hm_Job.php rename to services/Contracts/Job.php index b044ce7e00..2d888b766b 100644 --- a/services/Contracts/Hm_Job.php +++ b/services/Contracts/Job.php @@ -2,7 +2,7 @@ namespace Services\Contracts; -interface Hm_Job +interface Job { public function handle(): void; public function failed(): void; diff --git a/services/Contracts/Notifications/Hm_Dispatcher.php b/services/Contracts/Notifications/Dispatcher.php similarity index 95% rename from services/Contracts/Notifications/Hm_Dispatcher.php rename to services/Contracts/Notifications/Dispatcher.php index ece9939759..7d38f45c6e 100644 --- a/services/Contracts/Notifications/Hm_Dispatcher.php +++ b/services/Contracts/Notifications/Dispatcher.php @@ -2,7 +2,7 @@ namespace Services\Contracts\Notifications; -interface Hm_Dispatcher +interface Dispatcher { /** * Send the given notification to the given notifiable entities. diff --git a/services/Contracts/Notifications/Hm_Factory.php b/services/Contracts/Notifications/Factory.php similarity index 70% rename from services/Contracts/Notifications/Hm_Factory.php rename to services/Contracts/Notifications/Factory.php index 9d935b9bed..c361b0f1ce 100644 --- a/services/Contracts/Notifications/Hm_Factory.php +++ b/services/Contracts/Notifications/Factory.php @@ -2,9 +2,9 @@ namespace Services\Contracts\Notifications; -use Services\Core\Notifications\Hm_Notification; +use Services\Core\Notifications\Notification; -interface Hm_Factory +interface Factory { /** * Get a channel instance by name. @@ -20,7 +20,7 @@ static public function channel(string $name); * @param mixed $notification * @return void */ - static public function send(Hm_Notification $notification): void; + static public function send(Notification $notification): void; /** * Send the given notification immediately. @@ -28,5 +28,5 @@ static public function send(Hm_Notification $notification): void; * @param mixed $notification * @return void */ - static public function sendNow(Hm_Notification $notification): void; + static public function sendNow(Notification $notification): void; } diff --git a/services/Contracts/Queue/Hm_Queueable.php b/services/Contracts/Queue/Hm_Queueable.php deleted file mode 100644 index f17be64dbe..0000000000 --- a/services/Contracts/Queue/Hm_Queueable.php +++ /dev/null @@ -1,10 +0,0 @@ -writeln("Processing jobs from the [$queue] on connection [$connection]..."); if ($input->getOption('once')) { - Hm_Container::getContainer()->get('queue.worker')->work(); + Container::getContainer()->get('queue.worker')->work(); } else { while (true) { - Hm_Container::getContainer()->get('queue.worker')->work(); + Container::getContainer()->get('queue.worker')->work(); sleep($input->getOption('sleep')); } } diff --git a/services/Core/Commands/Hm_ScheduleRunCommand.php b/services/Core/Commands/ScheduleRunCommand.php similarity index 84% rename from services/Core/Commands/Hm_ScheduleRunCommand.php rename to services/Core/Commands/ScheduleRunCommand.php index 3152c8a162..f17b8f44d2 100644 --- a/services/Core/Commands/Hm_ScheduleRunCommand.php +++ b/services/Core/Commands/ScheduleRunCommand.php @@ -2,8 +2,8 @@ namespace Services\Core\Commands; -use Services\Core\Hm_Container; -use Services\Core\Commands\Hm_BaseCommand; +use Services\Core\Container; +use Services\Core\Commands\BaseCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -12,7 +12,7 @@ * Class Hm_ScheduleRunCommand * @package Services\Core\Commands */ -class Hm_ScheduleRunCommand extends Hm_BaseCommand +class ScheduleRunCommand extends BaseCommand { /** * The name of the command. @@ -41,7 +41,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output): int { - $scheduler = Hm_Container::getContainer()->get('scheduler'); + $scheduler = Container::getContainer()->get('scheduler'); $scheduler->run(); $output->writeln("All due scheduled tasks have been executed."); diff --git a/services/Core/Commands/Hm_SchedulerWorkCommand.php b/services/Core/Commands/SchedulerWorkCommand.php similarity index 94% rename from services/Core/Commands/Hm_SchedulerWorkCommand.php rename to services/Core/Commands/SchedulerWorkCommand.php index 52a4ec3a22..c0474fad76 100644 --- a/services/Core/Commands/Hm_SchedulerWorkCommand.php +++ b/services/Core/Commands/SchedulerWorkCommand.php @@ -2,7 +2,7 @@ namespace Services\Core\Commands; -use Services\Core\Hm_Container; +use Services\Core\Container; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -11,7 +11,7 @@ * Class Hm_SchedulerWorkCommand * @package Services\Core\Commands */ -class Hm_SchedulerWorkCommand extends Hm_BaseCommand +class SchedulerWorkCommand extends BaseCommand { /** * The name of the command. @@ -51,7 +51,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output): int { - $scheduler = Hm_Container::getContainer()->get('scheduler'); + $scheduler = Container::getContainer()->get('scheduler'); $output->writeln("Scheduler started. Press Ctrl+C to stop."); if (function_exists('pcntl_signal')) { diff --git a/services/Core/Commands/Hm_WebSocketServerCommand.php b/services/Core/Commands/WebSocketServerCommand.php similarity index 85% rename from services/Core/Commands/Hm_WebSocketServerCommand.php rename to services/Core/Commands/WebSocketServerCommand.php index 8df4f30f8b..1d902601ab 100644 --- a/services/Core/Commands/Hm_WebSocketServerCommand.php +++ b/services/Core/Commands/WebSocketServerCommand.php @@ -4,18 +4,18 @@ use Ratchet\Server\IoServer; use Ratchet\WebSocket\WsServer; -use Services\Core\Hm_Container; -use Services\Core\Commands\Hm_BaseCommand; +use Services\Core\Container; +use Services\Core\Commands\BaseCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Services\Core\Notifications\Hm_WebSocketServer; +use Services\Core\Notifications\WebSocketServer; use Symfony\Component\Console\Output\OutputInterface; /** * Class Hm_WebSocketServerCommand * @package Services\Core\Commands */ -class Hm_WebSocketServerCommand extends Hm_BaseCommand +class WebSocketServerCommand extends BaseCommand { /** * The name of the command. @@ -42,13 +42,13 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output): int { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $broadcastConfig = $config->get('broadcast'); $output->writeln('Starting WebSocket server on '.$broadcastConfig['port'].'...'); $server = IoServer::factory( new WsServer( - new Hm_WebSocketServer() + new WebSocketServer() ), $broadcastConfig['port'] ); diff --git a/services/Core/Hm_Container.php b/services/Core/Container.php similarity index 84% rename from services/Core/Hm_Container.php rename to services/Core/Container.php index 8e549cbe5b..9e6eab4e2f 100644 --- a/services/Core/Hm_Container.php +++ b/services/Core/Container.php @@ -6,13 +6,13 @@ use Hm_Redis; use Hm_AmazonSQS; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Services\Providers\{Hm_CommandServiceProvider, Hm_EventServiceProvider, Hm_SchedulerServiceProvider, Hm_QueueServiceProvider}; +use Services\Providers\{CommandServiceProvider, EventServiceProvider, SchedulerServiceProvider, QueueServiceProvider}; /** * Class Hm_Container * @package Services\Core */ -class Hm_Container +class Container { private static $container = null; @@ -66,17 +66,17 @@ public static function bind(): ContainerBuilder } // Register Hm_CommandServiceProvider - self::$container->register('command.serviceProvider', Hm_CommandServiceProvider::class) + self::$container->register('command.serviceProvider', CommandServiceProvider::class) ->setPublic(true); // Register Hm_QueueServiceProvider - self::$container->register('queue.ServiceProvider', Hm_QueueServiceProvider::class) + self::$container->register('queue.ServiceProvider', QueueServiceProvider::class) ->setPublic(true); - self::$container->register('scheduler.ServiceProvider', Hm_SchedulerServiceProvider::class) + self::$container->register('scheduler.ServiceProvider', SchedulerServiceProvider::class) ->setPublic(true); - self::$container->register('event.ServiceProvider', Hm_EventServiceProvider::class) + self::$container->register('event.ServiceProvider', EventServiceProvider::class) ->setPublic(true); return self::$container; diff --git a/services/Core/Events/Hm_BaseEvent.php b/services/Core/Events/BaseEvent.php similarity index 74% rename from services/Core/Events/Hm_BaseEvent.php rename to services/Core/Events/BaseEvent.php index 0cc70fd52f..2545c73b71 100644 --- a/services/Core/Events/Hm_BaseEvent.php +++ b/services/Core/Events/BaseEvent.php @@ -2,11 +2,11 @@ namespace Services\Core\Events; -use Services\Traits\Hm_Serializes; +use Services\Traits\Serializes; -abstract class Hm_BaseEvent +abstract class BaseEvent { - use Hm_Serializes; + use Serializes; protected array $params; diff --git a/services/Core/Events/Hm_EventDispatcher.php b/services/Core/Events/EventDispatcher.php similarity index 96% rename from services/Core/Events/Hm_EventDispatcher.php rename to services/Core/Events/EventDispatcher.php index 26999f4f21..0c56ac9c7b 100644 --- a/services/Core/Events/Hm_EventDispatcher.php +++ b/services/Core/Events/EventDispatcher.php @@ -2,7 +2,7 @@ namespace Services\Core\Events; -class Hm_EventDispatcher +class EventDispatcher { protected static array $listeners = []; diff --git a/services/Core/Jobs/Hm_BaseJob.php b/services/Core/Jobs/BaseJob.php similarity index 57% rename from services/Core/Jobs/Hm_BaseJob.php rename to services/Core/Jobs/BaseJob.php index dbced8d9b1..e61bb3dd74 100644 --- a/services/Core/Jobs/Hm_BaseJob.php +++ b/services/Core/Jobs/BaseJob.php @@ -2,10 +2,10 @@ namespace Services\Core\Jobs; -use Services\Contracts\Hm_Job; -use Services\Core\Queue\Hm_Queueable; +use Services\Contracts\Job; +use Services\Core\Queue\Queueable; -abstract class Hm_BaseJob extends Hm_Queueable implements Hm_Job +abstract class BaseJob extends Queueable implements Job { public function __construct(protected array $data = []) { $this->data = $data; diff --git a/services/Core/Jobs/Hm_JobDispatcher.php b/services/Core/Jobs/JobDispatcher.php similarity index 62% rename from services/Core/Jobs/Hm_JobDispatcher.php rename to services/Core/Jobs/JobDispatcher.php index db6af8f369..d0d33abde8 100644 --- a/services/Core/Jobs/Hm_JobDispatcher.php +++ b/services/Core/Jobs/JobDispatcher.php @@ -2,16 +2,16 @@ namespace Services\Core\Jobs; -use Services\Core\Hm_Container; -use Services\Core\Jobs\Hm_BaseJob; -use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Container; +use Services\Core\Jobs\BaseJob; +use Services\Contracts\Queue\ShouldQueue; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Hm_JobDispatcher * @package Services\Queue */ -class Hm_JobDispatcher +class JobDispatcher { /** * Dispatch the job to the queue @@ -20,10 +20,10 @@ class Hm_JobDispatcher * @param string|null $queue * @return void */ - static public function dispatch(Hm_BaseJob $job): void { - if (is_subclass_of($job, Hm_ShouldQueue::class)) { + static public function dispatch(BaseJob $job): void { + if (is_subclass_of($job, ShouldQueue::class)) { $driver = $job->driver; - $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); + $queueDriver = Container::getContainer()->get('queue.manager')->getDriver($driver); if ($queueDriver) { $queueDriver->push($job); } else { diff --git a/services/Core/Notifications/Channels/Hm_BroadcastChannel.php b/services/Core/Notifications/Channels/BroadcastChannel.php similarity index 93% rename from services/Core/Notifications/Channels/Hm_BroadcastChannel.php rename to services/Core/Notifications/Channels/BroadcastChannel.php index 7dd3797f73..a63b1df99f 100644 --- a/services/Core/Notifications/Channels/Hm_BroadcastChannel.php +++ b/services/Core/Notifications/Channels/BroadcastChannel.php @@ -4,9 +4,9 @@ use Ratchet\Client\WebSocket; use Ratchet\Client\Connector; -use Services\Core\Hm_Container; +use Services\Core\Container; -class Hm_BroadcastChannel extends Hm_NotificationChannel +class BroadcastChannel extends NotificationChannel { protected $wsClient; protected $connector; @@ -15,7 +15,7 @@ class Hm_BroadcastChannel extends Hm_NotificationChannel public function __construct() { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $broadcastConfig = $config->get('broadcast'); $this->port = $broadcastConfig['port']; $this->connector = new Connector(); diff --git a/services/Core/Notifications/Channels/Hm_NotificationChannel.php b/services/Core/Notifications/Channels/NotificationChannel.php similarity index 75% rename from services/Core/Notifications/Channels/Hm_NotificationChannel.php rename to services/Core/Notifications/Channels/NotificationChannel.php index 43bd2f4609..0e0f7b65bc 100644 --- a/services/Core/Notifications/Channels/Hm_NotificationChannel.php +++ b/services/Core/Notifications/Channels/NotificationChannel.php @@ -2,7 +2,7 @@ namespace Services\Core\Notifications\Channels; -abstract class Hm_NotificationChannel +abstract class NotificationChannel { abstract public function send($notification): void; } diff --git a/services/Core/Notifications/Channels/Hm_SlackChannel.php b/services/Core/Notifications/Channels/SlackChannel.php similarity index 89% rename from services/Core/Notifications/Channels/Hm_SlackChannel.php rename to services/Core/Notifications/Channels/SlackChannel.php index f38da64be2..0142b6ee36 100644 --- a/services/Core/Notifications/Channels/Hm_SlackChannel.php +++ b/services/Core/Notifications/Channels/SlackChannel.php @@ -2,7 +2,7 @@ namespace Services\Core\Notifications\Channels; -use Services\Core\Hm_Container; +use Services\Core\Container; use Symfony\Component\Notifier\Chatter; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\ChatMessage; @@ -12,7 +12,7 @@ * Class Hm_SlackChannel * @package Services\Core\Notifications\Channels */ -class Hm_SlackChannel extends Hm_NotificationChannel +class SlackChannel extends NotificationChannel { /** * The Chatter instance. @@ -27,7 +27,7 @@ class Hm_SlackChannel extends Hm_NotificationChannel */ public function __construct() { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $slackConfig = $config->get('slack'); $slackToken = $slackConfig['token']; $slackChannel = $slackConfig['channel']; diff --git a/services/Core/Notifications/Channels/Hm_TelegramChannel.php b/services/Core/Notifications/Channels/TelegramChannel.php similarity index 90% rename from services/Core/Notifications/Channels/Hm_TelegramChannel.php rename to services/Core/Notifications/Channels/TelegramChannel.php index c53fc3293a..b7e975533e 100644 --- a/services/Core/Notifications/Channels/Hm_TelegramChannel.php +++ b/services/Core/Notifications/Channels/TelegramChannel.php @@ -2,7 +2,7 @@ namespace Services\Core\Notifications\Channels; -use Services\Core\Hm_Container; +use Services\Core\Container; use Symfony\Component\Notifier\Chatter; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\ChatMessage; @@ -12,7 +12,7 @@ * Class Hm_TelegramChannel * @package Services\Core\Notifications\Channels */ -class Hm_TelegramChannel extends Hm_NotificationChannel +class TelegramChannel extends NotificationChannel { /** * The Chatter instance. @@ -27,7 +27,7 @@ class Hm_TelegramChannel extends Hm_NotificationChannel */ public function __construct() { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $telegramConfig = $config->get('telegram'); $telegramToken = $telegramConfig['bot_token']; $telegramChatId = $telegramConfig['chat_id']; // ID du chat ou username (@username) diff --git a/services/Core/Notifications/Channels/Hm_TwilioChannel.php b/services/Core/Notifications/Channels/TwilioChannel.php similarity index 89% rename from services/Core/Notifications/Channels/Hm_TwilioChannel.php rename to services/Core/Notifications/Channels/TwilioChannel.php index 7075fb1d0c..cd77d804ee 100644 --- a/services/Core/Notifications/Channels/Hm_TwilioChannel.php +++ b/services/Core/Notifications/Channels/TwilioChannel.php @@ -2,7 +2,7 @@ namespace Services\Core\Notifications\Channels; -use Services\Core\Hm_Container; +use Services\Core\Container; use Symfony\Component\Notifier\Chatter; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\SmsMessage; @@ -12,7 +12,7 @@ * Class Hm_TwilioChannel * @package Services\Core\Notifications\Channels */ -class Hm_TwilioChannel extends Hm_NotificationChannel +class TwilioChannel extends NotificationChannel { /** * The Chatter instance. @@ -27,7 +27,7 @@ class Hm_TwilioChannel extends Hm_NotificationChannel */ public function __construct() { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $twilioConfig = $config->get('twilio'); $twilioAccountSid = $twilioConfig['sid']; $twilioAuthToken = $twilioConfig['token']; @@ -58,7 +58,7 @@ public function __construct() */ public function send($notification): void { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $twilioTo = $config->get('twilio')['to']; $smsMessage = new SmsMessage($twilioTo, $notification->getContent()); diff --git a/services/Core/Notifications/Channels/Hm_VonageChannel.php b/services/Core/Notifications/Channels/VonageChannel.php similarity index 86% rename from services/Core/Notifications/Channels/Hm_VonageChannel.php rename to services/Core/Notifications/Channels/VonageChannel.php index 063c25ddb9..7df127e71d 100644 --- a/services/Core/Notifications/Channels/Hm_VonageChannel.php +++ b/services/Core/Notifications/Channels/VonageChannel.php @@ -2,13 +2,13 @@ namespace Services\Core\Notifications\Channels; -use Services\Core\Hm_Container; +use Services\Core\Container; use Symfony\Component\Notifier\Chatter; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; -class Hm_VonageChannel extends Hm_NotificationChannel +class VonageChannel extends NotificationChannel { /** * The Chatter instance. @@ -23,7 +23,7 @@ class Hm_VonageChannel extends Hm_NotificationChannel */ public function __construct() { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $vonageConfig = $config->get('vonage'); $vonageApiKey = $vonageConfig['api_key']; @@ -49,7 +49,7 @@ public function __construct() */ public function send($notification): void { - $config = Hm_Container::getContainer()->get('config'); + $config = Container::getContainer()->get('config'); $vonageTo = $config->get('vonage')['to']; $smsMessage = new SmsMessage($vonageTo, $notification->getContent()); diff --git a/services/Core/Notifications/Hm_Notification.php b/services/Core/Notifications/Notification.php similarity index 89% rename from services/Core/Notifications/Hm_Notification.php rename to services/Core/Notifications/Notification.php index 04aa5a223c..722ea81ba7 100644 --- a/services/Core/Notifications/Hm_Notification.php +++ b/services/Core/Notifications/Notification.php @@ -2,17 +2,17 @@ namespace Services\Core\Notifications; -use Services\Traits\Hm_Dispatchable; -use Services\Core\Queue\Hm_Queueable; -use Services\Contracts\Notifications\Hm_Dispatcher; +use Services\Traits\Dispatchable; +use Services\Core\Queue\Queueable; +use Services\Contracts\Notifications\Dispatcher; /** * Notification class * package: Services\Core\Notifications */ -class Hm_Notification extends Hm_Queueable implements Hm_Dispatcher +class Notification extends Queueable implements Dispatcher { - use Hm_Dispatchable; + use Dispatchable; /** * The notification content(message). diff --git a/services/Core/Notifications/Hm_NotificationDispatcher.php b/services/Core/Notifications/NotificationDispatcher.php similarity index 65% rename from services/Core/Notifications/Hm_NotificationDispatcher.php rename to services/Core/Notifications/NotificationDispatcher.php index 93bfaa7149..1727b457a1 100644 --- a/services/Core/Notifications/Hm_NotificationDispatcher.php +++ b/services/Core/Notifications/NotificationDispatcher.php @@ -2,16 +2,16 @@ namespace Services\Core\Notifications; -use Services\Core\Hm_Container; -use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Contracts\Notifications\Hm_Factory; -use Services\Core\Notifications\Channels\{Hm_BroadcastChannel, Hm_SlackChannel, Hm_TwilioChannel, Hm_TelegramChannel, Hm_VonageChannel }; +use Services\Core\Container; +use Services\Contracts\Queue\ShouldQueue; +use Services\Contracts\Notifications\Factory; +use Services\Core\Notifications\Channels\{BroadcastChannel, SlackChannel, TwilioChannel, TelegramChannel, VonageChannel }; /** * Notification dispatcher class * package: Services\Core\Notifications */ -class Hm_NotificationDispatcher implements Hm_Factory +class NotificationDispatcher implements Factory { /** * Mapping of channel names to their respective classes @@ -19,21 +19,21 @@ class Hm_NotificationDispatcher implements Hm_Factory * @var array */ protected static array $channelClasses = [ - 'slack' => Hm_SlackChannel::class, - 'vonage' => Hm_VonageChannel::class, - 'twilio' => Hm_TwilioChannel::class, - 'telegram' => Hm_TelegramChannel::class, - 'broadcast' => Hm_BroadcastChannel::class, + 'slack' => SlackChannel::class, + 'vonage' => VonageChannel::class, + 'twilio' => TwilioChannel::class, + 'telegram' => TelegramChannel::class, + 'broadcast' => BroadcastChannel::class, ]; /** * Dispatch a notification to all registered channels. * * @param Hm_Notification $notification The notification instance */ - public static function send(Hm_Notification $notification): void + public static function send(Notification $notification): void { $driver = $notification->driver; - $queueDriver = Hm_Container::getContainer()->get('queue.manager')->getDriver($driver); + $queueDriver = Container::getContainer()->get('queue.manager')->getDriver($driver); if ($queueDriver) { $queueDriver->push($notification); } else { @@ -46,7 +46,7 @@ public static function send(Hm_Notification $notification): void * * @param BaseNotification $notification The notification instance */ - public static function sendNow(Hm_Notification $notification): void + public static function sendNow(Notification $notification): void { $channels = $notification->via(); foreach ($channels as $channelName) { diff --git a/services/Core/Notifications/Hm_WebSocketServer.php b/services/Core/Notifications/WebSocketServer.php similarity index 95% rename from services/Core/Notifications/Hm_WebSocketServer.php rename to services/Core/Notifications/WebSocketServer.php index 1dc0901b45..a3a0a29b5a 100644 --- a/services/Core/Notifications/Hm_WebSocketServer.php +++ b/services/Core/Notifications/WebSocketServer.php @@ -5,7 +5,7 @@ use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; -class Hm_WebSocketServer implements MessageComponentInterface +class WebSocketServer implements MessageComponentInterface { protected $clients; diff --git a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php b/services/Core/Queue/Drivers/AmazonSQSQueue.php similarity index 95% rename from services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php rename to services/Core/Queue/Drivers/AmazonSQSQueue.php index d64f71bec4..976181823c 100644 --- a/services/Core/Queue/Drivers/Hm_AmazonSQSQueue.php +++ b/services/Core/Queue/Drivers/AmazonSQSQueue.php @@ -4,14 +4,14 @@ use Hm_AmazonSQS; use Aws\Sqs\SqsClient; -use Services\Contracts\Queue\Hm_Queueable; -use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; +use Services\Contracts\Queue\Queueable; +use Services\Contracts\Queue\ShouldQueue; +use Services\Core\Queue\Queueable as Hm_QueueableClass; /** * Amazon SQS Queue */ -class Hm_AmazonSQSQueue implements Hm_ShouldQueue, Hm_Queueable +class AmazonSQSQueue implements ShouldQueue, Queueable { /** * @var Hm_AmazonSQS diff --git a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php b/services/Core/Queue/Drivers/DatabaseQueue.php similarity index 94% rename from services/Core/Queue/Drivers/Hm_DatabaseQueue.php rename to services/Core/Queue/Drivers/DatabaseQueue.php index 03a4aaea41..0b29dbc87d 100644 --- a/services/Core/Queue/Drivers/Hm_DatabaseQueue.php +++ b/services/Core/Queue/Drivers/DatabaseQueue.php @@ -4,16 +4,16 @@ use PDO; use Hm_DB; -use Services\Contracts\Queue\Hm_Queueable; -use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Notifications\Hm_Notification; -use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; +use Services\Contracts\Queue\Queueable; +use Services\Contracts\Queue\ShouldQueue; +use Services\Core\Notifications\Notification; +use Services\Core\Queue\Queueable as Hm_QueueableClass; /** * Class Hm_DatabaseQueue * @package Services\Core\Queue\Drivers */ -class Hm_DatabaseQueue implements Hm_ShouldQueue, Hm_Queueable +class DatabaseQueue implements ShouldQueue, Queueable { protected const FAILED_JOBS_TABLE = 'hm_failed_jobs'; diff --git a/services/Core/Queue/Drivers/Hm_RedisQueue.php b/services/Core/Queue/Drivers/RedisQueue.php similarity index 95% rename from services/Core/Queue/Drivers/Hm_RedisQueue.php rename to services/Core/Queue/Drivers/RedisQueue.php index a3568fa482..7bab77eaf3 100644 --- a/services/Core/Queue/Drivers/Hm_RedisQueue.php +++ b/services/Core/Queue/Drivers/RedisQueue.php @@ -5,14 +5,14 @@ use Redis; use Hm_Redis; use Exception; -use Services\Contracts\Queue\Hm_Queueable; -use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Core\Queue\Hm_Queueable as Hm_QueueableClass; +use Services\Contracts\Queue\Queueable; +use Services\Contracts\Queue\ShouldQueue; +use Services\Core\Queue\Queueable as Hm_QueueableClass; /** * Redis Queue using Hm_Redis with Hm_Cache_Base trait Hm_RedisQueue */ -class Hm_RedisQueue implements Hm_ShouldQueue, Hm_Queueable +class RedisQueue implements ShouldQueue, Queueable { protected Hm_Redis $redis; protected Redis $redisConnection; diff --git a/services/Core/Queue/Hm_QueueManager.php b/services/Core/Queue/QueueManager.php similarity index 61% rename from services/Core/Queue/Hm_QueueManager.php rename to services/Core/Queue/QueueManager.php index 6afb42692b..486a862517 100644 --- a/services/Core/Queue/Hm_QueueManager.php +++ b/services/Core/Queue/QueueManager.php @@ -2,13 +2,13 @@ namespace Services\Core\Queue; -use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Contracts\Queue\ShouldQueue; /** * Class Hm_QueueManager * @package Services\Queue */ -class Hm_QueueManager +class QueueManager { protected array $drivers; @@ -16,12 +16,12 @@ class Hm_QueueManager * Hm_QueueManager constructor. * @param array $drivers */ - public function addDriver(string $name, Hm_ShouldQueue $driver): void + public function addDriver(string $name, ShouldQueue $driver): void { $this->drivers[$name] = $driver; } - public function getDriver(string $name): Hm_ShouldQueue + public function getDriver(string $name): ShouldQueue { return $this->drivers[$name]; } diff --git a/services/Core/Queue/Hm_QueueWorker.php b/services/Core/Queue/QueueWorker.php similarity index 82% rename from services/Core/Queue/Hm_QueueWorker.php rename to services/Core/Queue/QueueWorker.php index 19eccea8f8..3abd4dfbe5 100644 --- a/services/Core/Queue/Hm_QueueWorker.php +++ b/services/Core/Queue/QueueWorker.php @@ -2,21 +2,21 @@ namespace Services\Core\Queue; -use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Contracts\Queue\ShouldQueue; /** * Class Hm_QueueWorker * @package Services\Queue */ -class Hm_QueueWorker +class QueueWorker { - protected Hm_ShouldQueue $queue; + protected ShouldQueue $queue; /** * Hm_QueueWorker constructor. * @param Hm_ShouldQueue $queue */ - public function __construct(Hm_ShouldQueue $queue) + public function __construct(ShouldQueue $queue) { $this->queue = $queue; } diff --git a/services/Core/Queue/Hm_Queueable.php b/services/Core/Queue/Queueable.php similarity index 98% rename from services/Core/Queue/Hm_Queueable.php rename to services/Core/Queue/Queueable.php index fc66c89080..b876e3578e 100644 --- a/services/Core/Queue/Hm_Queueable.php +++ b/services/Core/Queue/Queueable.php @@ -2,7 +2,7 @@ namespace Services\Core\Queue; -class Hm_Queueable +class Queueable { /** * The name of the connection the item should be sent to. diff --git a/services/Core/Scheduling/Hm_CacheMutex.php b/services/Core/Scheduling/CacheMutex.php similarity index 98% rename from services/Core/Scheduling/Hm_CacheMutex.php rename to services/Core/Scheduling/CacheMutex.php index cb5b1ad770..c76252f07e 100644 --- a/services/Core/Scheduling/Hm_CacheMutex.php +++ b/services/Core/Scheduling/CacheMutex.php @@ -5,7 +5,7 @@ use Hm_Cache; use Services\Contracts\Scheduling\Mutex; -class Hm_CacheMutex implements Mutex +class CacheMutex implements Mutex { private $cache; private $expiresAt; diff --git a/services/Core/Scheduling/Hm_CommandTask.php b/services/Core/Scheduling/CommandTask.php similarity index 93% rename from services/Core/Scheduling/Hm_CommandTask.php rename to services/Core/Scheduling/CommandTask.php index 49f1728446..98e6f53ec5 100644 --- a/services/Core/Scheduling/Hm_CommandTask.php +++ b/services/Core/Scheduling/CommandTask.php @@ -2,7 +2,7 @@ namespace Services\Core\Scheduling; -class Hm_CommandTask extends Hm_ScheduledTask +class CommandTask extends ScheduledTask { public $command; private $onOneServer = false; @@ -17,7 +17,7 @@ class Hm_CommandTask extends Hm_ScheduledTask */ public $name; - public function __construct($command, Hm_CacheMutex $mutex, $name = null) + public function __construct($command, CacheMutex $mutex, $name = null) { $this->name = $name ?: $command; $fullCommand = "php console " . $command; diff --git a/services/Core/Scheduling/Hm_ScheduledTask.php b/services/Core/Scheduling/ScheduledTask.php similarity index 98% rename from services/Core/Scheduling/Hm_ScheduledTask.php rename to services/Core/Scheduling/ScheduledTask.php index de8258e548..fd317f612c 100644 --- a/services/Core/Scheduling/Hm_ScheduledTask.php +++ b/services/Core/Scheduling/ScheduledTask.php @@ -2,11 +2,11 @@ namespace Services\Core\Scheduling; -use Services\Traits\Hm_ScheduleFrequencyManager; +use Services\Traits\ScheduleFrequencyManager; -class Hm_ScheduledTask +class ScheduledTask { - use Hm_ScheduleFrequencyManager; + use ScheduleFrequencyManager; /** * The caallback to run diff --git a/services/Core/Scheduling/Hm_Scheduler.php b/services/Core/Scheduling/Scheduler.php similarity index 88% rename from services/Core/Scheduling/Hm_Scheduler.php rename to services/Core/Scheduling/Scheduler.php index d3292880bc..2946c3b2aa 100644 --- a/services/Core/Scheduling/Hm_Scheduler.php +++ b/services/Core/Scheduling/Scheduler.php @@ -5,9 +5,9 @@ use Hm_Msgs; use Hm_Cache; use Hm_Debug; -use Services\Core\Hm_Container; +use Services\Core\Container; -class Hm_Scheduler +class Scheduler { protected $tasks = []; protected $config; @@ -29,8 +29,8 @@ public function __construct($config) */ public function command($command) { - $cache = new Hm_Cache($this->config, Hm_Container::getContainer()->get('session')); - $commandTask = new Hm_CommandTask($command, new Hm_CacheMutex($cache)); + $cache = new Hm_Cache($this->config, Container::getContainer()->get('session')); + $commandTask = new CommandTask($command, new CacheMutex($cache)); $this->tasks[] = $commandTask; return $commandTask; } @@ -47,7 +47,7 @@ public function command($command) */ public function register(callable $callback, $name = '', $description = '', $tags = [], $timezone = 'UTC') { - $task = new Hm_ScheduledTask($callback, $name, $description, $tags, $timezone); + $task = new ScheduledTask($callback, $name, $description, $tags, $timezone); $this->tasks[] = $task; return $task; diff --git a/services/Events/Hm_NewEmailProcessedEvent.php b/services/Events/NewEmailProcessedEvent.php similarity index 53% rename from services/Events/Hm_NewEmailProcessedEvent.php rename to services/Events/NewEmailProcessedEvent.php index 9fd5ce6c85..108d4c5cdd 100644 --- a/services/Events/Hm_NewEmailProcessedEvent.php +++ b/services/Events/NewEmailProcessedEvent.php @@ -2,12 +2,12 @@ namespace Services\Events; -use Services\Traits\Hm_Dispatchable; -use Services\Core\Events\Hm_BaseEvent; +use Services\Traits\Dispatchable; +use Services\Core\Events\BaseEvent; -class Hm_NewEmailProcessedEvent extends Hm_BaseEvent// implements Hm_ShouldQueue +class NewEmailProcessedEvent extends BaseEvent// implements Hm_ShouldQueue { - use Hm_Dispatchable;//Hm_InteractsWithQueue; + use Dispatchable;//Hm_InteractsWithQueue; /** * Create a new event instance. diff --git a/services/Jobs/Hm_ProcessNewEmail.php b/services/Jobs/ProcessNewEmail.php similarity index 57% rename from services/Jobs/Hm_ProcessNewEmail.php rename to services/Jobs/ProcessNewEmail.php index 5e35956811..654fa697ca 100644 --- a/services/Jobs/Hm_ProcessNewEmail.php +++ b/services/Jobs/ProcessNewEmail.php @@ -2,16 +2,16 @@ namespace Services\Jobs; -use Services\Core\Jobs\Hm_BaseJob; -use Services\Traits\Hm_Serializes; -use Services\Traits\Hm_Dispatchable; -use Services\Traits\Hm_InteractsWithQueue; -use Services\Contracts\Queue\Hm_ShouldQueue; -use Services\Events\Hm_NewEmailProcessedEvent; - -class Hm_ProcessNewEmail extends Hm_BaseJob implements Hm_ShouldQueue +use Services\Core\Jobs\BaseJob; +use Services\Traits\Serializes; +use Services\Traits\Dispatchable; +use Services\Traits\InteractsWithQueue; +use Services\Contracts\Queue\ShouldQueue; +use Services\Events\NewEmailProcessedEvent; + +class ProcessNewEmail extends BaseJob implements ShouldQueue { - use Hm_Dispatchable, Hm_InteractsWithQueue, Hm_Serializes; + use Dispatchable, InteractsWithQueue, Serializes; public string $email; /** @@ -33,7 +33,7 @@ public function handle(): void dump("Processing ".self::class); - Hm_NewEmailProcessedEvent::dispatch($this->email); + NewEmailProcessedEvent::dispatch($this->email); } @@ -41,7 +41,7 @@ public function failed(): void { print("Failed to process email for {$this->email}\n"); } - public function process(Hm_BaseJob $job): void + public function process(BaseJob $job): void { } diff --git a/services/Hm_Kernel.php b/services/Kernel.php similarity index 76% rename from services/Hm_Kernel.php rename to services/Kernel.php index 180320c7cb..95761635f5 100644 --- a/services/Hm_Kernel.php +++ b/services/Kernel.php @@ -2,13 +2,13 @@ namespace Services; -use Services\Core\Scheduling\Hm_Scheduler; +use Services\Core\Scheduling\Scheduler; -class Hm_Kernel +class Kernel { protected $scheduler; - public function __construct(Hm_Scheduler $scheduler) + public function __construct(Scheduler $scheduler) { $this->scheduler = $scheduler; diff --git a/services/Listeners/Hm_NewMaiListener.php b/services/Listeners/NewMaiListener.php similarity index 91% rename from services/Listeners/Hm_NewMaiListener.php rename to services/Listeners/NewMaiListener.php index 67c9eaea93..5deb545fa6 100644 --- a/services/Listeners/Hm_NewMaiListener.php +++ b/services/Listeners/NewMaiListener.php @@ -2,7 +2,7 @@ namespace Services\Listeners; -class Hm_NewMaiListener +class NewMaiListener { public function handle(object $event): void { diff --git a/services/Notifications/Hm_NewMailNotification.php b/services/Notifications/Hm_NewMailNotification.php deleted file mode 100644 index b7d3dab2b1..0000000000 --- a/services/Notifications/Hm_NewMailNotification.php +++ /dev/null @@ -1,20 +0,0 @@ - [ - Hm_NewMaiListener::class, + NewEmailProcessedEvent::class => [ + NewMaiListener::class, ], ]; @@ -28,7 +28,7 @@ public function register(): void { foreach ($this->listen as $event => $listeners) { foreach ($listeners as $listener) { - Hm_EventDispatcher::listen($event, $listener); + EventDispatcher::listen($event, $listener); } } } diff --git a/services/Providers/Hm_QueueServiceProvider.php b/services/Providers/QueueServiceProvider.php similarity index 73% rename from services/Providers/Hm_QueueServiceProvider.php rename to services/Providers/QueueServiceProvider.php index 0a937ca483..bf4b4052ce 100644 --- a/services/Providers/Hm_QueueServiceProvider.php +++ b/services/Providers/QueueServiceProvider.php @@ -1,49 +1,49 @@ get('config'); $queueConnection = $config->get('queue_driver'); - $containerBuilder->register('queue.manager', Hm_QueueManager::class) + $containerBuilder->register('queue.manager', QueueManager::class) ->setPublic(true); - $containerBuilder->register('job.dispatcher', Hm_JobDispatcher::class) + $containerBuilder->register('job.dispatcher', JobDispatcher::class) ->addArgument(new Reference('queue.manager')) ->setPublic(true); - $containerBuilder->register('queue.worker', Hm_QueueWorker::class) + $containerBuilder->register('queue.worker', QueueWorker::class) ->addArgument(new Reference('queue.driver.' . $queueConnection)) ->setPublic(true); switch ($queueConnection) { case 'redis': - $containerBuilder->register('queue.driver.redis', Hm_RedisQueue::class) + $containerBuilder->register('queue.driver.redis', RedisQueue::class) ->addArgument(new Reference('redis')) ->addArgument(new Reference('redis.connection')) ->addArgument('default') @@ -52,7 +52,7 @@ public function register() ->addMethodCall('addDriver', ['redis', new Reference('queue.driver.redis')]); break; case 'sqs': - $containerBuilder->register('queue.driver.sqs', Hm_AmazonSQSQueue::class) + $containerBuilder->register('queue.driver.sqs', AmazonSQSQueue::class) ->addArgument(new Reference('amazon.sqs')) ->addArgument(new Reference('amazon.sqs.connection')) ->setPublic(true); @@ -60,7 +60,7 @@ public function register() ->addMethodCall('addDriver', ['sqs', new Reference('queue.driver.sqs')]); break; case 'database': - $containerBuilder->register('queue.driver.database', Hm_DatabaseQueue::class) + $containerBuilder->register('queue.driver.database', DatabaseQueue::class) ->addArgument(new Reference('db')) ->addArgument(new Reference('db.connection')) ->setPublic(true); diff --git a/services/Providers/Hm_SchedulerServiceProvider.php b/services/Providers/SchedulerServiceProvider.php similarity index 68% rename from services/Providers/Hm_SchedulerServiceProvider.php rename to services/Providers/SchedulerServiceProvider.php index b9a219d789..8aa5a35cbd 100644 --- a/services/Providers/Hm_SchedulerServiceProvider.php +++ b/services/Providers/SchedulerServiceProvider.php @@ -3,15 +3,15 @@ namespace Services\Providers; use Hm_Cache; -use Services\Core\Hm_Container; -use Services\Core\Scheduling\Hm_Scheduler; -use Services\Core\Scheduling\Hm_CacheMutex; +use Services\Core\Container; +use Services\Core\Scheduling\Scheduler; +use Services\Core\Scheduling\CacheMutex; /** * Class Hm_SchedulerServiceProvider * @package Services\Providers */ -class Hm_SchedulerServiceProvider +class SchedulerServiceProvider { /** @@ -21,15 +21,15 @@ class Hm_SchedulerServiceProvider */ public function register($config, $session) { - $containerBuilder = Hm_Container::getContainer(); + $containerBuilder = Container::getContainer(); // Initialize Hm_Cache $cache = new Hm_Cache($config, $session); // Create the CacheMutex instance using the cache - $mutex = new Hm_CacheMutex($cache); + $mutex = new CacheMutex($cache); // Create the Scheduler instance, passing in the CacheMutex - $scheduler = new Hm_Scheduler($config); + $scheduler = new Scheduler($config); $containerBuilder->set('scheduler', $scheduler); $containerBuilder->set('mutex', $mutex); diff --git a/services/Traits/Hm_Dispatchable.php b/services/Traits/Dispatchable.php similarity index 62% rename from services/Traits/Hm_Dispatchable.php rename to services/Traits/Dispatchable.php index 4d32dcfe29..513f67de1f 100644 --- a/services/Traits/Hm_Dispatchable.php +++ b/services/Traits/Dispatchable.php @@ -2,14 +2,14 @@ namespace Services\Traits; -use Services\Core\Jobs\Hm_BaseJob; -use Services\Core\Events\Hm_BaseEvent; -use Services\Core\Jobs\Hm_JobDispatcher; -use Services\Core\Events\Hm_EventDispatcher; -use Services\Core\Notifications\Hm_Notification; -use Services\Core\Notifications\Hm_NotificationDispatcher; +use Services\Core\Jobs\BaseJob; +use Services\Core\Events\BaseEvent; +use Services\Core\Jobs\JobDispatcher; +use Services\Core\Events\EventDispatcher; +use Services\Core\Notifications\Notification; +use Services\Core\Notifications\NotificationDispatcher; -trait Hm_Dispatchable +trait Dispatchable { /** * Dispatch the event with the given arguments. @@ -19,12 +19,12 @@ trait Hm_Dispatchable public static function dispatch(...$arguments) { $instance = new static(...$arguments); - if (is_subclass_of($instance, Hm_BaseJob::class)) { - Hm_JobDispatcher::dispatch($instance); - }elseif(is_subclass_of($instance, Hm_BaseEvent::class)){ - Hm_EventDispatcher::dispatch($instance); - }elseif(is_subclass_of($instance, Hm_Notification::class)){ - Hm_NotificationDispatcher::send($instance); + if (is_subclass_of($instance, BaseJob::class)) { + JobDispatcher::dispatch($instance); + }elseif(is_subclass_of($instance, BaseEvent::class)){ + EventDispatcher::dispatch($instance); + }elseif(is_subclass_of($instance, Notification::class)){ + NotificationDispatcher::send($instance); }else{ throw new \Exception("Class must be an instance of Hm_BaseJob or Hm_BaseEvent"); } diff --git a/services/Traits/Hm_InteractsWithQueue.php b/services/Traits/InteractsWithQueue.php similarity index 76% rename from services/Traits/Hm_InteractsWithQueue.php rename to services/Traits/InteractsWithQueue.php index 54b4e1846a..c68635894f 100644 --- a/services/Traits/Hm_InteractsWithQueue.php +++ b/services/Traits/InteractsWithQueue.php @@ -2,14 +2,14 @@ namespace Services\Traits; -use Services\Core\Queue\Hm_Queueable; -use Services\Core\Queue\Hm_QueueManager; -use Services\Contracts\Queue\Hm_ShouldQueue; +use Services\Core\Queue\Queueable; +use Services\Core\Queue\QueueManager; +use Services\Contracts\Queue\ShouldQueue; /** * Trait Hm_InteractsWithQueue */ -trait Hm_InteractsWithQueue +trait InteractsWithQueue { /** * The underlying queue job instance. @@ -24,12 +24,12 @@ trait Hm_InteractsWithQueue * @param string|null $queue Optional name of the queue. * @return void */ - public function push(Hm_Queueable $item, $data = '', $queue = null): void + public function push(Queueable $item, $data = '', $queue = null): void { $driver = $this->getDriver(); // Call the appropriate method from the QueueManager to push the job - (new Hm_QueueManager)->getDriver($driver)->push($this, $data, $queue); + (new QueueManager)->getDriver($driver)->push($this, $data, $queue); echo "Job of type " . get_class($item) . " pushed to the queue successfully.\n"; } @@ -40,12 +40,12 @@ public function push(Hm_Queueable $item, $data = '', $queue = null): void * @param string|null $queue Optional name of the queue. * @return mixed The job from the queue or null if the queue is empty. */ - public function pop($queue = null): ?Hm_Queueable + public function pop($queue = null): ?Queueable { $driver = $this->getDriver(); // Call the appropriate method from the QueueManager to pop a job - $item = (new Hm_QueueManager)->getDriver($driver)->pop($queue); + $item = (new QueueManager)->getDriver($driver)->pop($queue); if ($item) { echo "Job of type " . get_class($item) . " popped from the queue.\n"; @@ -67,7 +67,7 @@ public function release($queue = null, $delay = 0): void { $driver = $this->getDriver(); - (new Hm_QueueManager)->getDriver($driver)->release($this, $queue, $delay); + (new QueueManager)->getDriver($driver)->release($this, $queue, $delay); // echo "Job of type " . get_class($this) . " released back to the queue with a delay of {$delay} seconds.\n"; } diff --git a/services/Traits/Hm_Queueable.php b/services/Traits/Queueable.php similarity index 67% rename from services/Traits/Hm_Queueable.php rename to services/Traits/Queueable.php index 9ff4a22e02..f3599f81be 100644 --- a/services/Traits/Hm_Queueable.php +++ b/services/Traits/Queueable.php @@ -2,6 +2,6 @@ namespace Services\Traits; -trait Hm_Queueable +trait Queueable { } diff --git a/services/Traits/Hm_ScheduleFrequencyManager.php b/services/Traits/ScheduleFrequencyManager.php similarity index 99% rename from services/Traits/Hm_ScheduleFrequencyManager.php rename to services/Traits/ScheduleFrequencyManager.php index 16b6e373e1..22789c787b 100644 --- a/services/Traits/Hm_ScheduleFrequencyManager.php +++ b/services/Traits/ScheduleFrequencyManager.php @@ -2,7 +2,7 @@ namespace Services\Traits; -trait Hm_ScheduleFrequencyManager +trait ScheduleFrequencyManager { /** * The expression representing the event's frequency. diff --git a/services/Traits/Hm_Serializes.php b/services/Traits/Serializes.php similarity index 99% rename from services/Traits/Hm_Serializes.php rename to services/Traits/Serializes.php index 3eac108ca3..e1ed0bf07c 100644 --- a/services/Traits/Hm_Serializes.php +++ b/services/Traits/Serializes.php @@ -5,7 +5,7 @@ use ReflectionClass; use ReflectionProperty; -trait Hm_Serializes +trait Serializes { /** * Prepare the instance for serialization. diff --git a/services/Hm_bootstrap.php b/services/bootstrap.php similarity index 88% rename from services/Hm_bootstrap.php rename to services/bootstrap.php index b217e11070..54f32a72ce 100644 --- a/services/Hm_bootstrap.php +++ b/services/bootstrap.php @@ -1,7 +1,7 @@ set('config', $config); @@ -55,13 +55,13 @@ // list($session, $request) = session_init(); $containerBuilder->set('session', $session); -Hm_Container::bind(); +Container::bind(); // Prepare Kernel instance parameters $queueServiceProvider = $containerBuilder->get('scheduler.ServiceProvider'); $queueServiceProvider->register($config, $session); // Create a new Kernel instance -$kernel = (new Hm_Kernel($containerBuilder->get('scheduler')))->schedule(); +$kernel = (new Kernel($containerBuilder->get('scheduler')))->schedule(); return [$containerBuilder, $config]; diff --git a/services/readme.rd b/services/readme.rd index 0714e9af07..7adb812b7c 100644 --- a/services/readme.rd +++ b/services/readme.rd @@ -16,7 +16,7 @@ $worker->work(); ``` #Adding scheduler -you can use register or command on `$scheduler`, we prepared the class `Hm_Kernel` for that: +you can use register or command on `$scheduler`, we prepared the class `Kernel` for that: ```