Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fcde7d5
feat: introduce WP aware container contract and adapter
dpanta94 Jul 1, 2026
a7afb10
fix: pass $actions to array_map
dpanta94 Jul 1, 2026
53d37fc
fix: use same closure across all actions so we can reliably remove it
dpanta94 Jul 1, 2026
09f3e49
chore: pint issues
dpanta94 Jul 1, 2026
d209ff8
docs: add actions docblocks
dpanta94 Jul 1, 2026
98042ae
docs: add package readme file
dpanta94 Jul 1, 2026
1f6bfbd
chore: pint issues
dpanta94 Jul 1, 2026
3a99408
revert: change to container contract @param hinting
dpanta94 Jul 1, 2026
907b55a
chore: fix pint issue
dpanta94 Jul 1, 2026
0e73f8a
feat: added wordpress-stubs
dpanta94 Jul 1, 2026
d947f4f
chore: static analysis
dpanta94 Jul 1, 2026
f2e9383
refactor: amend CR comments
dpanta94 Jul 1, 2026
f9b9785
tweak: make a callback static
dpanta94 Jul 1, 2026
c91e577
refactor: rename method get_container to getContainer
dpanta94 Jul 1, 2026
986ba7a
Revert "tweak: make a callback static"
dpanta94 Jul 1, 2026
2384e22
tweak: fix some typos
dpanta94 Jul 1, 2026
685e81d
fix: added missed semicolon
dpanta94 Jul 1, 2026
964ef77
tweak: rename default hook prefix
dpanta94 Jul 1, 2026
72b86a6
fix: require 1.1 of the foundation container
dpanta94 Jul 1, 2026
5631536
chore: pint issue
dpanta94 Jul 1, 2026
0863587
feat: bump di52 to 4.1
dpanta94 Jul 1, 2026
6accd50
feat: added mergeArrayVar method
dpanta94 Jul 1, 2026
a6d2dd9
chore: pint issue
dpanta94 Jul 1, 2026
ade2a4d
docs: fix binding
dpanta94 Jul 1, 2026
864a271
docs: amend change in method names
dpanta94 Jul 1, 2026
46e8806
chore: pint issue
dpanta94 Jul 1, 2026
ce6901c
tests: adding integration coverage
dpanta94 Jul 1, 2026
b97b473
tests: add unit coverage for the WP container
dpanta94 Jul 1, 2026
5801531
tests: mergeArrayVar method coverage added
dpanta94 Jul 1, 2026
b16295b
tests: fix counting issue
dpanta94 Jul 1, 2026
f67bc15
chore: pint fixes
dpanta94 Jul 1, 2026
b369a63
Fix README.md
defunctl Jul 1, 2026
a23eca6
Ignore dead catches in tests
defunctl Jul 1, 2026
ee85172
Fix README.md example
defunctl Jul 1, 2026
e71fe90
normalize $prefix, do a more strict identifier check, validate action…
defunctl Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Foundation is a StellarWP Composer monorepo for reusable PHP packages intended f

