feat: initial ACRIB WordPress deployment

- WordPress 6.9.4 (es_ES) with Kadence theme
- Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto
- Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold
- Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI)
- Plugins: Kadence Blocks, Polylang, Contact Form 7
- Custom CSS with full brand styling and responsive layout
- HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
Malin
2026-05-19 19:25:59 +02:00
commit f3ff7b7186
6119 changed files with 1984255 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace KadenceWP\KadenceBlocks\PHPSTORM_META {
$STATIC_METHOD_TYPES = [
\tad_DI52_Container::make( '' ) => [
"" == "@",
],
\KadenceWP\KadenceBlocks\lucatume\DI52\Container::make( '' ) => [
"" == "@",
],
\KadenceWP\KadenceBlocks\lucatume\DI52\Container::get( '' ) => [
"" == "@",
],
\KadenceWP\KadenceBlocks\lucatume\DI52\App::make( '' ) => [
"" == "@",
],
\KadenceWP\KadenceBlocks\lucatume\DI52\App::get( '' ) => [
"" == "@",
],
];
}

View File

@@ -0,0 +1,555 @@
# Change Log
All notable changes to this project will be documented in this file. This project adheres
to [Semantic Versioning](http://semver.org/).
## [unreleased] Unreleased
### Fixed
- Fixed `Container::unsetOffset` method behavior to ensure singletons and when-needs-gives bindings are correctly removed.
## [4.0.0] 2024-12-14;
### Changed
- Bumped minimum supported version to PHP 7.1.
- Fixed PHP 8.4 deprecation warnings.
## [3.3.7] 2024-04-26;
### Fixed
- Correctly bind the container to the builders and resolvers when cloning the container.
## [3.3.6] 2024-04-02;
### Added
- The `afterBuildAll` parameter to the `bindDecorators` and `singletonDecorators` method, fixes #61.
## [3.3.5] 2023-09-01;
### Changed
- Added [PHPStan generics](https://phpstan.org/blog/generics-in-php-using-phpdocs) to multiple classes. IDEs that support them will get autocompletion (.phpstorm.meta.php not required) if a fully-qualified class name is used, e.g. `$instance = $container->get( Test::class );`.
## [3.3.4] 2023-06-20;
### Added
- Implement the `Container::__clone` method to clone the container accessory classes correctly upon cloning.
### Changed
- The `Container::__construct` method will bind itself to the `Psr\Container\ContainerInterface` interface as a singleton.
- If the `Container` class is extended, it will bind itself to the extended class in the `__construct` and `__clone` methods.
## [3.3.3] 2023-04-28;
### Fixed
- Return value of the `App::callback` method to be `callable` (thanks @estevao90).
## [3.3.2] 2023-04-07;
### Fixed
- Set the correct return type for the `Container::callback` function (thanks @estevao90).
## [3.3.1] 2023-03-17;
### Fixed
- Allow bound interfaces to properly resolve their concrete class when the concrete class has contextual bindings (thanks @defunctl). e.g. the following will now work properly:
```php
$container = new \lucatume\DI52\Container();
$container->bind(SomeInterface::class, SomeClass::class);
$container->when(Someclass:class)
->needs('$num')
->give(20);
// This now works, properly resolving SomeClass::class.
$instance = $container->get(SomeInterface::class);
```
## [3.3.0] 2023-02-28;
### Breaking change
- Removed the `aliases.php` file from the package autoloading. Take a look at the README.md file to see how to gracefully upgrade to version 3.3
## [3.2.1] 2023-02-28;
### Fixed
- Correctly resolve unbound parameters to default value when available.
## [3.2.0] 2023-02-27;
### Changed
- Allow customization of rethrown exceptions catpured during container resolution; alter message, file and line by default.
### Fixed
- Some `App` generated methods signatures.
## [3.1.1] 2023-02-16;
### Changed
- Removed leftover `@since TBD` comments.
## [3.1.0] 2023-01-28;
### Added
- PHP8.2 support.
- Parameter detection support for PHP Union Types.
- Parameter Enum detection/Enum container resolving.
- thanks @defunctl.
### Changed
- Add PHP8.2 to GitHub workflow matrix.
- Separated tests into a `unit` suite and a `php81` suite to avoid fatal parse errors when asserting enums.
- Updated GitHub workflows to remove deprecated functionality and run composer install via https://github.com/ramsey/composer-install.
- Updated GitHub workflows to attempt to automatically migrate the phpunit configuration file based on the current version being run.
- Updated deprecated "actions/checkout" GitHub action from v2 to v3.
- thanks @defunctl.
### Fixed
- Use the correct PHP version ID to ensure `PHP81ContextualBindingContainerTest` runs under PHP8.1.
- Fixed the phpunit.xml schema to validate against phpunit 5.7.
- Fatal Error Handling snapshots.
- thanks @defunctl.
## [3.0.3] 2023-01-24;
### Added
- The container now registers Service Providers using its own `Container::get()` method, instead of the `new` keyword. This allows Service Providers to utilize dependency injection. (thanks @defunctl).
- Additional contextual binding examples for primitives + service provider documentation in the README.
## [3.0.2] 2023-01-20;
### Added
- Add support for resolving primitive values (e.g. int, string, bool etc...) using `Container::when()`, `Container::needs()` and `Container::give()` (thanks @defunctl).
## [3.0.1] 2022-11-16;
### Changed
- Add `.gitattributes` file to exclude development artifacts. (thanks @Luc45)
## [3.0.0] 2022-02-09;
### Changed
- Add support for a default value in the `lucatume\DI52\Container::getVar(string $key, mixed $default = null)
:mixed` method.
- The `lucatume\DI52\Container::setVar(string $key, mixed $value) :void` will **not** try to run callables when
storing variables on the container using the method. As such, the need to protect the variables when using
the `setVar()` method is no more required.
- The `lucatume\DI52\Container::tagged(string $tag) :array` method will now return an empty array if nothing was
tagged with the tag; it would throw an error in previous versions.
- Rewritten the code to fully leverage Closure support.
- Move build tools to Docker.
- Make the container implementation compatible with [PSR-11 Container specification](https://www.php-fig.
org/psr/psr-11/)
- Fix #26 to handle and format files syntax errors while trying to autoload.
- Fix #13 and allow explicit definition of default binding method.
- Allow getting the registered provider instances using `getProvider` and `get`, `make` or the `ArrayAccess` API.
- Allow getting callbacks for static and instance methods consistently to unhook.
- Add phstan, phan and phpcs checks.
- Move benchmarks to Docker, automate them.
- Update documentation and examples.
### Removed
- Removed the `tad_DI52_ServiceProviderInterface` and `tad_DI52_ContainerInterface` interfaces.
## [2.1.5] 2021-12-20;
### Fixed
- PHP 8.1 compatibility issues (thanks @bordoni)
## [2.1.4] 2021-01-01;
### Fixed
- PHP 8 compatibility issues (thanks @bordoni)
## [2.1.3] 2020-11-02;
### Fixed
- Error messages and format in the context of nested `make` resolution (thanks @Luc45)
## [2.1.2] 2020-10-27;
### Fixed
- PHP 5.3 and 7.4 incompatibility issues
### Changed
- moved the builds to GitHub Actions
## [2.1.1] 2020-10-23;
### Added
- new build tools to the repository
### Changed
- refactor `Container::callback` code to re-use callbacks when available (thanks @sc0ttkclark)
### Fixed
- fix an issue where the Closure produced by the `callback` method would build the object for static method calls
## [2.1.0] - 2020-07-14
### Added
- support for one parameter singletong binding of concrete, instantiatable, classes, thanks @Luc45
## [2.0.12] - 2019-10-14
### Added
- add PHPStorm make method auto-completion (thanks @Luc45)
## [2.0.11] - 2019-09-26
### Changed
- improve exception throwing to show original exceptions when the building of a bound class, interface or slug fails
## [2.0.10] - 2018-10-29
### Fixed
- an issue with array variable handling
- made error message more clear for non-string offsets
## [2.0.9] - 2017-09-26
### Fixed
- issue with `setVar` method where, in some instances, variable values could not be overridden
## [2.0.8] - 2017-07-18
### Fixed
- check for file existence in autoload script (thanks @truongwp)
## [2.0.7] - 2017-06-15
### Fixed
- issue where non registered classes object dependencies would be built just the first time (issue #2)
## [2.0.6] - 2017-05-09
### Fixed
- fix handling of unbound interface arguments
## [2.0.5] - 2017-02-22
### Changed
- change internal method visibility to improve compatibility with monkey patching libraries
## [2.0.4] - 2017-02-22
### Fixed
- allow unbound classes with `__construct` method requirements to be used in `instance` callbacks
## [2.0.3] - 2017-02-07
### Fixed
- support for use of `callback` to feed `instance` and viceversa
## [2.0.2] - 2017-02-02
### Fixed
- support for built objects in `instance` and `callback` methods
## [2.0.1] - 2017-01-23
### Fixed
- an issue where re-binding implementations could lead to built objects still using previous bindings
#### Changed
- removed some dead code left over from previous iterations
## [2.0.0] - 2017-01-21
### Added
- `instance` and `callback` methods
### Changed
- refactored the code completely
- the README file to update it to the new code
### Removed
- support for array based construction instructions (see `instance` methods)
## [1.4.5] - 2017-01-19
### Fixed
- an issue where singleton resolution would result in circular reference on some Windows versions (thanks @bordoni)
## [1.4.4] - 2017-01-09
### Added
- support for binding replacement
## [1.4.3] - 2016-10-18
### Changed
- snake_case method names are now set to camelCase
### Fixed
- an inheritance issue on PHP 5.2
- non PHP 5.2 compatible tests
### Added
- Travis CI support and build
## [1.4.2] - 2016-10-14
### Fixed
- nested dependency resolution issue with interfaces and default values
## [1.4.1b] - 2016-10-14
### Fixed
- pass the `afterBuildMethods` argument along...
## [1.4.1] - 2016-10-14
### Fixed
- updated `tad_di512_Container` `bind` and `singleton` methods signatures
## [1.4.0] - 2016-10-14
### Added
- more informative exception message when trying to resolve unbound slug or non existing class
- support for after build methods
### Fixed
- another nested dependency resolving issue
## [1.3.2] - 2016-07-28
### Fixed
- nested dependency resolving issue
## [1.3.1] - 2016-04-19
### Added
- more informative exception message when default primitive value is missing
## [1.3.0] - 2016-04-19
### Added
- support for the custom bindings
- support for same class singleton binding
### Changed
- performance optimization
## [1.2.6] - 2016-04-11
### Changed
- internal workings to improve performance (
using [@TomBZombie benchmarks](https://github.com/TomBZombie/php-dependency-injection-benchmarks)
## [1.2.5] - 2016-03-06
### Added
- support for decorator pattern in PHP 5.2 compatible syntax
- code highlighting for code examples in doc (thanks @omarreiss)
## [1.2.4] - 2016-03-05
### Added
- tests for uncovered code
## [1.2.3] - 2016-03-04
### Fixed
- singleton resolution for same implementations
## [1.2.2] - 2016-02-13
- doc updates
## [1.2.1] - 2016-02-13
### Added
- `hasTag($tag)` method to the container
- `isBound($classOrInterface)` method to the container
- support for deferred service providers
## [1.2.1] - 2016-01-23
### Added
- tagging support
- service providers support
## [1.2.0] - 2016-01-22
### Added
- the binding and automatic resolution
API ([code inspiration](https://www.ltconsulting.co.uk/automatic-dependency-injection-with-phps-reflection-api/))
## [1.1.2] - 2016-01-19
### Fixed
- resolution for objects in arrays
## [1.1.1] - 2016-01-19
### Added
- support for the `%varName%` variable notation.
## [1.1.0] - 2016-01-18
### Added
- array resolution support for the Array Access API.
- the changelog.
[1.0.2]: https://github.com/lucatume/di52/compare/1.0.1...1.0.2
[1.0.3]: https://github.com/lucatume/di52/compare/1.0.2...1.0.3
[1.1.0]: https://github.com/lucatume/di52/compare/1.0.3...1.1.0
[1.1.1]: https://github.com/lucatume/di52/compare/1.0.3...1.1.2
[1.1.2]: https://github.com/lucatume/di52/compare/1.0.3...1.1.2
[1.2.0]: https://github.com/lucatume/di52/compare/1.1.2...1.2.0
[1.2.0]: https://github.com/lucatume/di52/compare/1.1.2...1.2.0
[1.2.1]: https://github.com/lucatume/di52/compare/1.2.0...1.2.1
[1.2.2]: https://github.com/lucatume/di52/compare/1.2.1...1.2.2
[1.2.3]: https://github.com/lucatume/di52/compare/1.2.2...1.2.3
[1.2.4]: https://github.com/lucatume/di52/compare/1.2.3...1.2.4
[1.2.5]: https://github.com/lucatume/di52/compare/1.2.4...1.2.5
[1.2.6]: https://github.com/lucatume/di52/compare/1.2.5...1.2.6
[1.3.0]: https://github.com/lucatume/di52/compare/1.2.6...1.3.0
[1.3.1]: https://github.com/lucatume/di52/compare/1.3.0...1.3.1
[1.3.2]: https://github.com/lucatume/di52/compare/1.3.1...1.3.2
[1.4.0]: https://github.com/lucatume/di52/compare/1.3.1...1.4.0
[1.4.1]: https://github.com/lucatume/di52/compare/1.4.0...1.4.1
[1.4.1b]: https://github.com/lucatume/di52/compare/1.4.1...1.4.1b
[1.4.2]: https://github.com/lucatume/di52/compare/1.4.1b...1.4.2
[1.4.3]: https://github.com/lucatume/di52/compare/1.4.2...1.4.3
[1.4.4]: https://github.com/lucatume/di52/compare/1.4.3...1.4.4
[1.4.5]: https://github.com/lucatume/di52/compare/1.4.4...1.4.5
[2.0.0]: https://github.com/lucatume/di52/compare/1.4.5...2.0.0
[2.0.1]: https://github.com/lucatume/di52/compare/2.0.0...2.0.1
[2.0.2]: https://github.com/lucatume/di52/compare/2.0.1...2.0.2
[2.0.3]: https://github.com/lucatume/di52/compare/2.0.2...2.0.3
[2.0.4]: https://github.com/lucatume/di52/compare/2.0.3...2.0.4
[2.0.5]: https://github.com/lucatume/di52/compare/2.0.4...2.0.5
[2.0.6]: https://github.com/lucatume/di52/compare/2.0.5...2.0.6
[2.0.7]: https://github.com/lucatume/di52/compare/2.0.6...2.0.7
[2.0.8]: https://github.com/lucatume/di52/compare/2.0.7...2.0.8
[2.0.9]: https://github.com/lucatume/di52/compare/2.0.8...2.0.9
[2.0.10]: https://github.com/lucatume/di52/compare/2.0.9...2.0.10
[2.0.11]: https://github.com/lucatume/di52/compare/2.0.10...2.0.11
[2.0.12]: https://github.com/lucatume/di52/compare/2.0.11...2.0.12
[2.1.0]: https://github.com/lucatume/di52/compare/2.0.12...2.1.0
[2.1.1]: https://github.com/lucatume/di52/compare/2.1.0...2.1.1
[2.1.2]: https://github.com/lucatume/di52/compare/2.1.1...2.1.2
[2.1.3]: https://github.com/lucatume/di52/compare/2.1.2...2.1.3
[2.1.4]: https://github.com/lucatume/di52/compare/2.1.3...2.1.4
[2.1.5]: https://github.com/lucatume/di52/compare/2.1.4...2.1.5
[3.0.0]: https://github.com/lucatume/di52/compare/2.1.5...3.0.0
[3.0.1]: https://github.com/lucatume/di52/compare/3.0.0...3.0.1
[3.0.1]: https://github.com/lucatume/di52/compare/3.0.1...3.0.1
[3.0.2]: https://github.com/lucatume/di52/compare/3.0.1...3.0.2
[3.0.3]: https://github.com/lucatume/di52/compare/3.0.2...3.0.3
[3.1.0]: https://github.com/lucatume/di52/compare/3.0.3...3.1.0
[3.1.1]: https://github.com/lucatume/di52/compare/3.1.0...3.1.1
[3.2.0]: https://github.com/lucatume/di52/compare/3.1.1...3.2.0
[3.2.1]: https://github.com/lucatume/di52/compare/3.2.0...3.2.1
[3.3.0]: https://github.com/lucatume/di52/compare/3.2.1...3.3.0
[3.3.1]: https://github.com/lucatume/di52/compare/3.3.0...3.3.1
[3.3.2]: https://github.com/lucatume/di52/compare/3.3.1...3.3.2
[3.3.3]: https://github.com/lucatume/di52/compare/3.3.2...3.3.3
[3.3.4]: https://github.com/lucatume/di52/compare/3.3.3...3.3.4
[3.3.5]: https://github.com/lucatume/di52/compare/3.3.4...3.3.5
[3.3.6]: https://github.com/lucatume/di52/compare/3.3.5...3.3.6
[3.3.7]: https://github.com/lucatume/di52/compare/3.3.6...3.3.7
[4.0.0]: https://github.com/lucatume/di52/compare/3.3.7...4.0.0
[unreleased]: https://github.com/lucatume/di52/compare/4.0.0...HEAD

View File

@@ -0,0 +1,978 @@
A PHP 5.6+ compatible dependency injection container inspired
by [Laravel IOC](https://laravel.com/docs/5.0/container "Service Container - Laravel - The PHP Framework For Web Artisans")
and [Pimple](http://pimple.sensiolabs.org/ "Pimple - A simple PHP Dependency Injection Container") that works even
better on newer version of PHP.
A quick overview of the Container features:
* **Auto-wiring** - The Container will use Reflection to find what class should be built and how, it's _almost_ magic.
* **Flexible** - Legacy code? Fat constructors with tons of side-effects? The Container offers auto-wiring without
making it a requirement. It will adapt to existing code and will not require your code to adapt to it.
* **Fatal error handling** - On PHP 7.0+ the Container will take care of handling fatal errors that might happen at
class file load time and handle them.
* **Fast** - The Container is optimized for speed as much as it can be squeezed out of the required PHP compatibility.
* **Flexible default mode** - Singleton (build at most once) an prototype (build new each time) default modes available.
* **Global Application** - Like using `App::get($service)->doStuff()`? The `App` facade allows using the DI Container as
a globally available Service Locator.
* **PSR-11 compatible** - The container is fully compatible
with [PSR-11 specification](https://www.php-fig.org/psr/psr-11/).
* **Ready for WordPress and other Event-driven frameworks** - The container API provides methods
like [`callback`](#the-callback-method) and [`instance`](#the-instance-method) to easily be integrated with
Event-driven frameworks like WordPress that require hooking callbacks to events.
* **Service Providers** - To keep your code organized, the library provides
an [advanced Service Provider implementation](#service-providers).
## Table of Contents
- [Code Example](#code-example)
- [Installation](#installation)
- [Upgrading from version 2 to version 3](#upgrading-from-version-2-to-version-3)
- [Upgrading from version 3.2 to version 3.3](#upgrading-from-version-32-to-version-33)
- [Quick and dirty introduction to dependency injection](#quick-and-dirty-introduction-to-dependency-injection)
* [What is dependency injection?](#what-is-dependency-injection-)
* [What is a DI container?](#what-is-a-di-container-)
* [What is a Service Locator?](#what-is-a-service-locator-)
* [Construction templates](#construction-templates)
- [The power of `get`](#the-power-of--get-)
- [Storing variables](#storing-variables)
- [Binding implementations](#binding-implementations)
- [Binding implementations to slugs](#binding-implementations-to-slugs)
- [Contextual binding](#contextual-binding)
- [Binding decorator chains](#binding-decorator-chains)
- [Tagging](#tagging)
- [The callback method](#the-callback-method)
- [Service providers](#service-providers)
* [Booting service providers](#booting-service-providers)
* [Deferred service providers](#deferred-service-providers)
* [Dependency injection with service providers](#dependency-injection-with-service-providers)
- [Customizing the container](#customizing-the-container)
* [Unbound classes resolution](#unbound-classes-resolution)
* [Exception masking](#exception-masking)
## Code Example
In the application bootstrap file we define how the components will come together:
```php
<?php
/**
* The application bootstrap file: here the container is provided the minimal set of instructions
* required to set up the application objects.
*/
namespace lucatume\DI52\Example1;
use lucatume\DI52\App;
use lucatume\DI52\Container;
require_once __DIR__ . '/vendor/autoload.php';
// Start by building an instance of the DI container.
$container = new Container();
// When an instance of `TemplateInterface` is required, build and return an instance
// of `PlainPHPTemplate`; build at most once (singleton).
$container->singleton(
TemplateInterface::class,
static function () {
return new PlainPHPTemplate(__DIR__ . '/templates');
}
);
// The default application Repository is the Posts one.
// When a class needs an instance of the `RepositoryInterface`, then
// return an instance of the `PostsRepository` class.
$container->bind(RepositoryInterface::class, PostsRepository::class);
// But the Users page should use the Users repository.
$container->when(UsersPageRequest::class)
->needs(RepositoryInterface::class)
->give(UsersRepository::class);
// Bind primitive values, e.g. public function __construct( int $per_page ) {}
$container->when(UsersPageRequest::class)
->needs('$per_page')
->give(10);
// Fetch the above class without any further definitions
$container->get(UsersPageRequest::class)
// The `UsersRepository` will require a `DbConnection` instance, that
// should be built at most once (singleton).
$container->singleton(DbConnection::class);
// Set the routes.
$container->bind('home', HomePageRequest::class);
$container->bind('users', UsersPageRequest::class);
// Make the container globally available as a service locator using the App.
App::setContainer($container);
```
In the application entrypoint, the `index.php` file, we'll **lazily** resolve the whole dependency tree following the
rules set in the bootstrap file:
```php
<?php
use lucatume\DI52\App;
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/bootstrap.php';
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$route = basename(basename($path, '.php'), '.html') ?: 'home';
App::get($route)->serve(); ?>
?>
```
That's it.
## Installation
Use [Composer](https://getcomposer.org/) to require the library:
```bash
composer require lucatume/di52
```
Include the [Composer](https://getcomposer.org/) autoload file in your project entry point and create a new instance of
the container to start using it:
```php
<?php
require_once 'vendor/autoload.php';
$container = new lucatume\DI52\Container();
$container->singleton(DbInterface::class, MySqlDb::class);
```
If you would prefer using the Dependency Injection Container as a globally-available Service Locator, then you
can use the `lucatume\DI52\App`:
```php
<?php
require_once 'vendor/autoload.php';
lucatume\DI52\App::singleton(DbInterface::class, MySqlDb::class);
```
See the [example above](#code-example) for more usage examples.
## Upgrading from version 2 to version 3
The main change introduced by version `3.0.0` of the library is dropping compatibility with PHP 5.2 to require a minimum
version of PHP 5.6. The library is tested up to PHP 8.1.
If you're using version 2 of DI52 in your project, then there _should_ be nothing you need to do.
The new, namespaced, classes of version 3 are aliased to their version 2 correspondent, e.g. `tad_DI52_Container` is
aliased to `lucatume\di52\Container` and `tad_DI52_ServiceProvider` is aliased to `lucatume\di52\ServiceProvider`.
I suggest an update for **a small performance gain**, though, to use the new, namespaced, class names in place of the
PHP 5.2
compatible ones:
* replace uses of `tad_DI52_Container` with `lucatume\di52\Container`
* replace uses of `tad_DI52_ServiceProvider` with `lucatume\DI52\ServiceProvider`
The new version implemented [PSR-11](https://www.php-fig.org/psr/psr-11/) compatibility and the main method to get hold
of an object instance from the container changed from `make` to `get`.
Do not worry, the `lucatume\di52\Container::make` method is still there: it's just an alias of
the `lucatume\di52\Container::get` one.
For another small performance gain replace uses of `tad_DI52_Container::make` with `lucatume\di52\Container::get`.
That should be all of it.
## Upgrading from version 3.2 to version 3.3
Version 3.3.0 of the library removed the `aliases.php` file, which previously helped to load non-PSR namespaced class names.
However, if you're using the `tad_DI52_Container` and `tad_DI52_ServiceProvider` classes in your project, you can set up the aliases by adding a few lines of code to your project's bootstrap file to ensure your code continues to work as expected:
```php
<?php
$aliases = [
['lucatume\DI52\Container', 'tad_DI52_Container'],
['lucatume\DI52\ServiceProvider', 'tad_DI52_ServiceProvider']
];
foreach ($aliases as list($class, $alias)) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
```
## Quick and dirty introduction to dependency injection
### What is dependency injection?
A [Dependency Injection (DI) Container](https://en.wikipedia.org/wiki/Dependency_injection "Dependency injection - Wikipedia")
is a tool meant to make dependency injection possible and easy to manage.
Dependencies are specified by a class constructor method via
[**type-hinting**](http://php.net/manual/en/language.oop5.typehinting.php "PHP: Type Hinting - Manual"):
```php
class A {
private $b;
private $c;
public function __construct(B $b, C $c){
$this->b = $b;
$this->c = $c;
}
}
```
Any instance of class `A` **depends** on implementations of the `B` and `C` classes.
The "injection" happens when class `A` dependencies are passed to it, "injected" in its constructor method, in place of
being created inside the class itself.
```php
$a = new A(new B(), new C());
```
The flexibility of type hinting allows injecting into `A` not just instances of `B` and `C` but instances of any class
extending the two:
```php
class ExtendedB extends B {}
class ExtendedC extends C {}
$a = new a(new ExtendedB(), new ExtendedC());
```
PHP allows type hinting not just **concrete implementations** (classes) but **interfaces** too:
```php
class A {
private $b;
private $c;
public function __construct(BInterface $b, CInterface $c){
$this->b = $b;
$this->c = $c;
}
}
```
This extends the possibilities of dependency injection even further and avoids strict coupling of the code:
```php
class B implements BInterface {}
class C implements CInterface {}
$a = new a(new B(), new C());
```
### What is a DI container?
The `B` and `C` classes are concrete (as in "you can instance them") implementations of interfaces and while the
interfaces might never change the implementations might and should change in the lifecycle of code: that's
the [Dependency Inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) or "depend upon
abstractions, non concretions".
If the implementation of `BInterface` changes from `B` to `BetterB` then I'd have to update all the code where I'm
building instances of `A` to use `BetterB` in place of `B`:
```php
// before
$a = new A(new B(), new C());
//after
$a = new A(new BetterB(), new C());
```
On smaller code-bases this might prove to be a quick solution but, as the code grows, it will become less and less an
applicable solution.
Adding classes to the mix proves the point when dependencies start to stack:
```php
class D implements DInterface{
public function __construct(AInterface $a, CInterface $c){}
}
class E {
public function __construct(DInterface $d){}
}
$a = new A (new BetterB(), new C());
$d = new D($a, $c);
$e = new E($d);
```
Another issue with this approach is that classes have to be built immediately to be injected, see `$a` and `$d` above to
feed `$e`, with the immediate cost of "eager" instantiation, if `$e` is never used than the effort put into building it,
in terms of time and resources spent by PHP to build `$a`, `$b`, `$c`, `$d` and finally `$e`, are wasted.
A **dependency injection container** will take care of building only objects that are needed taking care of
**resolving** nested dependencies.
> Need an instance of `E`? I will build instances of `B` and `C` to build an instance of `A` to build an instance of `D`
to finally build and return an instance of `E`.
### What is a Service Locator?
A "Service Locator" is an object, or function, that will answer to this question made by your code:
```php
$database = $serviceLocator->get('database');
```
In Plain English "I do not care how it's built or where it comes from, give me the current implementation of the
database service.".
Service Locators are, usually, globally-available DI Containers for obvious reasons: the DI Container knows how to build
the services the Service Locator will provide when required.
The concept of Service Locators and DI Containers are often conflated as a DI Container, when globally available,
makes a good implementation of a Service Locator.
An example of this is the `lucatume\DI52\App` class: it will expose, by means of static methods, a globally-available
instance of the `lucatume\DI52\Container` class.
```php
<?php
use lucatume\DI52\Container;
use lucatume\DI52\App;
// This is a DI Container.
$diContainer = new Container();
// If we make it globally-available, then it will be used by the Service Locator (the `App` class).
App::setContainer($diContainer);
// Register a binding in the DI Container.
$diContainer->singleton('database', MySqlDb::class);
// We can now globally, i.e. anywhere in the code, access the `db` service.
$db = App::get('database');
```
Since the `lucatume\DI52\App` class proxies calls to the Container, the example could be made shorter:
```php
<?php
use lucatume\DI52\App;
// Register a binding in the App (Service Locator).
App::singleton('database', MySqlDb::class);
// We can now globally, i.e. anywhere in the code, access the `db` service.
$db = App::get('database');
```
### Construction templates
The container will need to be told, just once, how objects should be built.
For the container it's easy to understand that a class type-hinting an instance of the concrete class `A` will require a
new instance of `A` but loosely coupled code leveraging the use of a DI container will probably type-hint an `interface`
in place of concrete `class`es.
Telling the container what concrete `class` to instance when a certain `interface` is requested by an
object `__construct` method is called "binding and implementation to an interface".
While dependency injection can be made in other methods too beyond the `__construct` one that's what di52 supports at
the moment; if you want to read more the web is full of good reference
material, [this article by Fabien Potencier](http://fabien.potencier.org/what-is-dependency-injection.html) is a very
good start.
## The power of `get`
At its base the container is a dependency resolution and injection machine: given a class to its `get` method it will
read the class type-hinted dependencies, build them and inject them in the class.
```php
// file ClassThree.php
class ClassThree {
private $one;
private $two;
public function __construct(ClassOne $one, ClassTwo $two){
$this->one = $one;
$this->two = $two;
}
}
// The application bootstrap file
use lucatume\DI52\Container;
$container = new Container();
$three = $container->get('ClassThree');
```
Keep that in mind while reading the following paragraphs.
## Storing variables
In its most basic use case the container can store variables:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->setVar('number', 23);
$number = $container->getVar('number');
```
Since the container will treat any callable object as a factory (see below) callables have to be protected using the
container `protect` method:
```php
$container = new tad_DI52_Container();
$container->setVar('randomNumberGenerator', $container->protect(function($val){
return mt_rand(1,100) + 23;
}));
$randomNumberGenerator = $container->getVar('randomNumberGenerator');
```
The protect method tells the container that, when `get`ting the `randomNumberGenerator` alias, we do not want to run the function and
get its result, but we want to get back the function itself.
## Binding implementations
Telling the container what should be built and when is done by an API similar to the one exposed by the [Laravel Service container](https://laravel.com/docs/5.3/container "Service Container - Laravel - The PHP Framework For Web ...")
and while the inner workings are different the good idea (kudos to Laravel's creator and maintainers) is reused.
Reusing the example above:
```php
use lucatume\DI52\Container;
$container = new Container();
// Bind to a class name.
$container->bind(AInterface::class, A::class);
// Bind to a Closure.
$container->bind(BInterface::class, function(){
return new BetterB();
});
// Bind to a constructor and methods that should be called on the built object.
$container->bind(CInterface::class, LegacyC::class, ['init','register']);
// Bind to a factory method.
$container->bind(D::interface, [DFactory::class,'buildInstance'])
// Bind to an object, it will be a singleton by default.
$container->bind(E::interface, new EImplementation());
$e = $container->get(F::class);
```
The `get` method will build the `F` object resolving its requirements to the bound implementations when requested.
When using the `bind` method a new instance of the bound implementations will be returned on each request; this might
not be the wanted behaviour especially for object costly to build (like a database driver that needs to connect): in
that case the `singleton` method should be used:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->singleton(DBDriverInterface::class, MYSqlDriver::class);
$container->singleton(RepositoryInterface::class, MYSQLRepository::class);
$container->get(RepositoryInterface::class);
```
Binding an implementation to an interface using the `singleton` methods tells the container the implementations should
be built just the first time: any later call for that same interface should return the same instance.
Implementations can be redefined in any moment simple calling the `bind` or `singleton` methods again specifying a
different implementation.
You can customize how unbound classes are resolved by the container, check the [unbound classes](#unbound-classes-resolution) section.
## Binding implementations to slugs
The container was heavily inspired
by [Pimple](http://pimple.sensiolabs.org/ "Pimple - A simple PHP Dependency Injection Container") and offers some
features of the PHP 5.3+ DI container as well:
```php
use lucatume\DI52\Container;
$container = new Container();
// Storing vars using the ArrayAccess API.
$container['db.name'] = 'appDb';
$container['db.user'] = 'root';
$container['db.pass'] = 'secret';
$container['db.host'] = 'localhost:3306';
// Bindings can be set using ArrayAccess methods.
$container['db.driver'] = MYSQLDriver::class;
// Bound closures will receive the container instance as argument.
$container['db.connection'] = function($container){
$host = $container['db.host']
$user = $container['db.user'],
$pass = $container['db.pass'],
$name = $container['db.name'],
$dbDriver = $container['db.driver'];
$dbDriver->connect($host, $user, $pass, $name);
return new DBConnection($dbDriver);
};
// Equivalent to $container->get('db.connection');
$dbConnection = $container['db.connection'];
// Using ArrayAccess API to store a closure as a variable.
$container['uniqid'] = $container->protect(function(){
return uniqid('id', true);
});
```
There is no replacement for the `factory` method offered by Pimple: the `bind` method should be used instead.
## Contextual binding
Borrowing an excellent idea from Laravel's container the possibility of contextual binding exists (supporting all the
binding possibilities above).
Contextual binding solves the problem of different objects requiring different implementations of the same interface (or
class, see above):
```php
use lucatume\DI52\Container;
$container = new Container();
/*
* By default any object requiring an implementation of the `CacheInterface`
* should be given the same instance of `Array Cache`
*/
$container->singleton(CacheInterface::class, ArrayCache::class);
$container->bind(DbCache::class, function($container){
$cache = $container->get(CacheInterface::class);
$dbCache = new DbCache($cache);
return $dbCache;
});
/*
* But when an implementation of the `CacheInterface` is requested by
* `TransactionManager`, then it should be given an instance of `Array Cache`.
*/
$container->when(TransactionManager::class)
->needs(CacheInterface::class)
->give(DbCache::class);
/*
* We can also bind primitives where the container doesn't know how to auto-wire
* them.
*/
$container->when(MysqlOrm:class)
->needs('$dbUrl')
->give('mysql://user:password@127.0.0.1:3306/app');
/*
* When primitives are bound to a class the container will correctly resolve them when building the class
* bound to an interface.
*/
$container->bind(ORMInterface::class, MysqlOrm::class);
// The `ORMInterface` will be resolved an instance of the `MysqlOrm` class, with the `$dbUrl` argument set correctly.
$orm = $container->get(ORMInterface::class);
```
## Binding decorator chains
The [Decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern "Decorator pattern - Wikipedia") allows
extending the functionalities of an implementation without creating an extension and leveraging interfaces.
The container allows binding "chain of decorators" to an interface (or slug à la Pimple, or class) using
the `bindDecorators` and `singletonDecorators`.
The two methods are the `bind` and `singleton` equivalents for decorators.
```php
use lucatume\DI52\Container;
$container = new Container();
$container->bind(RepositoryInterface::class, PostRepository::class);
$container->bind(CacheInterface::class, ArrayCache::class);
$container->bind(LoggerInterface::class, FileLogger::class);
// Decorators are built left to right, outer decorators are listed first.
$container->bindDecorators(PostEndpoint::class, [
LoggingEndpoint::class,
CachingEndpoint::class,
BaseEndpoint::class
]);
```
Similarly to a `bind` or `singleton` call, you can specify a set of methods to call after the decorator chain is built
with the `afterBuildMethods` parameter:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->bind(RepositoryInterface::class, PostRepository::class);
$container->bind(CacheInterface::class, ArrayCache::class);
$container->bind(LoggerInterface::class, FileLogger::class);
// Decorators are built left to right, outer decorators are listed first.
$container->bindDecorators(PostEndpoint::class, [
LoggingEndpoint::class,
CachingEndpoint::class,
BaseEndpoint::class
], ['register']);
```
By default, the `register` method will be called **only on the base instance**, the one on the right of the decorator chain.
In the example above only `BaseEndpoint::register` would be called.
If you need to call the same set of after-build methods on all instances after each is build, you can set the value of
the `afterBuildAll` parameter to `true`:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->bind(RepositoryInterface::class, PostRepository::class);
$container->bind(CacheInterface::class, ArrayCache::class);
$container->bind(LoggerInterface::class, FileLogger::class);
// Decorators are built left to right, outer decorators are listed first.
$container->bindDecorators(PostEndpoint::class, [
LoggingEndpoint::class,
CachingEndpoint::class,
BaseEndpoint::class
], ['register'], true);
```
In this example the `register` method will be called on the `BaseEndpoint` after it's built, then on the
`CachingEndpoint` instance after it's built, and finally on the `LoggingEndpoint` instance after it's built.
Different and more complex combinations of decorators and after-build methods should be handled binding, with a
`bind` or `singleton` call, a Closure to build the decorator chain.
## Tagging
Tagging allows grouping similar implementations for the purpose of referencing them by group.
Grouping implementations makes sense when, as an example, the same method has to be called on each implementation:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->bind(UnsupportedEndpoint::class, function($container){
$template = '404';
$message = 'Nope';
$redirectAfter = 3;
$redirectTo = $container->get(HomeEndpoint::class);
return new UnsupportedEndpoint($template, $message, $redirectAfter, $redirectTo);
});
$container->tag([
HomeEndpoint::class,
PostEndpoint::class,
UnsupportedEndpoint::class,
], 'endpoints');
foreach($container->tagged('endpoints') as $endpoint) {
$endpoint->register();
}
```
The `tag` method supports any possibility offered by the container in terms of binding of objects, closures, decorator
chains and after-build methods.
## The callback method
Some applications require callbacks (or some form of callable) to be returned in specific pieces of code.
This is especially the case with WordPress and
its [event-based architecture](https://codex.wordpress.org/Plugin_API/Filter_Reference "Plugin API/Filter Reference « WordPress Codex")
.
Using the container does not removes that possibility:
```php
use lucatume\DI52\Container;
$container = new Container();
add_filter('some_filter', [$container->get(SomeFilteringClass::class), 'filter']);
```
This code suffers from an eager instantiation problem: `SomeFilteringClass` is built for the purpose of binding it but
might never be used.
The problem is easy to solve using the `Container::callback` method:
```php
use lucatume\DI52\Container;
$container = new Container();
$container->singleton(SomeFilteringClass::class);
add_filter('some_filter', $container->callback(SomeFilteringClass::class, 'filter'));
```
The advantage of this solution is the container will return the same callback every time it's called with the same
arguments when the called class is a singleton:
```php
// Some code later we need to remove the filter: we'll get the same callback.
remove_filter('some_filter', App::callback(SomeFilteringClass::class, 'filter'));
```
## Service providers
To avoid passing the container instance around (
see [Service Locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern "Service locator pattern - Wikipedia"))
or globalising it all the binding should happen in the same PHP file: this could lead, as the application grows, to a
thousand lines monster.
To avoid that the container supports service providers: those are classes extending
the `lucatume\DI52\ServiceProvider` class, that
allow organizing the binding registrations into logical, self-contained and manageable units:
```php
use lucatume\DI52\ServiceProvider;
// file ProviderOne.php
class ProviderOne extends ServiceProvider {
public function register() {
$this->container->bind(InterfaceOne::class, ClassOne::class);
$this->container->bind(InterfaceTwo::class, ClassTwo::class);
$this->container->singleton(InterfaceThree::class, ClassThree::class);
}
}
// Application bootstrap file.
use lucatume\DI52\Container;
$container = new Container();
$container->register(ProviderOne::class);
$container->register(ProviderTwo::class);
$container->register(ProviderThree::class);
$container->register(ProviderFour::class);
```
### Booting service providers
The container implements a `boot` method that will, in turn, call the `boot` method on any service provider that
overloads it.
Some applications might define constants and environment variables at "boot" time (e.g. WordPress `plugins_loaded`
action) that might make an immediate registration futile.
In that case service providers can overload the `boot` method:
```php
// file ProviderOne.php
use lucatume\DI52\ServiceProvider;
class ProviderOne extends ServiceProvider {
public function register() {
$this->container->bind(InterfaceOne::class, ClassOne::class);
$this->container->bind(InterfaceTwo::class, ClassTwo::class);
$this->container->singleton(InterfaceThree::class, ClassThree::class);
}
public function boot() {
if(defined('SOME_CONSTANT')) {
$this->container->bind(InterfaceFour::class, ClassFour::class);
} else {
$this->container->bind(InterfaceFour::class, AnotherClassFour::class);
}
}
}
// Application bootstrap file.
use lucatume\DI52\Container;
$container = new Container();
$container->register(ProviderOne::class);
$container->register(ProviderTwo::class);
$container->register(ProviderThree::class);
// Some code later ...
$container->boot();
```
### Deferred service providers
Sometimes even just setting up the implementations might require such an up-front cost to make it undesirable unless
it's needed.
This might happen with non-autoloading code that will require a tangle of files to load (and side load) to grab a simple
class instance.
To "defer" that cost service providers can overload the `deferred` property and the `provides` method:
```php
// file ProviderOne.php
use lucatume\DI52\ServiceProvider;
class ProviderOne extends ServiceProvider {
public $deferred = true;
public function provides() {
return array(LegacyClassOne::class, LegacyInterfaceTwo::class);
}
public function register() {
include_once('legacy-file-one.php')
include_once('legacy-file-two.php')
$db = new Db();
$details = $db->getDetails();
$this->container->singleton(LegacyClassOne::class, new LegacyClassOne($details));
$this->container->bind(LegacyInterfaceTwo::class, new LegacyClassTwo($details));
}
}
// Application bootstrap file
use lucatume\DI52\Container;
$container = new Container();
// The provider `register` method will not be called immediately...
$container->register(ProviderOne::class);
// ...it will be called here as it provides the binding of `LegacyClassOne`
$legacyOne = $container->get(LegacyClassOne::class);
// Will not be called again here, done already.
$legacyTwo = $container->get(LegacyInterfaceTwo::class);
```
### Dependency injection with service providers
The container supports additional dependency injection for service providers (version 3.0.3+). Auto-wiring
will work the same as any class, simply override the service provider's constructor and add any additional concrete dependencies (don't forget to call the parent!):
```php
// file ProviderOne.php
use lucatume\DI52\ServiceProvider;
class ProviderOne extends ServiceProvider {
/**
* @var ConfigHelper
*/
protected $config;
public function __construct(\lucatume\DI52\Container $container, ConfigHelper $config)
{
parent::__construct($container);
$this->config = $config;
}
public function register()
{
$this->container->when(ClassFour::class)
->needs('$value')
->give($this->config->get('value'));
}
}
// Application bootstrap file.
use lucatume\DI52\Container;
$container = new Container();
$container->register(ProviderOne::class);
```
If you want to inject primitives into a service provider, you need to utilize the `when`, `needs`, `give` methods **_before_** registering the provider in the container:
```php
// file ProviderOne.php
use lucatume\DI52\ServiceProvider;
class ProviderOne extends ServiceProvider {
/**
* @var bool
*/
protected $service_enabled;
public function __construct(\lucatume\DI52\Container $container, $service_enabled)
{
parent::__construct($container);
$this->service_enabled = $service_enabled;
}
public function register()
{
if (!$this->service_enabled) {
return;
}
$this->container->bind(InterfaceOne::class, ClassOne::class);
}
}
// Application bootstrap file.
use lucatume\DI52\Container;
$container = new Container();
$container->when(ProviderOne::class)
->needs('$service_enabled')
->give(true);
$container->register(ProviderOne::class);
```
## Customizing the container
The container will be built with some opinionated defaults; those are not set in stone and you can customize the
container to your needs.
### Unbound classes resolution
The container will use reflection to work out the dependencies of an object, and will not require setup when resolving
objects with type-hinted object dependencies in the `__construct` method.
By default those _unbound_ classes will be resolved **as prototypes**, built new on **each** `get` request.
To control the mode used to resolve unbound classes, a flag property can be set on the container when constructing it:
```php
use lucatume\DI52\Container;
$container1 = new Container();
$container2 = new Container(true);
// Default resolution of unbound classes is prototype.
assert($container1->get(A::class) !== $container1->get(A::class));
// The second container will resolve unbound classes once, then store them as singletons.
assert($container2->get(A::class) === $container2->get(A::class));
```
This will only apply to unbound classes! Whatever the flag used to build the container instance, the mode set in the
binding phase using `Container::bind()` or `Container::singleton()` methods will **always** be respected.
### Exception masking
By default the container will catch any exception thrown during a service resolution and wrap into a `ContainerException`
instance.
The container will modify the exception message and the trace file and line to provide information about the nested
resolution tree and point your debug to the file and line that caused the issue.
You can customize how the container will handle exceptions by using the `Container::setExceptionMask()` method:
```php
use lucatume\DI52\Container;
$container = new Container();
// The container will throw any exception thrown during a service resolution without any modification.
$container->setExceptionMask(Container::EXCEPTION_MASK_NONE);
// Wrap any exception thrown during a service resolution in a `ContainerException` instance, modify the message.
$container->setExceptionMask(Container::EXCEPTION_MASK_MESSAGE);
// Wrap any exception thrown during a service resolution in a `ContainerException` instance, modify the trace file and line.
$container->setExceptionMask(Container::EXCEPTION_MASK_FILE_LINE);
// You can combine the options, this is the default value.
$container->setExceptionMask(Container::EXCEPTION_MASK_MESSAGE | Container::EXCEPTION_MASK_FILE_LINE);
```

View File

@@ -0,0 +1,510 @@
<?php
/**
* A facade to make a DI container instance globally available as a Service Locator.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
use KadenceWP\KadenceBlocks\lucatume\DI52\Builders\ValueBuilder;
/**
* Class App
*
* @package \lucatume\DI52
*/
class App
{
/** A reference to the singleton instance of the DI container
* the application uses as Service Locator.
*
* @var Container|null
*/
protected static $container;
/**
* Returns the singleton instance of the DI container the application
* will use as Service Locator.
*
* @return Container The singleton instance of the Container used as Service Locator
* by the application.
*/
public static function container()
{
if (!isset(static::$container)) {
static::$container = new Container();
}
return static::$container;
}
/**
* Sets the container instance the Application should use as a Service Locator.
*
* If the Application already stores a reference to a Container instance, then
* this will be replaced by the new one.
*
* @param Container $container A reference to the Container instance the Application
* should use as a Service Locator.
*
* @return void The method does not return any value.
*/
public static function setContainer(Container $container)
{
static::$container = $container;
}
/**
* Sets a variable on the container.
*
* @param string $key The alias the container will use to reference the variable.
* @param mixed $value The variable value.
*
* @return void The method does not return any value.
*/
public static function setVar($key, $value)
{
static::container()->setVar($key, $value);
}
/**
* Sets a variable on the container using the ArrayAccess API.
*
* When using the container as an array bindings will be bound as singletons.
* These are equivalent: `$container->singleton('foo','ClassOne');`, `$container['foo'] = 'ClassOne';`.
*
* @param string $offset The alias the container will use to reference the variable.
* @param mixed $value The variable value.
*
* @return void This method does not return any value.
*
* @throws ContainerException If the closure building fails.
*/
public static function offsetSet($offset, $value)
{
static::container()->offsetSet($offset, $value);
}
/**
* Binds an interface a class or a string slug to an implementation and will always return the same instance.
*
* @param string|class-string $id A class or interface fully qualified name or a string slug.
* @param mixed $implementation The implementation that should be bound to the alias(es); can
* be a class name, an object or a closure.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return void This method does not return any value.
*
* @throws ContainerException If there's any issue reflecting on the class, interface or the implementation.
*/
public static function singleton($id, $implementation = null, ?array $afterBuildMethods = null)
{
static::container()->singleton($id, $implementation, $afterBuildMethods);
}
/**
* Returns a variable stored in the container.
*
* If the variable is a binding then the binding will be resolved before returning it.
*
* @param string $key The alias of the variable or binding to fetch.
* @param mixed|null $default A default value to return if the variable is not set in the container.
*
* @return mixed The variable value or the resolved binding.
* @throws ContainerException If there's an issue resolving the variable.
*
* @see Container::get()
*/
public static function getVar($key, $default = null)
{
return static::container()->getVar($key, $default);
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @template T
*
* @param string|class-string<T> $offset Identifier of the entry to look for.
*
* @return T|mixed The value for the offset.
* @phpstan-return ($offset is class-string ? T : mixed)
*
* @throws ContainerException Error while retrieving the entry.
* @throws NotFoundException No entry was found for **this** identifier.
*/
public static function offsetGet($offset)
{
return static::container()->offsetGet($offset);
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @template T
*
* @param string|class-string<T> $id A fully qualified class or interface name or an already built object.
*
* @return T|mixed The entry for an id.
* @phpstan-return ($id is class-string ? T : mixed)
*
* @throws ContainerException Error while retrieving the entry.
*/
public static function get($id)
{
return static::container()->get($id);
}
/**
* Returns an instance of the class or object bound to an interface, class or string slug if any, else it will try
* to automagically resolve the object to a usable instance.
*
* If the implementation has been bound as singleton using the `singleton` method
* or the ArrayAccess API then the implementation will be resolved just on the first request.
*
* @template T
*
* @param string|class-string<T> $id A fully qualified class or interface name or an already built object.
*
* @return T|mixed
* @phpstan-return ($id is class-string ? T : mixed)
*
* @throws ContainerException If the target of the make is not bound and is not a valid,
* concrete, class name or there's any issue making the target.
*/
public static function make($id)
{
return static::container()->make($id);
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `$container[$id]` returning true does not mean that `$container[$id]` will not throw an exception.
* It does however mean that `$container[$id]` will not throw a `NotFoundExceptionInterface`.
*
* @param string|class-string $offset An offset to check for.
*
* @return boolean true on success or false on failure.
*/
public static function offsetExists($offset)
{
return static::container()->offsetExists($offset);
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string|class-string $id Identifier of the entry to look for.
*
* @return bool Whether the container contains a binding for an id or not.
*/
public static function has($id)
{
return static::container()->has($id);
}
/**
* Tags an array of implementations bindings for later retrieval.
*
* The implementations can also reference interfaces, classes or string slugs.
* Example:
*
* $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
*
* @param array<string|callable|object> $implementationsArray The ids, class names or objects to apply the tag to.
* @param string $tag The tag to apply.
*
* @return void This method does not return any value.
* @see Container::tagged()
*
*/
public static function tag(array $implementationsArray, $tag)
{
static::container()->tag($implementationsArray, $tag);
}
/**
* Retrieves an array of bound implementations resolving them.
*
* The array of implementations should be bound using the `tag` method:
*
* $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
* foreach($container->tagged('endpoints') as $endpoint){
* $endpoint->register();
* }
*
* @param string $tag The tag to return the tagged values for.
*
* @return array<mixed> An array of resolved bound implementations.
* @throws NotFoundException If nothing is tagged with the tag.
* @throws ContainerException If one of the bindings is not of the correct type.
* @see Container::tag()
*/
public static function tagged($tag)
{
return static::container()->tagged($tag);
}
/**
* Checks whether a tag group exists in the container.
*
* @param string $tag
*
* @return bool
* @see Container::tag()
*
*/
public static function hasTag($tag)
{
return static::container()->hasTag($tag);
}
/**
* Registers a service provider implementation.
*
* The `register` method will be called immediately on the service provider.
*
* If the provider overloads the `isDeferred` method returning a truthy value then the `register` method will be
* called only if one of the implementations provided by the provider is requested. The container defines which
* implementations is offering overloading the `provides` method; the method should return an array of provided
* implementations.
*
* If a provider overloads the `boot` method that method will be called when the `boot` method is called on the
* container itself.
*
* @param class-string $serviceProviderClass The fully-qualified Service Provider class name.
* @param string ...$alias A list of aliases the provider should be registered with.
* @return void This method does not return any value.
* @throws ContainerException If the Service Provider is not correctly configured or there's an issue
* reflecting on it.
* @see ServiceProvider::register()
* @see ServiceProvider::isDeferred()
* @see ServiceProvider::provides()
* @see Container::getProvider()
* @see ServiceProvider::boot()
*/
public static function register($serviceProviderClass, ...$alias)
{
static::container()->register($serviceProviderClass, ...$alias);
}
/**
* Binds an interface, a class or a string slug to an implementation.
*
* Existing implementations are replaced.
*
* @param string|class-string $id A class or interface fully qualified name or a string slug.
* @param mixed $implementation The implementation that should be bound to the alias(es); can
* be a class name, an object or a closure.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return void The method does not return any value.
*
* @throws ContainerException If there's an issue while trying to bind the implementation.
*/
public static function bind($id, $implementation = null, ?array $afterBuildMethods = null)
{
static::container()->bind($id, $implementation, $afterBuildMethods);
}
/**
* Boots up the application calling the `boot` method of each registered service provider.
*
* If there are bootable providers (providers overloading the `boot` method) then the `boot` method will be
* called on each bootable provider.
*
* @return void This method does not return any value.
*
* @see ServiceProvider::boot()
*/
public static function boot()
{
static::container()->boot();
}
/**
* Binds a class, interface or string slug to a chain of implementations decorating a base
* object; the chain will be lazily resolved only on the first call.
* The base decorated object must be the last element of the array.
*
* @param string|class-string $id The class, interface or slug the decorator chain
* should be bound to.
* @param array<string|object|callable> $decorators An array of implementations that decorate an object.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the
* instance after it has been built; the methods should
* not require any argument.
*
* @return void This method does not return any value.
* @throws ContainerException
*/
public static function singletonDecorators($id, $decorators, ?array $afterBuildMethods = null)
{
static::container()->singletonDecorators($id, $decorators, $afterBuildMethods);
}
/**
* Binds a class, interface or string slug to a chain of implementations decorating a
* base object.
*
* The base decorated object must be the last element of the array.
*
* @param string|class-string $id The class, interface or slug the decorator chain
* should be bound to.
* @param array<string|object|callable> $decorators An array of implementations that decorate an object.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the
* instance after it has been built; the methods should
* not require any argument.
*
* @return void This method does not return any value.
* @throws ContainerException If there's any issue binding the decorators.
*/
public static function bindDecorators($id, array $decorators, ?array $afterBuildMethods = null)
{
static::container()->bindDecorators($id, $decorators, $afterBuildMethods);
}
/**
* Unsets a binding or tag in the container.
*
* @param mixed $offset The offset to unset.
*
* @return void The method does not return any value.
*/
public static function offsetUnset($offset)
{
static::container()->offsetUnset($offset);
}
/**
* Starts the `when->needs->give` chain for a contextual binding.
*
* @param string|class-string $class The fully qualified name of the requesting class.
*
* Example:
*
* // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // But if the requesting class is `Worker` return another implementation
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @return Container The container instance, to continue the when/needs/give chain.
*/
public static function when($class)
{
return static::container()->when($class);
}
/**
* Second step of the `when->needs->give` chain for a contextual binding.
*
* Example:
*
* // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // But if the requesting class is `Worker` return another implementation.
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @param string|class-string $id The class or interface needed by the class.
*
* @return Container The container instance, to continue the when/needs/give chain.
*/
public static function needs($id)
{
return static::container()->needs($id);
}
/**
* Third step of the `when->needs->give` chain for a contextual binding.
*
* Example:
*
* // any class requesting an implementation of `LoggerInterface` will receive this implementation...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // but if the requesting class is `Worker` return another implementation
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @param mixed $implementation The implementation specified
*
* @return void This method does not return any value.
* @throws NotFoundException
*/
public static function give($implementation)
{
static::container()->give($implementation);
}
/**
* Returns a lambda function suitable to use as a callback; when called the function will build the implementation
* bound to `$id` and return the value of a call to `$method` method with the call arguments.
*
* @param string|class-string|object $id A fully-qualified class name, a bound slug or an object o call the
* callback on.
* @param string $method The method that should be called on the resolved implementation
* with the specified array arguments.
*
* @return callable The callback function.
* @throws ContainerException If the id is not a bound implementation or valid class name.
*/
public static function callback($id, $method)
{
return static::container()->callback($id, $method);
}
/**
* Returns a callable object that will build an instance of the specified class using the
* specified arguments when called.
* The callable will be a closure on PHP 5.3+ or a lambda function on PHP 5.2.
*
* @param string|class-string|mixed $id The fully qualified name of a class or an interface.
* @param array<mixed>|null $buildArgs An array of arguments that should be used to build the
* instance; note that any argument will be resolved using
* the container itself and bindings will apply.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return callable A callable function that will return an instance of the specified class when
* called.
*/
public static function instance($id, ?array $buildArgs = [], ?array $afterBuildMethods = null)
{
return static::container()->instance($id, $buildArgs ?? [], $afterBuildMethods);
}
/**
* Protects a value to make sure it will not be resolved, if callable or if the name of an existing class.
*
* @param mixed $value The value to protect.
*
* @return ValueBuilder A protected value instance, its value set to the provided value.
*/
public static function protect($value)
{
return static::container()->protect($value);
}
/**
* Returns the Service Provider instance registered.
*
* @param string|class-string $providerId The Service Provider class to return the instance for.
*
* @return ServiceProvider The service provider instance.
*
* @throws NotFoundException|ContainerException If the Service Provider class was never registered in the container
* or there's an issue retrieving it.
*/
public static function getProvider($providerId)
{
return static::container()->getProvider($providerId);
}
/**
* Returns whether a binding exists in the container or not.
*
* `isBound($id)` returning `true` means the call to `bind($id, $implementaion)` or `singleton($id,
* $implementation)` (or equivalent ArrayAccess methods) was explicitly made.
*
* @param string|class-string $id The id to check for bindings in the container.
*
* @return bool Whether an explicit binding for the id exists in the container or not.
*/
public static function isBound($id)
{
return static::container()->isBound($id);
}
/**
* Sets the mask for the throwables that should be caught and re-thrown as container exceptions.
*
* @param int $maskThrowables The mask for the throwables that should be caught and re-thrown as container
*
* @return void
*/
public static function setExceptionMask($maskThrowables)
{
static::container()->setExceptionMask($maskThrowables);
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* The API provided by each builder.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
/**
* Interface BuilderInterface
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
interface BuilderInterface
{
/**
* Builds and returns the implementation handled by the builder.
*
* @return mixed The implementation provided by the builder.
*/
public function build();
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Callable-based builder.
*
* @package KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use KadenceWP\KadenceBlocks\lucatume\DI52\Container;
/**
* Class CallableBuilder
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class CallableBuilder implements BuilderInterface, ReinitializableBuilderInterface
{
/**
* An instance of the DI Container.
*
* @var Container The
*/
protected $container;
/**
* The callable this builder will use.
*
* @var callable
*/
protected $callable;
/**
* An array of method that will be called on the built object.
*
* @var array<string>|null
*/
protected $afterBuildMethods;
/**
* An array of arguments that will be passed as input to the callable method.
*
* @var array<mixed>
*/
protected $buildArgs;
/**
* CallableBuilder constructor.
*
* @param Container $container An instance of the DI Container.
* @param callable $callable The builder callable.
* @param array<string>|null $afterBuildMethods A set of methods to call on the built instance.
* @param mixed ...$buildArgs A set of optional arguments for the callable method.
*/
public function __construct(Container $container, callable $callable, ?array $afterBuildMethods = null, ...$buildArgs)
{
$this->container = $container;
$this->callable = $callable;
$this->afterBuildMethods = $afterBuildMethods ?: [];
$this->buildArgs = $buildArgs;
}
/**
* Calls the callable for the builder and returns its value.
*
* @return mixed The built implementation.
*/
public function build()
{
$built = call_user_func($this->callable, ...$this->buildArgs);
foreach ((array) $this->afterBuildMethods as $afterBuildMethod) {
$built->{$afterBuildMethod}();
}
return $built;
}
/**
* Reinitialize the builder setting the after build methods and build args.
*
* @param array<string>|null $afterBuildMethods A set of methods to call on the object after it's built.
* @param mixed ...$buildArgs A set of build arguments that will be passed to the constructor.
*
* @return void This method does not return any value.
*/
public function reinit(?array $afterBuildMethods = null, ...$buildArgs)
{
$this->afterBuildMethods = $afterBuildMethods ?: [];
$this->buildArgs = $buildArgs;
}
}

View File

@@ -0,0 +1,215 @@
<?php
/**
* Builds and returns object instances.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use KadenceWP\KadenceBlocks\lucatume\DI52\ContainerException;
use KadenceWP\KadenceBlocks\lucatume\DI52\NotFoundException;
use ReflectionException;
use ReflectionMethod;
/**
* Class ClassBuilder
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class ClassBuilder implements BuilderInterface, ReinitializableBuilderInterface
{
/**
* An array cache of resolved constructor parameters, shared across all instances of the builder.
* @var array<string,array<Parameter>>
*/
protected static $constructorParametersCache = [];
/**
* A set of arguments that will be passed to the class constructor.
*
* @var array<mixed>
*/
protected $buildArgs;
/**
* The id associated with the builder by the resolver.
* @var string
*/
protected $id;
/**
* The fully-qualified class name the builder should build instances of.
*
* @var class-string
*/
protected $className;
/**
* A set of methods to call on the built object.
*
* @var array<string>|null
*/
protected $afterBuildMethods;
/**
* A reference to the resolver currently using the builder.
*
* @var Resolver
*/
protected $resolver;
/**
* Whether the $className is an implementation of $id
* and $id is an interface.
*
* @var bool
*/
protected $isInterface = false;
/**
* ClassBuilder constructor.
*
* @param string|class-string $id The identifier associated with this builder.
* @param Resolver $resolver A reference to the resolver currently using the builder.
* @param string $className The fully-qualified class name to build instances for.
* @param array<string>|null $afterBuildMethods An optional set of methods to call on the built object.
* @param mixed ...$buildArgs An optional set of build arguments that should be provided to
* the class constructor method.
*
* @throws NotFoundException If the class does not exist.
*/
public function __construct($id, Resolver $resolver, $className, ?array $afterBuildMethods = null, ...$buildArgs)
{
if (!class_exists($className)) {
throw new NotFoundException("nothing is bound to the '{$className}' id and it's not an existing or instantiable class.");
}
$interfaces = class_implements($className);
if ($interfaces && isset($interfaces[$id])) {
$this->isInterface = true;
}
$this->id = $id;
$this->className = $className;
$this->afterBuildMethods = $afterBuildMethods;
$this->resolver = $resolver;
$this->buildArgs = $buildArgs;
}
/**
* Builds and returns an instance of the class.
*
* @return object An instance of the class.
*
* @throws ContainerException
*/
public function build()
{
$constructorArgs = $this->resolveConstructorParameters();
$built = new $this->className(...$constructorArgs);
foreach ((array) $this->afterBuildMethods as $afterBuildMethod) {
$built->{$afterBuildMethod}();
}
return $built;
}
/**
* Resolves the constructor arguments to concrete implementations or values.
*
* @return array<mixed> A set of resolved constructor arguments.
*
* @throws ContainerException If a constructor argument resolution raises issues.
*/
protected function resolveConstructorParameters()
{
$constructorArgs = [];
/** @var Parameter $parameter */
foreach ($this->getResolvedConstructorParameters($this->className) as $i => $parameter) {
$this->resolver->addToBuildLine((string) $parameter->getType(), $parameter->getName());
if (isset($this->buildArgs[$i])) {
$arg = $this->buildArgs[$i];
if ($arg instanceof BuilderInterface) {
$constructorArgs[] = $arg->build();
continue;
}
$constructorArgs[] = $this->resolveBuildArg($this->buildArgs[$i]);
continue;
}
$constructorArgs[] = $this->resolveParameter($parameter);
$this->resolver->buildLinePop();
}
return $constructorArgs;
}
/**
* Returns a set of resolved constructor parameters.
*
* @param class-string $className The fully-qualified class name to get the resolved constructor parameters yet.
*
* @return array<Parameter> A set of resolved constructor parameters.
*
* @throws ContainerException If the resolution of any constructor parameters is problematic.
*/
protected function getResolvedConstructorParameters($className)
{
if (isset(self::$constructorParametersCache[$className])) {
return self::$constructorParametersCache[$className];
}
try {
$constructorReflection = new ReflectionMethod($className, '__construct');
} catch (ReflectionException $e) {
static::$constructorParametersCache[$className] = [];
// No constructor method, no args.
return [];
}
if (!$constructorReflection->isPublic()) {
throw new ContainerException("constructor method is not public.");
}
$parameters = [];
foreach ($constructorReflection->getParameters() as $i => $reflectionParameter) {
$parameters[] = new Parameter($i, $reflectionParameter);
}
self::$constructorParametersCache[$className] = $parameters;
return $parameters;
}
/**
* Resolves a build argument to a concrete implementation.
*
* @param mixed $arg The argument id or value to resolve.
*
* @return mixed The resolved build argument.
*
* @throws NotFoundException
*/
protected function resolveBuildArg($arg)
{
if (is_string($arg) && ($this->resolver->isBound($arg) || class_exists($arg))) {
return $this->resolver->resolve($arg);
}
return $arg;
}
/**
* Resolves a parameter to a concrete implementation or value.
*
* @param Parameter $parameter The parameter to resolve.
*
* @return mixed The resolved parameter.
*
* @throws ContainerException If the parameter resolution fails.
*/
protected function resolveParameter(Parameter $parameter)
{
$paramClass = $parameter->getClass();
if ($paramClass) {
$parameterImplementation = $this->resolver->whenNeedsGive($this->id, $paramClass);
} elseif ($this->isInterface) {
$name = $parameter->getName();
// If an interface was requested, resolve the underlying concrete class instead.
$parameterImplementation = $this->resolver->whenNeedsGive($this->className, "\${$name}");
} else {
$name = $parameter->getName();
$parameterImplementation = $this->resolver->whenNeedsGive($this->id, "\${$name}");
}
try {
return $parameterImplementation instanceof BuilderInterface ? $parameterImplementation->build() : $this->resolver->resolve($parameterImplementation);
} catch (NotFoundException $e) {
return $parameter->getDefaultValueOrFail();
}
}
/**
* {@inheritdoc}
*/
public function reinit(?array $afterBuildMethods = null, ...$buildArgs)
{
$this->afterBuildMethods = $afterBuildMethods;
$this->buildArgs = $buildArgs;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Closure-based builder.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use Closure;
use KadenceWP\KadenceBlocks\lucatume\DI52\Container;
/**
* Class ClosureBuilder
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class ClosureBuilder implements BuilderInterface
{
/**
* A reference to the resolver currently using the builder.
*
* @var Container
*/
protected $container;
/**
* A reference to the closure the builder should run to build.
*
* @var Closure
*/
protected $closure;
/**
* ClosureBuilder constructor.
*
* @param Container $container A reference to the current DI container instance.
* @param Closure $closure A reference to the closure that should be used to build the implementation.
*/
public function __construct(Container $container, Closure $closure)
{
$this->container = $container;
$this->closure = $closure;
}
/**
* Calls the Closure handled by the builder to return the built implementation.
*
* @return mixed The built implementation.
*/
public function build()
{
$closure = $this->closure;
return $closure($this->container);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Builds and sets up the correct builder for a binding.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use Closure;
use KadenceWP\KadenceBlocks\lucatume\DI52\Container;
use KadenceWP\KadenceBlocks\lucatume\DI52\NotFoundException;
/**
* Class Factory
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class Factory
{
/**
* A reference to the resolver that should be used to resolve the implementations.
*
* @var Resolver
*/
protected $resolver;
/**
* A reference to the DI container builder will be built for.
*
* @var Container
*/
protected $container;
/**
* BuilderFactory constructor.
* @param Container $container A reference to the DI container the builder is working for.
* @param Resolver $resolver A reference to the resolver builders will use to resolve to implementations.
*/
public function __construct(Container $container, Resolver $resolver)
{
$this->container = $container;
$this->resolver = $resolver;
}
/**
* Returns the correct builder for a value.
*
* @param string|class-string|mixed $id The string id to provide a builder for, or a value.
* @param mixed $implementation The implementation to build the builder for.
* @param string[]|null $afterBuildMethods A list of methods that should be called on the built
* instance after it's been built.
* @param mixed ...$buildArgs A set of arguments to pass that should be used to build
* the instance, if any.
*
* @return BuilderInterface A builder instance.
*
* @throws NotFoundException If a builder cannot find its implementation target.
*/
public function getBuilder($id, $implementation = null, ?array $afterBuildMethods = null, ...$buildArgs)
{
if ($implementation === null) {
$implementation = $id;
}
if (is_string($implementation) && is_string($id)) {
if (class_exists($implementation)) {
return new ClassBuilder($id, $this->resolver, $implementation, $afterBuildMethods, ...$buildArgs);
}
return new ValueBuilder($implementation);
}
if ($implementation instanceof BuilderInterface) {
return $implementation;
}
if ($implementation instanceof Closure) {
return new ClosureBuilder($this->container, $implementation);
}
if (is_callable($implementation)) {
return new CallableBuilder($this->container, $implementation);
}
return new ValueBuilder($implementation);
}
/**
* Sets the container the builder should use.
*
* @since TBD
*
* @param Container $container The container to bind.
*
* @return void
*/
public function setContainer(Container $container)
{
$this->container = $container;
}
/**
* Sets the resolver the container should use.
*
* @since TBD
*
* @param Resolver $resolver The resolver the container should use.
*
* @return void
*/
public function setResolver(Resolver $resolver)
{
$this->resolver = $resolver;
}
}

View File

@@ -0,0 +1,181 @@
<?php
/**
* The representation of a builder parameter.
*
* @package KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use KadenceWP\KadenceBlocks\lucatume\DI52\ContainerException;
use KadenceWP\KadenceBlocks\lucatume\DI52\NestedParseError;
use ParseError;
use ReflectionException;
use ReflectionParameter;
/**
* Class Parameter
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class Parameter
{
/**
* The parameter type or `null` if the type cannot be parsed.
*
* @var string|null
*/
protected $type;
/**
* Whether the parameter is an optional one or not.
*
* @var bool
*/
protected $isOptional;
/**
* The parameter default value, or `null` if not available.
*
* @var mixed|null
*/
protected $defaultValue;
/**
* Whether the parameter is a class or not.
*
* @var bool
*/
protected $isClass;
/**
* A list of the types that are NOT classes.
*
* @var array<string>
*/
protected static $nonClassTypes = ['string', 'int', 'bool', 'float', 'double', 'array', 'resource', 'callable', 'iterable', 'union'];
/**
* A map relating the string output type to the internal, type-hintable, type.
*
* @var array<string>
*/
protected static $conversionMap = ['integer' => 'int', 'boolean' => 'bool', 'double' => 'float'];
/**
* The parameter name.
*
* @var string
*/
protected $name;
/**
* Parameter constructor.
*
* @param int $index The parameter position in the list of parameters.
* @param ReflectionParameter $reflectionParameter The parameter reflection to extract the information from.
*
* @throws ReflectionException
*/
public function __construct($index, ReflectionParameter $reflectionParameter)
{
$string = $reflectionParameter->__toString();
$s = trim(str_replace('Parameter #' . $index, '', $string), '[ ]');
$frags = explode(' ', $s);
$this->name = $reflectionParameter->name;
$this->type = strpos($frags[1], '$') === 0 ? null : $frags[1];
// PHP 8.0 nullables.
$this->type = str_replace('?', '', (string) $this->type);
// PHP 8.0 Union types.
if (strpos($this->type, '|') !== false) {
$this->type = 'union';
}
if (isset(static::$conversionMap[$this->type])) {
$this->type = static::$conversionMap[$this->type];
// @codeCoverageIgnore
}
$this->isClass = $this->type && $this->isClass();
$this->isOptional = $frags[0] === '<optional>';
$this->defaultValue = $this->isOptional ? $reflectionParameter->getDefaultValue() : null;
}
/**
* Returns the parameter extracted data.
*
* @return array<string,string|bool|mixed> A map of the parameter data.
*/
public function getData()
{
return ['type' => $this->type, 'isOptional' => $this->isOptional, 'defaultValue' => $this->defaultValue];
}
/**
* Returns the parameter default value, if any.
*
* @return mixed|null The parameter default value, if any.
*/
public function getDefaultValue()
{
return $this->defaultValue;
}
/**
* Returns the parameter class name, if any.
*
* @return string|null The parameter class name, if any.
*/
public function getClass()
{
return $this->isClass ? $this->type : null;
}
/**
* Returns the parameter name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the parameter type, if any.
*
* @return string|null The parameter type, if any.
*/
public function getType()
{
return $this->type;
}
/**
* Either return the parameter default value, or die trying.
*
* @return mixed|null The parameter default value.
* @throws ContainerException If the parameter does not have a default value.
*/
public function getDefaultValueOrFail()
{
if ($this->isOptional) {
return $this->defaultValue;
}
if (!$this->isClass) {
$format = 'Parameter $%s is not optional and is not type-hinted: auto-wiring is not magic.';
$message = sprintf($format, $this->name);
} else {
$format = 'Parameter $%s is not optional and its type (%s) cannot be resolved to a concrete class.';
$message = sprintf($format, $this->name, $this->getClass());
}
throw new ContainerException($message);
}
/**
* Check if the parameter type is a class.
*
* @suppress PhanUndeclaredFunction
*
* @return bool
*
* @throws NestedParseError If a parsing error occurs while assessing the parameter type.
*/
private function isClass()
{
if (in_array($this->type, static::$nonClassTypes, true)) {
return false;
}
try {
if (function_exists('enum_exists') && enum_exists((string) $this->type)) {
return false;
}
} catch (ParseError $e) {
throw new NestedParseError($e->getMessage(), $e->getCode(), $e, (string) $this->type, $this->name);
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* The API provided by a Builder that can be re-initialized.
*
* @package KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
/**
* Interface ReinitializableBuilderInterface
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
interface ReinitializableBuilderInterface
{
/**
* Reinitialize the builder setting the after build methods and build args.
*
* @param array<string>|null $afterBuildMethods A set of methods to call on the object after it's built.
* @param mixed ...$buildArgs A set of build arguments that will be passed to the constructor.
*
* @return void This method does not return any value.
*/
public function reinit(?array $afterBuildMethods = null, ...$buildArgs);
}

View File

@@ -0,0 +1,293 @@
<?php
/**
* Resolves ids (string, class names or mixed values) to values with auto-wiring.
*
* @package KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
use KadenceWP\KadenceBlocks\lucatume\DI52\NotFoundException;
/**
* Class Resolver
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class Resolver
{
/**
* A map from ids bound in the container to their builder or resolved value.
*
* @var array<string,BuilderInterface|mixed>
*/
protected $bindings = [];
/**
* A flag property to indicate whether implicit bindings, those discovered during auto-wiring resolution, should
* be bound as prototype or singleton bindings.
*
* @var bool
*/
protected $resolveUnboundAsSingletons = false;
/**
* A map from ids bound in the container to their singleton nature.
*
* @var array<string,bool>
*/
protected $singletons = [];
/**
* A map of when-needs-give specifications.
* @var array<string,array<string,BuilderInterface>>
*/
protected $whenNeedsGive = [];
/**
* The current build line, a list from the trunk to the leaf of the current resolution.
*
* @var array<string>
*/
protected $buildLine = [];
/**
* Resolver constructor.
*
* @param false $resolveUnboundAsSingletons Whether implicit bindings, those discovered during auto-wiring
* resolution, should be bound as prototype or singleton bindings.
*/
public function __construct($resolveUnboundAsSingletons = false)
{
$this->resolveUnboundAsSingletons = $resolveUnboundAsSingletons;
}
/**
* Binds an implementation for an id, or class name, as prototype (build new each time).
*
* @param string|class-string $id The id to register the implementation for.
* @param BuilderInterface $implementation The builder that will provide the implementation for the id.
*
* @return void This method does not return any value.
*/
public function bind($id, BuilderInterface $implementation)
{
unset($this->singletons[$id]);
$this->bindings[$id] = $implementation;
}
/**
* Registers an implementation for an id, or class name, as singleton (build at most once).
*
* @param string|class-string $id The id to register the implementation for.
* @param BuilderInterface $implementation The builder that will provide the implementation for
* the id.
*
* @return void This method does not return any value.
*/
public function singleton($id, BuilderInterface $implementation)
{
$this->singletons[$id] = true;
$this->bindings[$id] = $implementation;
}
/**
* Returns whether an implementation was registered for the id in the resolver or not.
*
* @param string $id The id to check the implementation for.
*
* @return bool Whether an implementation was registered for the id in the resolver or not.
*/
public function isBound($id)
{
return isset($this->bindings[$id]);
}
/**
* Removes the relation between an id and a bound implementation from the resolver.
*
* @param string|class-string $id The id to unregister the implementation for.
*
* @return void This method does not return any value.
*/
public function unbind($id)
{
unset($this->bindings[$id], $this->whenNeedsGive[$id], $this->singletons[$id]);
}
/**
* Returns whether a specific id is bound as singleton (build at most once), or not.
*
* @param string|class-string $id The id to check.
*
* @return bool Whether a specific id is bound as singleton (build at most once), or not.
*/
public function isSingleton($id)
{
return isset($this->singletons[$id]);
}
/**
* Transform the canonical class to the class part of a when-needs-give specification, if required.
*
* @param string|class-string $id The ID to resolve the when-needs-give case for.
* @param string $paramClass The class of the parameter to solve the when-needs-give case for.
*
* @return BuilderInterface|string Either the builder for the when-needs-give replacement, or the input parameter
* class if not found.
*/
public function whenNeedsGive($id, $paramClass)
{
return isset($this->whenNeedsGive[$id][$paramClass]) ? $this->whenNeedsGive[$id][$paramClass] : $paramClass;
}
/**
* Sets an entry in the when->needs->give chain.
*
* @param string|class-string $whenClass The "when" part of the chain, a class name or id.
* @param string|class-string $needsClass The "needs" part of the chain, a class name or id.
* @param BuilderInterface $builder The Builder instance that should be returned when a class needs the
* specified id.
*
* @return void This method does not return any value.
*/
public function setWhenNeedsGive($whenClass, $needsClass, BuilderInterface $builder)
{
$this->whenNeedsGive[$whenClass][$needsClass] = $builder;
}
/**
* Resolves an ide to an implementation with the input arguments.
*
* @param string|class-string|mixed $id The id, class name or built value to resolve.
* @param string[]|null $afterBuildMethods A list of methods that should run on the built
* instance.
* @param mixed ...$buildArgs A set of build arguments that will be passed to
* the implementation constructor.
*
* @return BuilderInterface|ReinitializableBuilderInterface|mixed The builder, set up to use the specified set of
* build arguments.
* @throws NotFoundException If the id is a string that does not resolve to an existing, concrete, class.
*/
public function resolveWithArgs($id, ?array $afterBuildMethods = null, ...$buildArgs)
{
if (!is_string($id)) {
return $id;
}
if (empty($afterBuildMethods) && empty($buildArgs)) {
return $this->resolve($id);
}
return $this->cloneBuilder($id, $afterBuildMethods, ...$buildArgs)->build();
}
/**
* Resolves an id or input value to a value or object instance.
*
* @template T
*
* @param string|class-string<T>|mixed $id Either the id of a bound implementation, a class name or an
* object to resolve.
* @param string[]|null $buildLine The build line to append the resolution leafs to, or `null` to
* use the current one.
*
* @return T|mixed The resolved value or instance.
* @phpstan-return ($id is class-string ? T : mixed)
*
* @throws NotFoundException If the id is a string that is not bound and is not an existing, concrete, class.
*/
public function resolve($id, ?array $buildLine = null)
{
if ($buildLine !== null) {
$this->buildLine = $buildLine;
}
if (!is_string($id)) {
return $id;
}
if (!isset($this->bindings[$id])) {
return $this->resolveUnbound($id);
}
if ($this->bindings[$id] instanceof BuilderInterface) {
$built = $this->resolveBound($id);
} else {
$built = $this->bindings[$id];
}
return $built;
}
/**
* Builds, with auto-wiring, an instance of a not bound class.
*
* @param string|class-string $id The class name to build an instance of.
*
* @return object The built class instance.
*
* @throws NotFoundException If the id cannot be resolved to an existing, concrete class.
*/
private function resolveUnbound($id)
{
$built = (new ClassBuilder($id, $this, $id))->build();
if ($this->resolveUnboundAsSingletons) {
$this->singletons[$id] = true;
$this->bindings[$id] = $built;
}
return $built;
}
/**
* Resolves a bound implementation to a value or object.
*
* @param string|class-string $id The id to resolve the implementation for.
*
* @return mixed The resolved instance.
*/
private function resolveBound($id)
{
// @phpstan-ignore-next-line
$built = $this->bindings[$id]->build();
if (isset($this->singletons[$id])) {
$this->bindings[$id] = $built;
}
return $built;
}
/**
* Clones the builder assigned to an id and re-initializes it.
* The clone operation leverages the already resolved dependencies of a builder to create an up-to-date instance.
*
* @param string|class-string $id The id to clone the builder of.
* @param string[]|null $afterBuildMethods A set of methods to run on the built instance.
* @param mixed ...$buildArgs An optional set of arguments that will be passed to the instance
* constructor.
*
* @return BuilderInterface A new instance of the builder currently related to the id.
* @throws NotFoundException If trying to clone the builder for a non existing id or an id that does not map to a
* concrete class name.
*/
private function cloneBuilder($id, ?array $afterBuildMethods = null, ...$buildArgs)
{
if (isset($this->bindings[$id]) && $this->bindings[$id] instanceof BuilderInterface) {
$builder = clone $this->bindings[$id];
if ($builder instanceof ReinitializableBuilderInterface) {
$builder->reinit($afterBuildMethods, ...$buildArgs);
}
} else {
$builder = new ClassBuilder($id, $this, $id, $afterBuildMethods, ...$buildArgs);
}
return $builder;
}
/**
* Adds an entry to the build line.
*
* @param string $type The type of parameter the Resolver is currently attempting to resolve.
* @param string $parameterName The name of the parameter in the method signature, if any.
*
* @return void This method does not return any value.
*/
public function addToBuildLine($type, $parameterName)
{
$this->buildLine[] = trim("{$type} \${$parameterName}");
}
/**
* Returns the current build line.
*
* The build line will return a straight path from the current resolution root to the leaf
* currently being resolved. Used for error logging and formatting.
*
* @return string[] A set of consecutive items the resolver is currently trying to build.
*/
public function getBuildLine()
{
return $this->buildLine;
}
/**
* Removes the last element from the build line, if any.
*
* @return void The method does not return any value.
*/
public function buildLinePop()
{
array_pop($this->buildLine);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* A builder wrapping a value that will return upon build.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52\Builders;
/**
* Class ValueBuilder
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52\Builders
*/
class ValueBuilder implements BuilderInterface
{
/**
* The value the instance of the builder was built for.
*
* @var mixed
*/
private $value;
/**
* ValueBuilder constructor.
*
* @param mixed $value The value to build the instance of the builder for.
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Builds and returns an instance of the builder built on the specified value.
*
* @param mixed $value The value the instance of the builder should be built for.
*
* @return ValueBuilder An instance of the builder built on the specified value.
*/
public static function of($value)
{
return $value instanceof self ? $value : new self($value);
}
/**
* Returns the value wrapped by the builder.
*
* @return mixed The value wrapped by the builder.
*/
public function build()
{
return $this->value;
}
}

View File

@@ -0,0 +1,840 @@
<?php
/**
* The Dependency Injection container.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
use ArrayAccess;
use Closure;
use Exception;
use KadenceWP\KadenceBlocks\lucatume\DI52\Builders\BuilderInterface;
use KadenceWP\KadenceBlocks\lucatume\DI52\Builders\ValueBuilder;
use KadenceWP\KadenceBlocks\Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ReturnTypeWillChange;
use Throwable;
use function spl_object_hash;
/**
* Class Container
*
* @package \lucatume\DI52
* @implements ArrayAccess<string,object>
*/
class Container implements ArrayAccess, ContainerInterface
{
const EXCEPTION_MASK_NONE = 0;
const EXCEPTION_MASK_MESSAGE = 1;
const EXCEPTION_MASK_FILE_LINE = 2;
/**
* An array cache to store the results of the class exists checks.
*
* @var array<string,bool>
*/
protected $classIsInstantiatableCache = [];
/**
* A cache of what methods are static and what are not.
*
* @var array<string,bool>
*/
protected $isStaticMethodCache = [];
/**
* A list of bound and resolved singletons.
*
* @var array<string|class-string,bool>
*/
protected $singletons = [];
/**
* @var array<ServiceProvider>
*/
protected $deferred = [];
/**
* @var array<string,array<string|object|callable>>
*/
protected $tags = [];
/**
* @var array<ServiceProvider>
*/
protected $bootable = [];
/**
* @var string
*/
protected $whenClass;
/**
* @var string
*/
protected $needsClass;
/**
* A map from class name and static methods to the built callback.
*
* @var array<string,Closure>
*/
protected $callbacks = [];
/**
* @var Builders\Resolver
*/
protected $resolver;
/**
* @var Builders\Factory
*/
protected $builders;
/**
* What kind of masking should be applied to throwables catched by the container during resolution.
*
* @var int
*/
private $maskThrowables = self::EXCEPTION_MASK_MESSAGE | self::EXCEPTION_MASK_FILE_LINE;
/**
* Container constructor.
*
* @param false $resolveUnboundAsSingletons Whether unbound classes should be resolved as singletons by default,
* or not.
*/
public function __construct($resolveUnboundAsSingletons = false)
{
$this->resolver = new Builders\Resolver($resolveUnboundAsSingletons);
$this->builders = new Builders\Factory($this, $this->resolver);
$this->bindThis();
}
/**
* Sets a variable on the container.
*
* @param string $key The alias the container will use to reference the variable.
* @param mixed $value The variable value.
*
* @return void The method does not return any value.
*/
public function setVar($key, $value)
{
$this->resolver->bind($key, ValueBuilder::of($value));
}
/**
* Sets a variable on the container using the ArrayAccess API.
*
* When using the container as an array bindings will be bound as singletons.
* These are equivalent: `$container->singleton('foo','ClassOne');`, `$container['foo'] = 'ClassOne';`.
*
* @param string $offset The alias the container will use to reference the variable.
* @param mixed $value The variable value.
*
* @return void This method does not return any value.
*
* @throws ContainerException If the closure building fails.
*/
#[ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->singleton($offset, $value);
}
/**
* Binds an interface a class or a string slug to an implementation and will always return the same instance.
*
* @param string $id A class or interface fully qualified name or a string slug.
* @param mixed $implementation The implementation that should be bound to the alias(es); can be a
* class name, an object or a closure.
* @param array<string>|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return void This method does not return any value.
* @throws ContainerException If there's any issue reflecting on the class, interface or the implementation.
*/
public function singleton($id, $implementation = null, ?array $afterBuildMethods = null)
{
if ($implementation === null) {
$implementation = $id;
}
$this->resolver->singleton($id, $this->builders->getBuilder($id, $implementation, $afterBuildMethods));
}
/**
* Returns a variable stored in the container.
*
* If the variable is a binding then the binding will be resolved before returning it.
*
* @param string $key The alias of the variable or binding to fetch.
* @param mixed|null $default A default value to return if the variable is not set in the container.
*
* @return mixed The variable value or the resolved binding.
* @throws ContainerException If there's an issue resolving the variable.
*
* @see Container::get()
*/
public function getVar($key, $default = null)
{
if ($this->resolver->isBound($key)) {
return $this->resolver->resolve($key);
}
return $default;
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @template T
*
* @param string|class-string<T> $offset Identifier of the entry to look for.
*
* @return T|mixed The value for the offset.
* @phpstan-return ($offset is class-string ? T : mixed)
*
* @throws ContainerException Error while retrieving the entry.
* @throws NotFoundException No entry was found for **this** identifier.
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @template T
*
* @param string|class-string<T> $id A fully qualified class or interface name or an already built object.
*
* @return T|mixed The entry for an id.
* @phpstan-return ($id is class-string ? T : mixed)
*
* @throws ContainerException Error while retrieving the entry.
*/
public function get($id)
{
try {
return $this->resolver->resolve($id, [$id]);
} catch (Throwable $throwable) {
throw $this->castThrown($throwable, $id);
// @codeCoverageIgnoreStart
} catch (Exception $exception) {
// @phan-suppress-current-line PhanUnreachableCatch @phpstan-ignore-line
throw $this->castThrown($exception, $id);
// @codeCoverageIgnoreEnd
}
}
/**
* Builds an instance of the exception with a pretty message.
*
* @param Exception|Throwable $thrown The exception to cast.
* @param string|object $id The top identifier the containe was attempting to build, or object.
*
* @return ContainerException|Exception|Throwable The cast exception.
*/
private function castThrown($thrown, $id)
{
if ($this->maskThrowables === self::EXCEPTION_MASK_NONE) {
return $thrown;
}
return ContainerException::fromThrowable($id, $thrown, $this->maskThrowables, $this->resolver->getBuildLine());
}
/**
* Returns an instance of the class or object bound to an interface, class or string slug if any, else it will try
* to automagically resolve the object to a usable instance.
*
* If the implementation has been bound as singleton using the `singleton` method
* or the ArrayAccess API then the implementation will be resolved just on the first request.
*
* @template T
*
* @param string|class-string<T> $id A fully qualified class or interface name or an already built object.
*
* @return T|mixed
* @phpstan-return ($id is class-string ? T : mixed)
*
* @throws ContainerException If the target of the make is not bound and is not a valid,
* concrete, class name or there's any issue making the target.
*/
public function make($id)
{
return $this->get($id);
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `$container[$id]` returning true does not mean that `$container[$id]` will not throw an exception.
* It does however mean that `$container[$id]` will not throw a `NotFoundExceptionInterface`.
*
* @param string|class-string $offset An offset to check for.
*
* @return boolean true on success or false on failure.
*/
#[ReturnTypeWillChange]
public function offsetExists($offset)
{
return $this->has($offset);
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string|class-string $id Identifier of the entry to look for.
*
* @return bool Whether the container contains a binding for an id or not.
*/
public function has($id)
{
return $this->resolver->isBound($id) || class_exists($id);
}
/**
* Tags an array of implementations bindings for later retrieval.
*
* The implementations can also reference interfaces, classes or string slugs.
* Example:
*
* $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
*
* @param array<string|callable|object> $implementationsArray The ids, class names or objects to apply the tag to.
* @param string $tag The tag to apply.
*
* @return void This method does not return any value.
* @see Container::tagged()
*
*/
public function tag(array $implementationsArray, $tag)
{
$this->tags[$tag] = $implementationsArray;
}
/**
* Retrieves an array of bound implementations resolving them.
*
* The array of implementations should be bound using the `tag` method:
*
* $container->tag(['Posts', 'Users', 'Comments'], 'endpoints');
* foreach($container->tagged('endpoints') as $endpoint){
* $endpoint->register();
* }
*
* @param string $tag The tag to return the tagged values for.
*
* @return array<mixed> An array of resolved bound implementations.
* @throws NotFoundException If nothing is tagged with the tag.
* @throws ContainerException If one of the bindings is not of the correct type.
* @see Container::tag()
*/
public function tagged($tag)
{
if (!$this->hasTag($tag)) {
throw new NotFoundException("Nothing is tagged as '{$tag}'");
}
return array_map(function ($id) {
if (is_string($id)) {
return $this->get($id);
}
return $this->builders->getBuilder($id)->build();
}, $this->tags[$tag]);
}
/**
* Checks whether a tag group exists in the container.
*
* @param string $tag
*
* @return bool
* @see Container::tag()
*
*/
public function hasTag($tag)
{
return isset($this->tags[$tag]);
}
/**
* A wrapper around the `class_exists` function to capture and handle possible fatal errors on PHP 7.0+.
*
* @param string|class-string $class The class name to check.
*
* @return bool Whether the class exists or not.
*
* @throws ContainerException|ReflectionException If the class has syntax or other errors preventing its load.
*/
protected function classIsInstantiable($class)
{
if (isset($this->classIsInstantiatableCache[$class])) {
return $this->classIsInstantiatableCache[$class];
}
// @codeCoverageIgnoreStart
if (PHP_VERSION_ID < 70000) {
$isInstantiatable = $this->checkClassIsInstantiatable($class);
$this->classIsInstantiatableCache[$class] = $isInstantiatable;
return $isInstantiatable;
}
// @codeCoverageIgnoreEnd
// PHP 7.0+ allows handling fatal errors; x_exists will trigger auto-loading, that might result in an error.
try {
$isInstantiatable = $this->checkClassIsInstantiatable($class);
$this->classIsInstantiatableCache[$class] = $isInstantiatable;
return $isInstantiatable;
} catch (Throwable $e) {
$this->classIsInstantiatableCache[$class] = false;
throw new ContainerException($e->getMessage());
}
}
/**
* Checks a class, interface or trait exists.
*
* @param string|class-string $class The class, interface or trait to check.
*
* @return bool Whether the class, interface or trait exists or not.
* @throws ReflectionException If the class should be checked for concreteness and it does not exist.
*/
protected function checkClassIsInstantiatable($class)
{
$exists = class_exists($class);
if (!$exists) {
return false;
}
$classReflection = new ReflectionClass($class);
if ($classReflection->isAbstract()) {
return false;
}
$constructor = $classReflection->getConstructor();
if ($constructor === null) {
return true;
}
return $constructor->isPublic();
}
/**
* Registers a service provider implementation.
*
* The `register` method will be called immediately on the service provider.
*
* If the provider overloads the `isDeferred` method returning a truthy value then the `register` method will be
* called only if one of the implementations provided by the provider is requested. The container defines which
* implementations is offering overloading the `provides` method; the method should return an array of provided
* implementations.
*
* If a provider overloads the `boot` method that method will be called when the `boot` method is called on the
* container itself.
*
* @param class-string $serviceProviderClass The fully-qualified Service Provider class name.
* @param string ...$alias A list of aliases the provider should be registered with.
* @return void This method does not return any value.
* @throws ContainerException If the Service Provider is not correctly configured or there's an issue
* reflecting on it.
* @see ServiceProvider::register()
* @see ServiceProvider::isDeferred()
* @see ServiceProvider::provides()
* @see Container::getProvider()
* @see ServiceProvider::boot()
*/
public function register($serviceProviderClass, ...$alias)
{
/** @var ServiceProvider $provider */
$provider = $this->get($serviceProviderClass);
if (!$provider->isDeferred()) {
$provider->register();
} else {
$provided = $provider->provides();
// @phpstan-ignore-next-line
if (!is_array($provided) || count($provided) === 0) {
throw new ContainerException("Service provider '{$serviceProviderClass}' is marked as deferred" . " but is not providing any implementation.");
}
foreach ($provided as $id) {
$this->resolver->bind($id, $this->builders->getBuilder($this->getDeferredProviderMakeClosure($provider, $id)));
}
}
try {
$bootMethod = new ReflectionMethod($provider, 'boot');
} catch (ReflectionException $e) {
throw new ContainerException('Could not reflect on the provider boot method.');
}
$requiresBoot = $bootMethod->getDeclaringClass()->getName() === get_class($provider);
if ($requiresBoot) {
$this->bootable[] = $provider;
}
$this->resolver->singleton($serviceProviderClass, new ValueBuilder($provider));
foreach ($alias as $a) {
$this->resolver->singleton($a, new ValueBuilder($provider));
}
}
/**
* Returns a closure that will build a provider on demand, if an implementation provided by the provider is
* required.
*
* @param ServiceProvider $provider The provider instance to register.
* @param string $id The id of the implementation to bind.
*
* @return Closure A Closure ready to be bound to the id as implementation.
*/
private function getDeferredProviderMakeClosure(ServiceProvider $provider, $id)
{
return function () use ($provider, $id) {
static $registered;
if ($registered === null) {
$provider->register();
$registered = true;
}
return $this->get($id);
};
}
/**
* Binds an interface, a class or a string slug to an implementation.
*
* Existing implementations are replaced.
*
* @param string|class-string $id A class or interface fully qualified name or a string slug.
* @param mixed $implementation The implementation that should be bound to the alias(es); can
* be a class name, an object or a closure.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return void The method does not return any value.
*
* @throws ContainerException If there's an issue while trying to bind the implementation.
*/
public function bind($id, $implementation = null, ?array $afterBuildMethods = null)
{
if ($implementation === null) {
$implementation = $id;
}
if ($implementation === $id && !$this->classIsInstantiable($implementation)) {
throw new NotFoundException("Class {$implementation} does not exist.");
}
$this->resolver->bind($id, $this->builders->getBuilder($id, $implementation, $afterBuildMethods));
}
/**
* Boots up the application calling the `boot` method of each registered service provider.
*
* If there are bootable providers (providers overloading the `boot` method) then the `boot` method will be
* called on each bootable provider.
*
* @return void This method does not return any value.
*
* @see ServiceProvider::boot()
*/
public function boot()
{
if (!empty($this->bootable)) {
foreach ($this->bootable as $provider) {
/** @var ServiceProvider $provider */
$provider->boot();
}
}
}
/**
* Binds a class, interface or string slug to a chain of implementations decorating a base
* object; the chain will be lazily resolved only on the first call.
* The base decorated object must be the last element of the array.
*
* @param string|class-string $id The class, interface or slug the decorator chain
* should be bound to.
* @param array<string|object|callable> $decorators An array of implementations that decorate an object.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the
* instance after it has been built; the methods should
* not require any argument.
* @param bool $afterBuildAll Whether to call the after build methods on only the
* base instance or all instances of the decorator chain.
*
* @return void This method does not return any value.
* @throws ContainerException
*/
public function singletonDecorators($id, $decorators, ?array $afterBuildMethods = null, $afterBuildAll = false)
{
$this->resolver->singleton($id, $this->getDecoratorBuilder($decorators, $id, $afterBuildMethods, $afterBuildAll));
}
/**
* Builds and returns a closure that will start building the chain of decorators.
*
* @param array<string|object|callable> $decorators A list of decorators.
* @param string $id The id to bind the decorator tail to.
* @param array<string>|null $afterBuildMethods A set of method to run on the built decorated instance
* after it's built.
* @param bool $afterBuildAll Whether to run the after build methods only on the base
* instance (default, false) or on all instances of the
* decorator chain.
*
* @return BuilderInterface The callable or Closure that will start building the decorator chain.
*
* @throws ContainerException If there's any issue while trying to register any decorator step.
*/
private function getDecoratorBuilder(array $decorators, $id, ?array $afterBuildMethods = null, $afterBuildAll = false)
{
$decorator = array_pop($decorators);
if ($decorator === null) {
throw new ContainerException('The decorator chain cannot be empty.');
}
do {
$previous = isset($builder) ? $builder : null;
$builder = $this->builders->getBuilder($id, $decorator, $afterBuildMethods, $previous);
$decorator = array_pop($decorators);
if (!$afterBuildAll) {
$afterBuildMethods = [];
}
} while ($decorator !== null);
return $builder;
}
/**
* Binds a class, interface or string slug to a chain of implementations decorating a
* base object.
*
* The base decorated object must be the last element of the array.
*
* @param string|class-string $id The class, interface or slug the decorator chain
* should be bound to.
* @param array<string|object|callable> $decorators An array of implementations that decorate an object.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the
* base instance after it has been built; the methods
* should not require any argument.
* @param bool $afterBuildAll Whether to call the after build methods on only the
* base instance or all instances of the decorator chain.
*
* @return void This method does not return any value.
* @throws ContainerException If there's any issue binding the decorators.
*/
public function bindDecorators($id, array $decorators, ?array $afterBuildMethods = null, $afterBuildAll = false)
{
$this->resolver->bind($id, $this->getDecoratorBuilder($decorators, $id, $afterBuildMethods, $afterBuildAll));
}
/**
* Unsets a binding or tag in the container.
*
* @param mixed $offset The offset to unset.
*
* @return void The method does not return any value.
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset)
{
if (!is_string($offset)) {
return;
}
$this->resolver->unbind($offset);
unset($this->tags[$offset]);
}
/**
* Starts the `when->needs->give` chain for a contextual binding.
*
* @param string|class-string $class The fully qualified name of the requesting class.
*
* Example:
*
* // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // But if the requesting class is `Worker` return another implementation
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @return Container The container instance, to continue the when/needs/give chain.
*/
public function when($class)
{
$this->whenClass = $class;
return $this;
}
/**
* Second step of the `when->needs->give` chain for a contextual binding.
*
* Example:
*
* // Any class requesting an implementation of `LoggerInterface` will receive this implementation ...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // But if the requesting class is `Worker` return another implementation.
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @param string|class-string $id The class or interface needed by the class.
*
* @return Container The container instance, to continue the when/needs/give chain.
*/
public function needs($id)
{
$this->needsClass = $id;
return $this;
}
/**
* Third step of the `when->needs->give` chain for a contextual binding.
*
* Example:
*
* // any class requesting an implementation of `LoggerInterface` will receive this implementation...
* $container->singleton('LoggerInterface', 'FilesystemLogger');
* // but if the requesting class is `Worker` return another implementation
* $container->when('Worker')
* ->needs('LoggerInterface')
* ->give('RemoteLogger');
*
* @param mixed $implementation The implementation specified
*
* @return void This method does not return any value.
* @throws NotFoundException
*/
public function give($implementation)
{
$id = "{$this->whenClass}::{$this->needsClass}";
$builder = $this->builders->getBuilder($id, $implementation);
$this->resolver->setWhenNeedsGive($this->whenClass, $this->needsClass, $builder);
// @phpstan-ignore unset.possiblyHookedProperty
unset($this->whenClass, $this->needsClass);
// @phpstan-ignore unset.possiblyHookedProperty
}
/**
* Returns a lambda function suitable to use as a callback; when called the function will build the implementation
* bound to `$id` and return the value of a call to `$method` method with the call arguments.
*
* @param string|class-string|object $id A fully-qualified class name, a bound slug or an object o call the
* callback on.
* @param string $method The method that should be called on the resolved implementation
* with the specified array arguments.
*
* @return callable|Closure The callback function.
* @throws ContainerException If the id is not a bound implementation or valid class name.
*/
public function callback($id, $method)
{
$callbackIdPrefix = is_object($id) ? spl_object_hash($id) : $id;
// @phpstan-ignore-next-line
if (!is_string($callbackIdPrefix)) {
$typeOfId = gettype($id);
throw new ContainerException("Callbacks can only be built on ids, class names or objects; '{$typeOfId}' is neither.");
}
// @phpstan-ignore-next-line
if (!is_string($method)) {
throw new ContainerException("Callbacks second argument must be a string method name.");
}
$callbackId = $callbackIdPrefix . '::' . $method;
if (isset($this->callbacks[$callbackId])) {
return $this->callbacks[$callbackId];
}
$callbackClosure = function (...$args) use ($id, $method) {
$instance = is_string($id) ? $this->resolver->resolve($id) : $this->builders->getBuilder($id)->build();
return $instance->{$method}(...$args);
};
if (is_string($id) && ($this->resolver->isSingleton($id) || $this->isStaticMethod($id, $method))) {
// If we can know immediately, without actually resolving the binding, then build and cache immediately.
$this->callbacks[$callbackId] = $callbackClosure;
}
return $callbackClosure;
}
/**
* Whether a method of an id, possibly not a class, is static or not.
*
* @param object|string|class-string $object A class name, instance or something that does not map to a class.
* @param string $method The method to check.
*
* @return bool Whether a method of an id or class is static or not.
*/
protected function isStaticMethod($object, $method)
{
$key = is_string($object) ? $object . '::' . $method : get_class($object) . '::' . $method;
if (!isset($this->isStaticMethodCache[$key])) {
try {
$this->isStaticMethodCache[$key] = (new ReflectionMethod($object, $method))->isStatic();
} catch (ReflectionException $e) {
return false;
}
}
return $this->isStaticMethodCache[$key];
}
/**
* Returns a callable object that will build an instance of the specified class using the
* specified arguments when called.
* The callable will be a closure on PHP 5.3+ or a lambda function on PHP 5.2.
*
* @param string|class-string|mixed $id The fully qualified name of a class or an interface.
* @param array<mixed> $buildArgs An array of arguments that should be used to build the
* instance; note that any argument will be resolved using
* the container itself and bindings will apply.
* @param string[]|null $afterBuildMethods An array of methods that should be called on the built
* implementation after resolving it.
*
* @return callable|Closure A callable function that will return an instance of the specified class when
* called.
*/
public function instance($id, array $buildArgs = [], ?array $afterBuildMethods = null)
{
return function () use ($id, $afterBuildMethods, $buildArgs) {
if (is_string($id)) {
return $this->resolver->resolveWithArgs($id, $afterBuildMethods, ...$buildArgs);
}
return $this->builders->getBuilder($id, $id, $afterBuildMethods, ...$buildArgs)->build();
};
}
/**
* Protects a value to make sure it will not be resolved, if callable or if the name of an existing class.
*
* @param mixed $value The value to protect.
*
* @return ValueBuilder A protected value instance, its value set to the provided value.
*/
public function protect($value)
{
return new ValueBuilder($value);
}
/**
* Returns the Service Provider instance registered.
*
* @param string|class-string $providerId The Service Provider clas to return the instance for.
*
* @return ServiceProvider The service provider instance.
*
* @throws NotFoundException|ContainerException If the Service Provider class was never registered in the container
* or there's an issue retrieving it.
*/
public function getProvider($providerId)
{
if (!$this->resolver->isBound($providerId)) {
throw new NotFoundException("Service provider '{$providerId}' is not registered in the container.");
}
$provider = $this->get($providerId);
if (!$provider instanceof ServiceProvider) {
throw new NotFoundException("Bound implementation for '{$providerId}' is not Service Provider.");
}
return $provider;
}
/**
* Returns whether a binding exists in the container or not.
*
* `isBound($id)` returning `true` means the a call to `bind($id, $implementaion)` or `singleton($id,
* $implementation)` (or equivalent ArrayAccess methods) was explicitly made.
*
* @param string $id The id to check for bindings in the container.
*
* @return bool Whether an explicit binding for the id exists in the container or not.
*/
public function isBound($id)
{
// @phpstan-ignore-next-line
return is_string($id) && $this->resolver->isBound($id);
}
/**
* Sets the mask for the throwables that should be caught and re-thrown as container exceptions.
*
* @param int $maskThrowables The mask for the throwables that should be caught and re-thrown as container
*
* @return void
*/
public function setExceptionMask($maskThrowables)
{
$this->maskThrowables = (int) $maskThrowables;
}
/**
* Binds the container to the base class name, the current class name and the container interface.
*
* @return void
*/
private function bindThis()
{
$this->singleton(ContainerInterface::class, $this);
$this->singleton(Container::class, $this);
if (get_class($this) !== Container::class) {
$this->singleton(get_class($this), $this);
}
}
/**
* Upon cloning, clones the resolver and builders instances.
*
* @return void
*/
public function __clone()
{
$this->resolver = clone $this->resolver;
$this->builders = clone $this->builders;
$this->builders->setContainer($this);
$this->builders->setResolver($this->resolver);
$this->bindThis();
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* An exception thrown while trying to build or resolve a binding in the container.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
use Exception;
use KadenceWP\KadenceBlocks\Psr\Container\ContainerExceptionInterface;
use ReflectionClass;
use Throwable;
/**
* Class ContainerException
*
* @package \lucatume\DI52
*/
class ContainerException extends Exception implements ContainerExceptionInterface
{
/**
* Extracts a property from an object.
*
* @param object $object The object to extract the property from.
* @param string $property The property to extract.
*
* @return mixed The property value if found, `null` otherwise.
*/
private static function getPropertyValue($object, $property)
{
$reflectionClass = new ReflectionClass($object);
do {
if ($reflectionClass->hasProperty($property)) {
$traceProperty = $reflectionClass->getProperty($property);
$traceProperty->setAccessible(true);
return $traceProperty->getValue($object);
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass instanceof ReflectionClass);
return null;
}
/**
* Sets a private or protected property on an object.
*
* @param object $object The object to set the property on.
* @param string $property The property to set.
* @param mixed $value The value to set.
*
* @return bool Whether the property was set or not.
*/
private static function setPropertyValue($object, $property, $value)
{
$reflectionClass = new ReflectionClass($object);
do {
if ($reflectionClass->hasProperty($property)) {
$traceProperty = $reflectionClass->getProperty($property);
$traceProperty->setAccessible(true);
$traceProperty->setValue($object, $value);
return true;
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass instanceof ReflectionClass);
return false;
}
/**
* Formats an error message to provide a useful debug message.
*
* @param string|object $id The id of what is actually being built or the object that is being built.
* @param Exception|Throwable $thrown The original exception thrown while trying to make the target.
* @param array<string> $buildLine A set of consecutive items the resolver is currently trying to build.
*
* @return string The formatted make error message.
*/
private static function makeBuildLineErrorMessage($id, $thrown, array $buildLine)
{
$idString = is_string($id) ? $id : gettype($id);
if ($thrown instanceof NestedParseError) {
$last = $thrown->getType() . ' $' . $thrown->getName();
} else {
$last = array_pop($buildLine) ?: $idString;
}
$lastEntry = "Error while making {$last}: " . lcfirst(rtrim(str_replace('"', '', $thrown->getMessage()), '.')) . '.';
$frags = array_merge($buildLine, [$lastEntry]);
return implode("\n\t=> ", $frags);
}
/**
* Builds a container exception from a throwable.
*
* @param string|object $id The id of what is actually being built or the object that is being built.
* @param Exception|Throwable $thrown The throwable to build the exception from.
* @param int $maskThrowables The bitmask of throwable properties to mask.
* @param array<string> $buildLine A set of consecutive items the resolver is currently trying to build.
*
* @return ContainerException The built Container exception.
*/
public static function fromThrowable($id, $thrown, $maskThrowables, array $buildLine)
{
$message = $maskThrowables & Container::EXCEPTION_MASK_MESSAGE ? self::makeBuildLineErrorMessage($id, $thrown, $buildLine) : $thrown->getMessage();
$exceptionClass = $thrown instanceof self ? get_class($thrown) : self::class;
$built = new $exceptionClass($message, $thrown->getCode(), $thrown);
if ($maskThrowables & Container::EXCEPTION_MASK_FILE_LINE && ($thrownFile = self::getPropertyValue($thrown, 'file')) && $thrownLine = self::getPropertyValue($thrown, 'line')) {
self::setPropertyValue($built, 'file', $thrownFile);
self::setPropertyValue($built, 'line', $thrownLine);
}
return $built;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Class used to handle the special case of a parse error in a nested dependency
* and bubble its nature up.
*
* @package KadenceWP\KadenceBlocks\lucatume\DI52;
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
use Throwable;
/**
* Class NestedParseError.
*
* @package \KadenceWP\KadenceBlocks\lucatume\DI52;
*/
class NestedParseError extends \Exception
{
/**
* The type of the entity being loaded.
*
* @var string
*/
private $type;
/**
* The name of the entity being loaded.
*
* @var string
*/
private $name;
/**
* NestedParseError constructor.
*
* @param string $message The Exception message to throw.
* @param int $code The Exception code.
* @param Throwable|null $previous The previous exception used for the exception chaining.
* @param string $type The type of the entity being loaded.
* @param string $name The name of the entity being loaded.
*/
public function __construct($message = "", $code = 0, ?Throwable $previous = null, $type = '', $name = '')
{
parent::__construct($message, $code, $previous);
$this->type = $type;
$this->name = $name;
}
/**
* Returns the type of the entity being loaded.
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Returns the name of the entity being loaded.
*
* @return string
*/
public function getName()
{
return $this->name;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* An exception used to signal no binding was found for container ID.
*
* @package lucatume\DI52
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
use KadenceWP\KadenceBlocks\Psr\Container\NotFoundExceptionInterface;
/**
* Class NotFoundException
*
* @package \lucatume\DI52
*/
class NotFoundException extends ContainerException implements NotFoundExceptionInterface
{
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* The base service provider class.
*/
namespace KadenceWP\KadenceBlocks\lucatume\DI52;
/**
* Class ServiceProvider
*
* @package \lucatume\DI52
*/
abstract class ServiceProvider
{
/**
* Whether the service provider will be a deferred one or not.
*
* @var bool
*/
protected $deferred = false;
/**
* @var Container
*/
protected $container;
/**
* ServiceProvider constructor.
*
* @param Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Whether the service provider will be a deferred one or not.
*
* @return bool
*/
public function isDeferred()
{
return $this->deferred;
}
/**
* Returns an array of the class or interfaces bound and provided by the service provider.
*
* @return array<string> A list of fully-qualified implementations provided by the service provider.
*/
public function provides()
{
return [];
}
/**
* Binds and sets up implementations at boot time.
*
* @return void The method will not return any value.
*/
public function boot()
{
// no-op
}
/**
* Registers the service provider bindings.
*
* @return void The method does not return any value.
*/
abstract public function register();
}