Skip to content
Snippets Groups Projects
Commit f5c07186 authored by Marcin Kolanko's avatar Marcin Kolanko
Browse files

Merge branch '1.x' into 'review'

1.x

See merge request !2
parents ce884c34 ddca3cac
No related branches found
No related tags found
1 merge request!21.x
Showing
with 623 additions and 0 deletions
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
indent_size = 4
tab_width = 4
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpcs.xml.dist export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/vendor export-ignore
.DS_Store
vendor
composer.lock
.phpunit.result.cache
.phpcs-cache
# WordPress plugin initializer
Boot your plugin with superpowers.
## Installation
To use this library in your project, add it to `composer.json`:
```sh
composer require wpdesk/wp-init
```
## Creating a Plugin
Preferred method of using this library exercise Object Oriented Programming and organizing your
actions and filters in a multiple classes, although it isn't the only way you can interact (and
benefit from this library).
The plugin initialization consists of the following steps:
1. Create a regular main plugin file, following [header requirements](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/)
1. Prepare DI container definitions for your services.
1. Declare all classes included in hook binding.
The above limits your main plugin file to a short and simple structure.
```php
<?php
/**
* Plugin Name: Example Plugin
*/
use WPDesk\Init\Init;
require __DIR__ . '/vendor/autoload.php';
Init::setup('config.php')->boot();
```
### Plugin configuration
For plugin configuration, you may focus on succinct, declarative configuration.
[Supported configuration](docs/configuration.md):
```php
<?php
return [
'hook_resources_path' => 'config/hook_providers',
'services' => 'config/services.inc.php',
'cache_path' => 'generated',
'requirements' => [
'plugins' => [
'name' => 'woocommerce/woocommerce.php',
'nice_name' => 'WooCommerce',
]
],
'plugin_class_name' => 'Example\Plugin',
];
```
## Usage with `wpdesk/wp-builder`
As a legacy support, it is possible to power up your existing codebase, which uses
`wpdesk/wp-builder` with this library capabilities, as autowired services.
The only change, you have to do (besides configuration of services) is adding _hookables_ as class
string, ready for handling by DI container:
```diff
- $this->add_hookable( new \WPDesk\Init\Provider\I18n() );
+ $this->add_hookable( \WPDesk\Init\Provider\I18n::class );
```
## Credits
This package is heavily inspired by Cedaro's [`wp-plugin`](https://github.com/cedaro/wp-plugin/)
and Alain Schlesser's [`basic-scaffold`](https://github.com/mwpd/basic-scaffold).
## License
Copyright (c) 2024 WPDesk
This library is licensed under MIT.
#!/usr/bin/env php
<?php
use WPDesk\Init\Util\PhpFileDumper;
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';
$filename = $argv[1] ?? null;
$parser = new \WPDesk\Init\Plugin\DefaultHeaderParser();
$data = $parser->parse( $filename );
$dumper = new PhpFileDumper();
$dumper->dump( $parser->parse( $filename ), $argv[2] );
//echo "Compile static plugin resources.";
{
"name": "wpdesk/wp-init",
"description": "Bootstrap for a WordPress plugin",
"license": "MIT",
"type": "library",
"homepage": "https://gitlab.wpdesk.dev/wpdesk/wp-init",
"keywords": [
"wordpress",
"plugin",
"bootstrap"
],
"authors": [
{
"name": "WPDesk",
"homepage": "https://wpdesk.net"
},
{
"name": "Bartek Jaskulski",
"email": "bjaskulski@protonmail.com",
"role": "Developer"
}
],
"bin": [
"bin/wpinit"
],
"autoload": {
"psr-4": {
"WPDesk\\Init\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"WPDesk\\Init\\Tests\\": "tests"
},
"classmap": [
"vendor/wpdesk/wp-basic-requirements"
]
},
"require": {
"php": ">=7.2 | ^8",
"php-di/php-di": "^6 || ^7",
"psr/container": "~1.0.0 || ^2"
},
"require-dev": {
"brain/monkey": "^2.6",
"phpunit/phpunit": "^8 || ^9",
"symfony/filesystem": "^6.2",
"wpdesk/phpstan-rules": "^1.1",
"wpdesk/wp-basic-requirements": "^3",
"wpdesk/wp-builder": "^2.0",
"wpdesk/wp-code-sniffer": "^1.3"
},
"suggest": {
"wpdesk/wp-basic-requirements": "Enables your plugin to check an environment requirement before instantiation, e.g. PHP version or active plugins"
},
"conflict": {
"wpdesk/wp-basic-requirements": "<3, >=4"
},
"scripts": {
"test": "vendor/bin/phpunit --bootstrap tests/bootstrap.php ./tests"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
# Configuration
`wp-init` relies on declarative configuration, which encapsulates process of attaching hooks to
WordPress life cycle and provides some additional features, like filling DI container with your
services definitions.
## `hook_resources_path`
Pass a path to the file/directory with your hook actions. Configuration accepts any valid path
string, relative or absolute, either `hook_providers` or `__DIR__ . '/hook_providers/plugins_loaded.php'`
Files are mapped to hooks by name, so `woocommerce_init.php` is registered inside `woocommerce_init`
action. The exception is `index.php` file which is flushed immediately.
Example of a hook resource content:
```php
<?php
// plugins_loaded.php
return [
MyCoolTitleChanger::class,
AnotherHookAction::class,
function ( Migarator $migrator ) {
// You can even use a closure, to execute simple actions.
// Arguments are injected by DI container.
$migrator->migrate();
}
];
```
## `services`
As you add more services with increasing complexity, you will need to provide some kind of
definitions for a DI container to create objects. Pass a path to a file, which will hold such
definitions. Refer to [php-di documentation](https://php-di.org/doc/definitions.html) for more
information on such file content.
> *Warning*
>
> If you are using _shortcut_ functions from `php-di/php-di` (e.g. `DI\autowire`, `DI\create`), you
> must load them first. Add `require __DIR__ . '/vendor_prefixed/php-di/php-di/src/functions.php';`
> to your plugin file.
## `cache_path`
Plugin header data and compiled DI container is cached in a directory specified by this
setting. Defaults to `generated`.
## `requirements`
**This setting only works when `wpdesk/wp-basic-requirements` is installed.**
Enables your plugin to check an environment requirement before instantiation, e.g. PHP version or
active plugins. Refer to [wp-basic-requirements documentation](https://gitlab.wpdesk.dev/wpdesk/wp-basic-requirements)
for more information on setting structure.
## `plugin_class_name`
**This setting only works when `wpdesk/wp-builder` is installed.**
When a plugin is used in [legacy mode](legacy.md), `plugin_class_name` is used to create an instance
of main plugin class. This setting is required to enable legacy mode. Despite that,
`WPDesk\Init\Plugin\Plugin` is still accessible to your services.
# Migration from `wpdesk/wp-builder`
Projects using `wpdesk/wp-builder` (or `wpdesk/wp-plugin-flow`) may easily migrate to `wp-init`
in a few steps:
1. Install `wpdesk/wp-init`.
1.
# Prefixing `wp-init` with `php-scoper`
When developing plugins, it's worth to prefix all dependencies to avoid version collision between
different plugins loaded during runtime. For this library to enable prefixing you will need to
introduce following configuration to your `scoper.inc.php` file.
- Whitelist `vendor/php-di/php-di/src/Compiler/Template.php`
- Include `php-di` and it's dependencies in finders
> *Note*
>
> Pay attention to actual installed `php-di/php-di` version, as it's dependencies may change,
> requiring to update `scoper.inc.php` accordingly.
## Example configuration
**`php-di/php-di` up to 6.3.5**
```php
return [
'finder' => Finder::create()->in(['vendor/wpdesk/wp-init', 'vendor/php-di', 'vendor/opis/closure']),
'files-whitelist' => [
'vendor/php-di/php-di/src/Compiler/Template.php'
],
];
```
**`php-di/php-di` since 6.4.0**
```php
return [
'finder' => Finder::create()->in(['vendor/wpdesk/wp-init', 'vendor/php-di', 'vendor/laravel/serializable-closure']),
'files-whitelist' => [
'vendor/php-di/php-di/src/Compiler/Template.php'
],
];
```
<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards for WP Desk Plugin">
<!--
#############################################################################
COMMAND LINE ARGUMENTS
https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml
#############################################################################
-->
<!-- Scan all files. -->
<file>./src</file>
<!-- Only check PHP files. -->
<arg name="extensions" value="php"/>
<!-- Show progress, show the error codes for each message (source). -->
<arg value="sp"/>
<!-- Check up to 8 files simultaneously. -->
<arg name="parallel" value="8"/>
<!-- Cache outcomes for better performance. Remember to add the file to .gitignore. -->
<arg name="cache" value="./.phpcs-cache"/>
<!--
#############################################################################
USE THE WPDeskCS RULESET
#############################################################################
-->
<!-- Define plugin text domain for i18n. -->
<config name="text_domain" value="wp-init"/>
<!-- This value should be aligned with WordPress support version declared in plugin header -->
<config name="minimum_supported_wp_version" value="6.2"/>
<!-- Set value aligned with supported PHP Version for PHPCompatibilityWP check. -->
<config name="testVersion" value="7.2-"/>
<rule ref="WPDeskPlugin"/>
<!--
##############################################################################
CUSTOM RULES
##############################################################################
-->
<rule ref="Squiz.ControlStructures.InlineIfDeclaration">
<exclude name="Squiz.ControlStructures.InlineIfDeclaration.NoBrackets"/>
</rule>
</ruleset>
parameters:
level: 6
paths:
- src/
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding;
interface Binder {
public function bind( Definition $def ): void;
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Binder;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Binding\ComposableBinder;
use WPDesk\Init\Binding\Definition;
use WPDesk\Init\Binding\Definition\CallableDefinition;
class CallableBinder implements ComposableBinder {
/** @var ContainerInterface */
private $container;
public function __construct( ContainerInterface $c ) {
$this->container = $c;
}
public function can_bind( Definition $def ): bool {
return $def instanceof CallableDefinition;
}
public function bind( Definition $def ): void {
$ref = new \ReflectionFunction( $def->value() );
$parameters = [];
foreach ( $ref->getParameters() as $ref_param ) {
$parameters[] = $this->container->get( $ref_param->getType()->getName() );
}
$ref->invokeArgs( $parameters );
}
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Binder;
use WPDesk\Init\Binding\Binder;
use WPDesk\Init\Binding\Definition;
use WPDesk\Init\Binding\Definition\HookableDefinition;
final class CompositeBinder implements Binder {
/** @var Binder[] */
private $binders;
public function __construct( Binder ...$binders ) {
$this->binders = $binders;
}
public function add( Binder $binder ): void {
$this->binders[] = $binder;
}
public function bind( Definition $def ): void {
foreach ( $this->binders as $binder ) {
if ( $binder->can_bind( $def ) ) {
$binder->bind( $def );
break;
}
}
}
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Binder;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Binding\ComposableBinder;
use WPDesk\Init\Binding\Definition;
use WPDesk\Init\Binding\Definition\HookableDefinition;
class HookableBinder implements ComposableBinder {
/** @var ContainerInterface */
private $container;
public function __construct( ContainerInterface $c ) {
$this->container = $c;
}
public function can_bind( Definition $def ): bool {
return $def instanceof HookableDefinition;
}
public function bind( Definition $def ): void {
$this->container->get( $def->value() )->hooks();
}
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Binder;
use WPDesk\Init\Binding\Binder;
use WPDesk\Init\Binding\ComposableBinder;
use WPDesk\Init\Binding\Definition;
/**
* Binder decorator, specifically built for testing purposes. Can naively investigate other binders.
*/
final class ObservableBinder implements ComposableBinder {
/** @var Binder */
private $binder;
private $binds_count = 0;
public function __construct( Binder $b ) {
$this->binder = $b;
}
public function bind( Definition $def ): void {
$this->binder->bind( $def );
$this->binds_count++;
}
public function can_bind( Definition $def ): bool {
if ( $this->binder instanceof ComposableBinder ) {
return $this->binder->can_bind( $def );
}
return true;
}
public function binds_count(): int {
return $this->binds_count;
}
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Binder;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Binding\ComposableBinder;
use WPDesk\Init\Binding\StoppableBinder as Stop;
use WPDesk\Init\Binding\Definition;
use WPDesk\Init\Binding\Definition\HookableDefinition;
class StoppableBinder implements ComposableBinder {
/** @var ContainerInterface */
private $container;
/** @var Binder */
private $binder;
private $should_stop = false;
public function __construct( Binder $b, ContainerInterface $c ) {
$this->binder = $b;
$this->container = $c;
}
public function can_bind( Definition $def ): bool {
return $this->binder->can_bind( $def );
}
public function bind( Definition $def ): void {
if ( $this->should_stop === true ) {
return;
}
$this->binder->bind( $def );
if ( $this->can_be_stoppable( $def ) ) {
$binding = $this->container->get( $def->value() );
if ( $binding instanceof Stop && $binding->should_stop() ) {
$this->should_stop = true;
}
}
}
private function can_be_stoppable( Definition $def ): bool {
return is_string( $def->value() ) && class_exists( $def->value() );
}
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding;
/**
* Can be composed with other binders within {@see CompositeBinder} class.
*/
interface ComposableBinder extends Binder {
public function can_bind( Definition $def ): bool;
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding;
/**
* @template T
*/
interface Definition {
public function hook(): ?string;
/** @return T */
public function value();
}
<?php
declare(strict_types=1);
namespace WPDesk\Init\Binding\Definition;
use WPDesk\Init\Binding\Definition;
/** @implements Definition<callable> */
class CallableDefinition implements Definition {
/** @var ?string */
private $hook;
/** @var callable */
private $callable;
public function __construct(
callable $callable,
?string $hook = null,
) {
$this->callable = $callable;
$this->hook = $hook;
}
public function hook(): ?string {
return $this->hook;
}
public function value() {
return $this->callable;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment