diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
new file mode 100644
index 0000000..fe1ae53
--- /dev/null
+++ b/.github/workflows/phpunit.yml
@@ -0,0 +1,38 @@
+name: PHPUnit Tests
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+
+ - name: Cache Composer packages
+ id: composer-cache
+ uses: actions/cache@v3
+ with:
+ path: vendor
+ key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-
+
+ - name: Build the Docker image
+ run: make build
+
+ - name: Install dependencies
+ run: make install
+
+ - name: Run test suite
+ run: make test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index c76a9ff..3f58acd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.php_cs.cache
+.*.cache
/.idea/
/vendor/
composer.lock
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..b33e48c
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,25 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR12' => true,
+ '@Symfony' => true,
+ 'array_indentation' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'concat_space' => ['spacing' => 'one'],
+ 'declare_strict_types' => true,
+ 'string_implicit_backslashes' => true,
+ 'list_syntax' => ['syntax' => 'short'],
+ 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'],
+ 'ordered_imports' => true,
+ 'phpdoc_to_comment' => false,
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']],
+ 'visibility_required' => ['elements' => ['property', 'method', 'const']],
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__ . '/src')
+ ->in(__DIR__ . '/tests')
+ )
+;
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..40d0ebe
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,36 @@
+FROM php:8.4-cli
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y \
+ git \
+ unzip \
+ libssl-dev \
+ libcurl4-openssl-dev \
+ pkg-config \
+ openjdk-17-jre-headless \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install MongoDB extension
+RUN pecl install mongodb \
+ && docker-php-ext-enable mongodb \
+ && docker-php-ext-install pcntl
+
+# Copy Composer from official image
+COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
+
+# Set working directory
+WORKDIR /app
+
+# Copy composer files
+COPY composer.json composer.lock* ./
+
+# Install dependencies including dev dependencies for testing
+RUN composer install --optimize-autoloader
+
+# Copy application code
+COPY . .
+
+# Set environment variable for Composer
+ENV COMPOSER_ALLOW_SUPERUSER=1
+
+CMD ["tail", "-f", "/dev/null"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..30a5db2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+.PHONY: build up down test phpstan fix-cs install update shell logs clean
+
+# Build the Docker image
+build:
+ docker compose build
+
+# Start the services
+up:
+ docker compose up -d
+
+# Stop the services
+down:
+ docker compose down
+
+# Install dependencies
+install:
+ docker compose run --rm php composer install
+
+# Update dependencies
+update:
+ docker compose run --rm php composer update
+
+# Run all tests
+test: up
+ docker compose exec php vendor/bin/phpunit
+
+phpstan: up
+ docker compose exec php vendor/bin/phpstan
+
+fix-cs: up
+ docker compose exec php vendor/bin/php-cs-fixer fix -v
+
+# Open a shell in the PHP container
+shell:
+ docker compose exec php bash
+
+# View logs
+logs:
+ docker compose logs -f php
+
+# Clean up containers and volumes
+clean:
+ docker compose down -v
+ docker compose rm -f
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..5509e13
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,8 @@
+services:
+ php:
+ build: .
+ working_dir: /app
+ volumes:
+ - .:/app
+ environment:
+ - COMPOSER_ALLOW_SUPERUSER=1
diff --git a/composer.json b/composer.json
index bf99c35..556a02a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,31 +1,35 @@
{
"name": "recruiterphp/geezer",
"description": "PHP tools to build robust long-running processes",
+ "license": "MIT",
"type": "library",
- "license": "proprietary",
"authors": [
{
"name": "Easy Welfare Development Team",
"email": "dev@easywelfare.com"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/recruiterphp/geezer/graphs/contributors"
}
],
"require": {
- "php": "^7.2",
+ "php": "^8.4",
"ext-pcntl": "*",
- "symfony/console": "~4.0",
- "recruiterphp/concurrency": "^3.0.0"
+ "recruiterphp/concurrency": "^4.0",
+ "symfony/console": "^7.3"
},
"require-dev": {
- "phpunit/phpunit": "^7",
- "friendsofphp/php-cs-fixer": "^2.13",
- "phpstan/phpstan": "^0.10.5",
- "phpstan/phpstan-phpunit": "^0.10.0"
+ "ergebnis/composer-normalize": "^2.47",
+ "friendsofphp/php-cs-fixer": "^3.85",
+ "phpstan/phpstan": "^2.1",
+ "phpunit/phpunit": "^12.3",
+ "rector/rector": "^2.1"
},
"provide": {
- "easywelfare/geezer-implementation": "2.0.0"
+ "easywelfare/geezer-implementation": "self.version"
},
- "minimum-stability": "dev",
- "prefer-stable": true,
+ "minimum-stability": "stable",
"autoload": {
"psr-4": {
"Recruiter\\Geezer\\": "src",
@@ -36,5 +40,11 @@
"psr-4": {
"Recruiter\\Geezer\\": "tests"
}
+ },
+ "config": {
+ "allow-plugins": {
+ "ergebnis/composer-normalize": true,
+ "sort-packages": true
+ }
}
}
diff --git a/phpstan.neon b/phpstan.neon
index cc85d47..130e9ca 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,8 +1,5 @@
-includes:
- - ./vendor/phpstan/phpstan-phpunit/extension.neon
- - ./vendor/phpstan/phpstan-phpunit/rules.neon
-
parameters:
- level: 7
- excludes_analyse:
- - %currentWorkingDirectory%/vendor/
+ level: 10
+ paths:
+ - src
+ - tests
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..296ff04
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+
+
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..9400286
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,16 @@
+withPaths([
+ __DIR__ . '/src',
+ __DIR__ . '/tests',
+ ])
+ // uncomment to reach your current PHP version
+ // ->withPhpSets()
+ ->withTypeCoverageLevel(0)
+ ->withDeadCodeLevel(0)
+ ->withCodeQualityLevel(0);
diff --git a/src/Command/LeadershipEventsHandler.php b/src/Command/LeadershipEventsHandler.php
index fbcb4b9..8b65124 100644
--- a/src/Command/LeadershipEventsHandler.php
+++ b/src/Command/LeadershipEventsHandler.php
@@ -1,5 +1,7 @@
name());
- $this->wrapped = $wrapped;
- $this->logger = $logger;
$this->setDescription($wrapped->description());
$this->setDefinition($wrapped->definition());
$this->leadershipStatus = null;
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->wrapped->init($input);
$this->registerSignalHandlers();
@@ -77,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
try {
$success = $this->wrapped->execute();
- } catch (Throwable $e) {
+ } catch (\Throwable $e) {
$this->log('Thrown an Exception: ' . get_class($e), ['exception' => $e], LogLevel::ERROR);
$occuredException = $e;
@@ -102,21 +88,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
$leadershipStrategy->release();
if ($occuredException) {
- exit(2);
+ return self::FAILURE;
}
+
+ return self::SUCCESS;
}
- private function leadershipWasAcquired()
+ private function leadershipWasAcquired(): void
{
$this->leadershipStatusChanged(self::LEADERSHIP_STATUS_ACQUIRED, 'leadershipAcquired');
}
- private function leadershipWasLost()
+ private function leadershipWasLost(): void
{
$this->leadershipStatusChanged(self::LEADERSHIP_STATUS_LOST, 'leadershipLost');
}
- private function leadershipStatusChanged($newLeadershipStatus, string $hook)
+ private function leadershipStatusChanged(?string $newLeadershipStatus, string $hook): void
{
if ($this->leadershipStatus === $newLeadershipStatus) {
return;
@@ -134,13 +122,13 @@ private function leadershipStatusChanged($newLeadershipStatus, string $hook)
private function registerSignalHandlers(): void
{
foreach (self::STOP_SIGNALS as $signal) {
- pcntl_signal($signal, function () {
+ pcntl_signal($signal, function (): void {
$this->askedToStop = true;
});
}
}
- private function askedToStop()
+ private function askedToStop(): bool
{
if (!$this->askedToStop) {
pcntl_signal_dispatch();
@@ -152,7 +140,7 @@ private function askedToStop()
private function sleepIfNotAskedToStop(int $milliSeconds): bool
{
$microSeconds = $milliSeconds * 1000;
- for ($i = 0; $i < $microSeconds && !$this->askedToStop(); $i = $i + 50000) {
+ for ($i = 0; $i < $microSeconds && !$this->askedToStop(); $i += 50000) {
usleep(50000);
}
@@ -165,15 +153,16 @@ private function sleepAccordingTo(WaitStrategy $waitStrategy): void
$waitStrategy->next();
}
- private function possiblyCollectGarbage(): int
+ private function possiblyCollectGarbage(): void
{
if (0 === (++$this->garbageCollectorCounter % self::CYCLES_BEFORE_GC)) {
- return gc_collect_cycles();
+ gc_collect_cycles();
}
-
- return 0;
}
+ /**
+ * @param array $extra
+ */
private function log(string $message, array $extra = [], string $level = LogLevel::DEBUG): void
{
$this->logger->log($level, "[{hostname}:{pid}] $message", array_merge([
diff --git a/src/Leadership/Anarchy.php b/src/Leadership/Anarchy.php
index aa5b5cc..b794cf5 100644
--- a/src/Leadership/Anarchy.php
+++ b/src/Leadership/Anarchy.php
@@ -1,5 +1,7 @@
attempt;
}
diff --git a/src/Timing/ExponentialBackoffStrategy.php b/src/Timing/ExponentialBackoffStrategy.php
index 1e3f2c5..4f4c54e 100644
--- a/src/Timing/ExponentialBackoffStrategy.php
+++ b/src/Timing/ExponentialBackoffStrategy.php
@@ -1,23 +1,12 @@
from = $from;
- $this->to = $to;
}
/**
* Number of milliseconds to wait.
- *
- * @return int
*/
public function current(): int
{
@@ -44,7 +29,7 @@ public function current(): int
return intval(min(
2 ** ($this->attempt - 1) * $this->from,
- $this->to
+ $this->to,
));
}
@@ -52,9 +37,6 @@ public function current(): int
* Return the key of the current element.
*
* @see https://php.net/manual/en/iterator.key.php
- *
- * @return int
- *
* @since 5.0.0
*/
public function key(): int
@@ -94,7 +76,7 @@ public function rewind(): void
* @see https://php.net/manual/en/iterator.next.php
* @since 5.0.0
*/
- public function next()
+ public function next(): void
{
++$this->attempt;
}
diff --git a/src/Timing/WaitStrategy.php b/src/Timing/WaitStrategy.php
index 252669e..92bfd44 100644
--- a/src/Timing/WaitStrategy.php
+++ b/src/Timing/WaitStrategy.php
@@ -1,15 +1,16 @@
+ */
+interface WaitStrategy extends \Iterator
{
/**
* Number of milliseconds to wait.
- *
- * @return int
*/
public function current(): int;
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 7a9986c..e2741fe 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,5 +1,7 @@
assertEquals(0, $strategy->current());