## Repositories
- [stellarwp/foundation-container](https://github.com/stellarwp/foundation-container)
- [stellarwp/foundation-container-wordpress](https://github.com/stellarwp/foundation-container-wordpress)
- [stellarwp/foundation-pipeline](https://github.com/stellarwp/foundation-pipeline)
- [stellarwp/foundation-log](https://github.com/stellarwp/foundation-log)
- [stellarwp/foundation-wpcli](https://github.com/stellarwp/foundation-wpcli)
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"ext-curl": "*",
"ext-exif": "*",
"adbario/php-dot-notation": ">=2.5",
"lucatume/di52": ">=3.0",
"lucatume/di52": ">=4.1",
"monolog/monolog": "^2.11",
"psr/log": ">=1.0",
"stellarwp/container-contract": "^1.1",
Expand All @@ -25,6 +25,7 @@
"monorepo-php/monorepo": "^12.7",
"nunomaduro/collision": "^8.9",
"php-mock/php-mock-mockery": "^1.5",
"php-stubs/wordpress-stubs": ">=6.0",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.2",
"phpunit/phpunit": "^11.5",
Expand All @@ -34,6 +35,7 @@
"replace": {
"stellarwp/foundation-cli": "self.version",
"stellarwp/foundation-container": "self.version",
"stellarwp/foundation-container-wordpress": "self.version",
"stellarwp/foundation-log": "self.version",
"stellarwp/foundation-pipeline": "self.version",
"stellarwp/foundation-wpcli": "self.version"
Expand All @@ -43,6 +45,7 @@
"autoload": {
"psr-4": {
"StellarWP\\Foundation\\Cli\\": "src/Cli/",
"StellarWP\\Foundation\\ContainerWordPress\\": "src/ContainerWordPress/",
"StellarWP\\Foundation\\Container\\": "src/Container/",
"StellarWP\\Foundation\\Log\\": "src/Log/",
"StellarWP\\Foundation\\Pipeline\\": "src/Pipeline/",
Expand Down
6 changes: 6 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ parameters:
paths:
- src
- tests
scanFiles:
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
excludePaths:
analyse:
- src/*/vendor/*
- tests/CodeceptionSupport/*
ignoreErrors:
-
identifier: catch.neverThrown
path: tests/*
11 changes: 10 additions & 1 deletion src/Container/ContainerAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function get(string $id): mixed {
/**
* @codeCoverageIgnore
*/
public function get_container(): DI52Container {
public function getContainer(): DI52Container {
return $this->container;
}

Expand Down Expand Up @@ -95,6 +95,15 @@ public function instance(mixed $id, array $buildArgs = [], ?array $afterBuildMet
return $this->container->instance($id, $buildArgs, $afterBuildMethods);
}

/**
* {@inheritDoc}
*
* @throws ContainerException
*/
public function mergeArrayVar(string $id, mixed $implementation): void {
$this->container->mergeArrayVar($id, $implementation);
}

/**
* @param class-string|string|object $id
*
Expand Down
9 changes: 9 additions & 0 deletions src/Container/Contracts/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ public function needs(string $id): Container;

public function give(mixed $implementation): void;

/**
* Add array values to an existing or future binding without replacing previous values.
*
* @param class-string|string $id
*
* @throws \lucatume\DI52\ContainerException
*/
public function mergeArrayVar(string $id, mixed $implementation): void;

/**
* Returns a callable object (Closure) that will build an instance of the specified
* class using the specified arguments when called.
Expand Down
2 changes: 1 addition & 1 deletion src/Container/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"require": {
"php": ">=8.3",
"adbario/php-dot-notation": ">=2.5",
"lucatume/di52": ">=3.0",
"lucatume/di52": ">=4.1",
"stellarwp/container-contract": "^1.1",
"vlucas/phpdotenv": ">=4.3"
},
Expand Down
7 changes: 7 additions & 0 deletions src/ContainerWordPress/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore paths when git creates an archive of this package
.gitattributes export-ignore
.gitignore export-ignore
.github export-ignore
13 changes: 13 additions & 0 deletions src/ContainerWordPress/.github/workflows/close-pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Close Pull Request

on:
pull_request_target:
types: [opened]

jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: "This is a read-only repository. Please submit your PR on the https://github.com/stellarwp/foundation repository.<br><br>Thanks!"
2 changes: 2 additions & 0 deletions src/ContainerWordPress/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
composer.lock
258 changes: 258 additions & 0 deletions src/ContainerWordPress/ContainerAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<?php declare(strict_types=1);

namespace StellarWP\Foundation\ContainerWordPress;

use Closure;
use InvalidArgumentException;
use lucatume\DI52\Container as DI52Container;
use lucatume\DI52\ContainerException;
use StellarWP\Foundation\Container\ContainerAdapter as FoundationContainerAdapter;
use StellarWP\Foundation\Container\Contracts\Providable;
use StellarWP\Foundation\ContainerWordPress\Contracts\Container;

/**
* WordPress-aware container adapter.
*
* Wraps the Foundation container so WordPress projects keep the full base
* container API and gain WordPress-specific helpers. Add WordPress-specific
* methods here alongside the matching signatures on {@see Container}.
*
* @method mixed make(string $id)
* @method mixed getVar(string $key, mixed|null $default = null)
* @method void singletonDecorators($id, array<string> $decorators, ?array<string> $afterBuildMethods = null)
* @method void bindDecorators($id, array<string> $decorators, ?array<string> $afterBuildMethods = null)
*/
final readonly class ContainerAdapter implements Container
{
private FoundationContainerAdapter $container;

private string $prefix;

public function __construct(
FoundationContainerAdapter $container,
string $prefix = 'nexcess/foundation/container/wp/',
) {
$this->container = $container;
$this->prefix = $prefix === '' ? '' : rtrim($prefix, '/') . '/';
}

/**
* Build the "registered" WordPress action name for a service provider or alias.
*
* @param string $identifier The service provider class or alias slug.
*
Comment thread
defunctl marked this conversation as resolved.
* @throws InvalidArgumentException When the provided $identifier is empty.
*
* @return non-empty-string
*/
private function registeredAction(string $identifier): string {
if ($identifier === '') {
throw new InvalidArgumentException('You need to provide an identifier!');
}

return $this->prefix . $identifier . '/registered';
}

/**
* {@inheritDoc}
*/
public function register(string $serviceProviderClass, ...$alias): void {
$this->registeredAction($serviceProviderClass);

foreach ($alias as $slug) {
$this->registeredAction($slug);
}

$this->container->register($serviceProviderClass, ...$alias);

/**
* Fires after a service provider has been registered in the container.
*
* The dynamic portion of the hook name, `$serviceProviderClass`, refers to the
* fully-qualified class name of the registered service provider.
*
* @param class-string<Providable> $serviceProviderClass The registered service provider class.
* @param string[] $alias The aliases the provider was registered under.
*/
do_action($this->registeredAction($serviceProviderClass), $serviceProviderClass, $alias);

foreach ($alias as $slug) {
/**
* Fires after a service provider has been registered, once per alias.
*
* The dynamic portion of the hook name, `$slug`, refers to an alias the
* service provider was registered under.
*
* @param class-string<Providable> $serviceProviderClass The registered service provider class.
* @param string[] $alias The aliases the provider was registered under.
*/
do_action($this->registeredAction($slug), $serviceProviderClass, $alias);
}
}

/**
* {@inheritDoc}
*/
public function registerAfterAllActions(array $actions, string $serviceProviderClass, ...$alias): void {
$pending = array_values(array_filter($actions, static fn (string $action): bool => ! did_action($action)));

if ($pending === []) {
// All the actions have already fired, register the provider immediately.
$this->register($serviceProviderClass, ...$alias);

return;
}

// A single closure is hooked onto every pending action. Whichever action fires
// last finds all actions done, registers once, and detaches from the rest.
$register_when_ready = function () use ($actions, $pending, $serviceProviderClass, $alias, &$register_when_ready): void {
foreach ($actions as $action) {
if (! did_action($action)) {
return;
}
}

// Detach from every pending action so the provider is only registered once.
foreach ($pending as $action) {
remove_action($action, $register_when_ready);
}

$this->register($serviceProviderClass, ...$alias);
};

foreach ($pending as $action) {
add_action($action, $register_when_ready);
}
}

/**
* {@inheritDoc}
*/
public function registerOnAction(string $action, string $serviceProviderClass, ...$alias): void {
if (did_action($action)) {
// If the action has already fired, register the provider immediately.
$this->register($serviceProviderClass, ...$alias);

return;
}

// If the action has not fired yet, register the provider when/if it does.
$registration_closure = function () use ($action, $serviceProviderClass, $alias, &$registration_closure) {
// Remove the closure from the action to avoid calling it again.
remove_action($action, $registration_closure);
$this->register($serviceProviderClass, ...$alias);
};

add_action($action, $registration_closure);
}

/**
* {@inheritDoc}
*/
public function registerOnProvider(
string $baseProviderClass,
string $dependentProviderClass,
...$alias
): void {
$this->registerOnAction($this->registeredAction($baseProviderClass), $dependentProviderClass, ...$alias);
}

/**
* {@inheritDoc}
*/
public function when(string $class): Container {
$this->container->when($class);

return $this;
}

/**
* {@inheritDoc}
*/
public function needs(string $id): Container {
$this->container->needs($id);

return $this;
}

/**
* @param string[]|null $afterBuildMethods
*
* @throws \lucatume\DI52\ContainerException
*/
public function bind(string $id, mixed $implementation = null, ?array $afterBuildMethods = null): void {
$this->container->bind($id, $implementation, $afterBuildMethods);
}

/**
* {@inheritDoc}
*/
public function get(string $id): mixed {
return $this->container->get($id);
}

/**
* @codeCoverageIgnore
*/
public function getContainer(): DI52Container {
return $this->container->getContainer();
}

/**
* {@inheritDoc}
*
* @codeCoverageIgnore
*/
public function has(string $id): bool {
return $this->container->has($id);
}

/**
* @param string[]|null $afterBuildMethods
*
* @throws \lucatume\DI52\ContainerException
*/
public function singleton(string $id, mixed $implementation = null, ?array $afterBuildMethods = null): void {
$this->container->singleton($id, $implementation, $afterBuildMethods);
}

public function give(mixed $implementation): void {
$this->container->give($implementation);
}

/**
* {@inheritDoc}
*
* @throws ContainerException
*/
public function mergeArrayVar(string $id, mixed $implementation): void {
$this->container->mergeArrayVar($id, $implementation);
}

/**
* @param array<mixed> $buildArgs
* @param string[]|null $afterBuildMethods
*/
public function instance(mixed $id, array $buildArgs = [], ?array $afterBuildMethods = null): Closure {
return $this->container->instance($id, $buildArgs, $afterBuildMethods);
}

/**
* @param class-string|string|object $id
*
* @throws ContainerException
*/
public function callback(object|string $id, string $method): callable {
return $this->container->callback($id, $method);
}

/**
* Defer all other calls to the wrapped Foundation container adapter.
*
* @param string $name The method name.
* @param mixed[] $args Method arguments.
*/
public function __call(string $name, array $args): mixed {
Comment thread
defunctl marked this conversation as resolved.
return $this->container->{$name}(...$args);
}
}
Loading
Loading