Skip to content

Commit 62006d3

Browse files
committed
Add GitHub actions
1 parent dd6b43f commit 62006d3

File tree

8 files changed

+190
-17
lines changed

8 files changed

+190
-17
lines changed

.github/workflows/tests.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Run PHPUnit Tests
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches: [ "main" ]
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: "pages"
16+
cancel-in-progress: false
17+
18+
jobs:
19+
tests:
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Get Composer Cache Directory
26+
id: composer-cache
27+
run: |
28+
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
29+
30+
- name: Cache Composer dependencies
31+
uses: actions/cache@v4
32+
with:
33+
path: ${{ steps.composer-cache.outputs.dir }}
34+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
35+
restore-keys: |
36+
${{ runner.os }}-composer-
37+
38+
- name: Install dependencies
39+
uses: php-actions/composer@v6
40+
env:
41+
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ github.token }}"} }'
42+
with:
43+
php_version: '8.4'
44+
45+
- name: Run PHPUnit tests
46+
uses: php-actions/phpunit@v3
47+
env:
48+
XDEBUG_MODE: coverage
49+
with:
50+
php_version: '8.4'
51+
php_extensions: pcov
52+
53+
- name: Ensure minimum code coverage
54+
env:
55+
MINIMUM_COVERAGE: 80
56+
run: |
57+
COVERAGE=$(php -r '
58+
$xml = new SimpleXMLElement(file_get_contents("public/coverage/clover.xml"));
59+
$m = $xml->project->metrics;
60+
$pct = (int) round(((int) $m["coveredstatements"]) * 100 / (int) $m["statements"]);
61+
echo $pct;
62+
')
63+
echo "Coverage: ${COVERAGE}%"
64+
if [ "${COVERAGE}" -lt ${{ env.MINIMUM_COVERAGE }} ]; then
65+
echo "Code coverage below ${{ env.MINIMUM_COVERAGE }}% threshold."
66+
exit 1
67+
fi
68+
69+
- name: Generate API docs
70+
uses: phpDocumentor/[email protected]
71+
with:
72+
target: 'public/'
73+
74+
- name: Upload artifact
75+
if: github.ref == 'refs/heads/main'
76+
uses: actions/upload-pages-artifact@v3
77+
with:
78+
path: public/
79+
80+
deploy:
81+
if: github.ref == 'refs/heads/main'
82+
needs: tests
83+
environment:
84+
name: github-pages
85+
url: ${{ steps.deployment.outputs.page_url }}
86+
runs-on: ubuntu-latest
87+
steps:
88+
- name: Deploy to GitHub Pages
89+
id: deployment
90+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea/
2+
.phpdoc/
23
public/
34
vendor/
45
composer.lock

composer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
},
2424
"require-dev": {
2525
"coisa/php-cs-fixer": "^2.1",
26+
"phpdocumentor/shim": "^3.8",
2627
"phpspec/prophecy-phpunit": "^2.3",
2728
"phpunit/phpunit": "^9.6 || ^10.5 || ^11.5"
2829
},
@@ -41,7 +42,10 @@
4142
}
4243
},
4344
"config": {
44-
"sort-packages": true
45+
"sort-packages": true,
46+
"allow-plugins": {
47+
"phpdocumentor/shim": true
48+
}
4549
},
4650
"extra": {
4751
"branch-alias": {
@@ -51,6 +55,7 @@
5155
"scripts": {
5256
"cs-check": "php-cs-fixer fix --dry-run --diff",
5357
"cs-fix": "php-cs-fixer fix",
58+
"docs": "phpdoc",
5459
"mutation-testing": "infection --threads=4",
5560
"pre-commit": [
5661
"@cs-check",

phpdoc.dist.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<phpdocumentor
3+
configVersion="3"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns="https://www.phpdoc.org"
6+
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/phpDocumentor/phpDocumentor/master/data/xsd/phpdoc.xsd"
7+
>
8+
<title>phpDocumentor</title>
9+
<paths>
10+
<output>public/phpdoc</output>
11+
</paths>
12+
<version number="3.0.0">
13+
<api>
14+
<source dsn=".">
15+
<path>src</path>
16+
</source>
17+
</api>
18+
</version>
19+
</phpdocumentor>

src/ContainerInterface.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,23 @@
1515

1616
namespace FastForward\Container;
1717

18-
interface ContainerInterface extends \Psr\Container\ContainerInterface {}
18+
use Psr\Container\ContainerInterface as PsrContainerInterface;
19+
20+
/**
21+
* Interface ContainerInterface
22+
*
23+
* Extends the PSR-11 ContainerInterface to provide a consistent, domain-specific container interface
24+
* for the FastForward ecosystem. This interface SHALL serve as the preferred type hint within FastForward
25+
* components, while maintaining full compatibility with PSR-11 standards.
26+
*
27+
* Implementations of this interface MUST adhere to the behavior defined by PSR-11, specifically:
28+
*
29+
* - `get(string $id)` MUST return an entry if available, or throw a `NotFoundExceptionInterface`.
30+
* - `has(string $id)` MUST return true if the entry can be resolved, false otherwise.
31+
*
32+
* This abstraction MAY be extended in the future to incorporate additional container-related functionality
33+
* specific to the FastForward framework, without violating PSR-11 compatibility.
34+
*
35+
* @package FastForward\Container
36+
*/
37+
interface ContainerInterface extends PsrContainerInterface {}

src/Exception/ContainerException.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* Exception type for container-related errors in the event dispatcher.
2424
* This class MUST be used to signal problems occurring during service resolution
2525
* from a container that complies with PSR-11.
26+
*
27+
* @package FastForward\Container\Exception
2628
*/
2729
final class ContainerException extends \Exception implements ContainerExceptionInterface
2830
{

src/ServiceProviderContainer.php

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,61 @@
2121
use Psr\Container\ContainerExceptionInterface;
2222
use Psr\Container\ContainerInterface as PsrContainerInterface;
2323

24+
/**
25+
* Class ServiceProviderContainer
26+
*
27+
* Implements a PSR-11 compliant dependency injection container using a service provider.
28+
*
29+
* This container SHALL resolve services by delegating to the factories and extensions defined in the
30+
* provided ServiceProviderInterface instance. Services are lazily instantiated on first request and
31+
* cached for subsequent retrieval, enforcing singleton-like behavior within the container scope.
32+
*
33+
* The container supports service extension mechanisms by allowing callable extensions to modify or
34+
* enhance services after construction, based on the service identifier or its concrete class name.
35+
*
36+
* If an optional wrapper container is provided, it SHALL be passed to service factories and extensions,
37+
* allowing for delegation or decoration of service resolution. If omitted, the container defaults to itself.
38+
*
39+
* @package FastForward\Container
40+
*/
2441
final class ServiceProviderContainer implements ContainerInterface
2542
{
2643
/**
27-
* @var ServiceProviderInterface provides factories and extensions for service construction
44+
* The service provider supplying factories and extensions for service construction.
45+
*
46+
* This property MUST reference a valid ServiceProviderInterface implementation.
47+
*
48+
* @var ServiceProviderInterface
2849
*/
2950
private ServiceProviderInterface $serviceProvider;
3051

3152
/**
32-
* @var ContainerInterface the underlying container used for delegation, if available
53+
* The container instance used for service resolution and extension application.
54+
*
55+
* This property MAY reference another container for delegation, or default to this container instance.
56+
*
57+
* @var PsrContainerInterface
3358
*/
3459
private PsrContainerInterface $wrapperContainer;
3560

3661
/**
37-
* @var array<string, mixed> cached resolved services by their identifiers
62+
* Cache of resolved services keyed by their identifier or class name.
63+
*
64+
* This array SHALL store already constructed services to enforce singleton-like behavior within the container scope.
65+
*
66+
* @var array<string, mixed>
3867
*/
3968
private array $cache;
4069

70+
/**
71+
* Constructs a new ServiceProviderContainer instance.
72+
*
73+
* This constructor SHALL initialize the container with a service provider and an optional delegating container.
74+
* If no wrapper container is provided, the container SHALL delegate to itself.
75+
*
76+
* @param ServiceProviderInterface $serviceProvider The service provider supplying factories and extensions.
77+
* @param PsrContainerInterface|null $wrapperContainer An optional container for delegation. Defaults to self.
78+
*/
4179
public function __construct(
4280
ServiceProviderInterface $serviceProvider,
4381
?PsrContainerInterface $wrapperContainer = null,
@@ -49,9 +87,10 @@ public function __construct(
4987
/**
5088
* Determines if the container can return an entry for the given identifier.
5189
*
52-
* @param string $id identifier of the entry to look for
90+
* This method MUST return true if the entry exists in the cache or factories, false otherwise.
5391
*
54-
* @return bool true if the entry exists, false otherwise
92+
* @param string $id Identifier of the entry to look for.
93+
* @return bool True if the entry exists, false otherwise.
5594
*/
5695
public function has(string $id): bool
5796
{
@@ -110,20 +149,16 @@ public function get(string $id): mixed
110149
* service instance. If a corresponding extension is found, it MUST be a callable and
111150
* SHALL be invoked with the container and service instance as arguments.
112151
*
113-
* This mechanism allows for post-construction decoration or augmentation of the
114-
* resolved service. Extensions MAY be used to modify or enhance the behavior or state
115-
* of services after they have been created.
116-
*
117-
* Implementations MUST ensure that only valid callables are executed and SHOULD avoid
118-
* side effects beyond service enhancement. If an extension fails or is not callable,
119-
* the method SHALL silently ignore it unless explicitly configured otherwise.
152+
* Extensions MAY be used to modify or enhance services after creation. Invalid extensions
153+
* (non-callables) SHALL be ignored silently.
120154
*
121-
* @param string $id The identifier of the resolved service.
122-
* @param mixed $service The service instance to be extended.
155+
* @param string $id The identifier of the resolved service.
156+
* @param string $class The fully qualified class name of the service.
157+
* @param mixed $service The service instance to apply extensions to.
123158
*
124159
* @return void
125160
*
126-
* @throws ContainerException If any extension fails during invocation.
161+
* @throws ContainerException If an extension callable fails during execution.
127162
*/
128163
private function applyServiceExtensions(string $id, string $class, mixed $service): void
129164
{

src/functions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
* @return ContainerInterface the composed and autowire-enabled container
4545
*
4646
* @throws \InvalidArgumentException if an unsupported initializer type is encountered
47+
*
48+
* @package FastForward\Container
4749
*/
4850
function container(
4951
ConfigInterface|PsrContainerInterface|ServiceProviderInterface|string ...$initializers,

0 commit comments

Comments
 (0)