Skip to content
Snippets Groups Projects
Verified Commit 7d8fa3ad authored by Bartek Jaskulski's avatar Bartek Jaskulski
Browse files

feat: moving toward actual implementation


Add more flexibility for `PluginInit` with interfaces. Separate hook
attaching from initialization process to exchange hook registration
methods in future.

Start caching data from plugin and container in over-simplistic way.

Provide an extension point for DI container with bundles.

Signed-off-by: default avatarBart Jaskulski <bjaskulski@protonmail.com>
parent 59a2c72a
No related branches found
No related tags found
3 merge requests!3improve into wp-hook and some additional unfinished things,!21.x,!1Draft: Basic implementation of plugin initializer
Showing
with 208 additions and 260 deletions
.DS_Store
vendor
composer.lock
.phpunit.result.cache
\ No newline at end of file
......@@ -6,7 +6,9 @@
"type": "library",
"homepage": "https://gitlab.wpdesk.dev/wpdesk/wp-init",
"keywords": [
"wordpress"
"wordpress",
"plugin",
"bootstrap"
],
"authors": [
{
......
<?php
declare( strict_types=1 );
namespace WPDesk\Init\Bundle;
use WPDesk\Init\Configuration\ReadableConfig;
use WPDesk\Init\DependencyInjection\ContainerBuilder;
interface Bundle {
public function build( ContainerBuilder $container_builder, ReadableConfig $config ): void;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\Bundle;
use WPDesk\Init\HookProvider\ConcreteProviders\ActivationDate;
use WPDesk\Init\HookProvider\ConcreteProviders\I18n;
use WPDesk\Init\HookProvider\ConcreteProviders\WooCommerceHPOSCompatibility;
class CommonBundle {
public static function subscribers(): iterable {
return [
ActivationDate::class,
I18n::class,
WooCommerceHPOSCompatibility::class
];
}
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\Bundle;
use WPDesk\Migrations\Migrator;
class MigrationSubscriber implements \WPDesk\WPHook\HookSubscriber\HookSubscriber {
/** @var Migrator */
private $migrator;
public function __construct(Migrator $migrator) {
$this->migrator = $migrator;
}
public static function register(): iterable {
yield 'plugins_loaded' => 'migrate';
}
public function migrate(): void {
$this->migrator->migrate();
}
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\Bundle;
use WPDesk\Init\Configuration\ReadableConfig;
use WPDesk\Init\Plugin;
use WPDesk\Migrations\Migrator;
use WPDesk\Migrations\WpdbMigrator;
class MigrationsBundle {
public function build( ContainerBuilder $builder, ReadableConfig $config ): void {
$builder->addDefinitions( [
Migrator::class => function ( Plugin $plugin ) use ( $config ) {
return WpdbMigrator::from_directories(
$config->get( 'migrations', [] ),
$config->get( 'migrations_table_name', $plugin->get_slug() . 'migrations' )
);
}
] );
}
public static function subscribers(): iterable {
return [
MigrationSubscriber::class
];
}
}
\ No newline at end of file
......@@ -3,6 +3,9 @@ declare( strict_types=1 );
namespace WPDesk\Init\Configuration;
/**
* @implements \ArrayAccess<string, mixed>
*/
class Configuration implements ReadableConfig, \ArrayAccess {
/** @var array<string, mixed> */
......@@ -34,7 +37,7 @@ class Configuration implements ReadableConfig, \ArrayAccess {
unset( $this->config[ $key ] );
}
public function offsetExists( $offset ) {
public function offsetExists( $offset ): bool {
return $this->has( $offset );
}
......@@ -44,6 +47,10 @@ class Configuration implements ReadableConfig, \ArrayAccess {
}
public function offsetSet( $offset, $value ): void {
if ( $offset === null ) {
throw new \InvalidArgumentException( 'Cannot set value without key.' );
}
$this->set( $offset, $value );
}
......
<?php
declare( strict_types=1 );
namespace WPDesk\Init\DependencyInjection;
use DI\Container;
use DI\ContainerBuilder as DiBuilder;
use DI\Definition\Source\DefinitionSource;
final class ContainerBuilder {
/** @var DiBuilder */
private $original_builder;
public function __construct( DiBuilder $original_builder ) {
$this->original_builder = $original_builder;
}
/**
* Add definitions to the container.
*
* @param string|array|DefinitionSource ...$definitions
* Can be an array of definitions, the name of a file containing definitions or
* a DefinitionSource object.
*
* @return $this
*/
public function add_definitions( ...$definitions ): self {
$this->original_builder->addDefinitions( $definitions );
return $this;
}
public function build(): Container {
return $this->original_builder->build();
}
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\Dumper;
class PhpFileDumper {
public function dump( array $config, string $filename ): void {
$directory = dirname( $filename );
$this->createCompilationDirectory( $directory );
$content = '<?php' . PHP_EOL . PHP_EOL;
$content .= 'declare(strict_types=1);' . PHP_EOL . PHP_EOL;
$content .= 'return ' . var_export( $config, true ) . ';' . PHP_EOL;
$this->writeFileAtomic( $filename, $content );
}
private function createCompilationDirectory( string $directory ): void {
if ( ! is_dir( $directory ) && ! @mkdir( $directory, 0777, true ) && ! is_dir( $directory ) ) {
throw new \InvalidArgumentException( sprintf( 'Compilation directory does not exist and cannot be created: %s.', $directory ) );
}
if ( ! is_writable( $directory ) ) {
throw new \InvalidArgumentException( sprintf( 'Compilation directory is not writable: %s.', $directory ) );
}
}
private function writeFileAtomic( string $fileName, string $content ): void {
$tmpFile = @tempnam( dirname( $fileName ), 'swap-compile' );
if ( $tmpFile === false ) {
throw new \InvalidArgumentException(
sprintf( 'Error while creating temporary file in %s', dirname( $fileName ) )
);
}
@chmod( $tmpFile, 0666 );
$written = file_put_contents( $tmpFile, $content );
if ( $written === false ) {
@unlink( $tmpFile );
throw new \InvalidArgumentException( sprintf( 'Error while writing to %s', $tmpFile ) );
}
@chmod( $tmpFile, 0666 );
$renamed = @rename( $tmpFile, $fileName );
if ( ! $renamed ) {
@unlink( $tmpFile );
throw new \InvalidArgumentException( sprintf( 'Error while renaming %s to %s', $tmpFile, $fileName ) );
}
}
}
\ No newline at end of file
<?php
namespace WPDesk\Init;
interface HeaderParser {
public function parse( string $plugin_file ): array;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookDriver;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Configuration\ReadableConfig;
class CallbackDriver implements HookDriver {
public function register_hooks( ReadableConfig $config, array $bundles, ContainerInterface $container ): void {
$hooks = $config->get( 'hooks', [] );
foreach ( $hooks as $hook => $callback_definition ) {
[ $callback, $priority ] = \array_replace(
[ null, 10 ],
(array) $callback_definition
);
add_filter( $hook, $callback, $priority );
}
}
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookDriver;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Configuration\ReadableConfig;
/**
* Hook can be attached to WordPress in different ways, and this interface decouples possible methods
* from our initialization system, to make it more flexible.
*
* Even though hook registration is sort of the main purpose of this library, it's better to encapsulate
* hook registration in a separate class, so that it can be easily replaced with a different
* implementation and composition.
*/
interface HookDriver {
/**
* @param ReadableConfig $config
* @param array $bundles
* @param ContainerInterface $container
*
* @return void
*/
public function register_hooks( ReadableConfig $config, array $bundles, ContainerInterface $container ): void;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookDriver;
interface HookableBundle extends \WPDesk\Init\Bundle\Bundle {
public static function hookable(): array;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookDriver;
use Psr\Container\ContainerInterface;
use WPDesk\Init\Configuration\ReadableConfig;
class HookableDriver implements HookDriver {
public function register_hooks( ReadableConfig $config, array $bundles, ContainerInterface $container ): void {
$subscribers = $config->get( 'hookables', [] );
foreach ( $bundles as $bundle ) {
$subscribers = \array_merge( $subscribers, $bundle::hookable() );
}
foreach ( $subscribers as $subscriber ) {
$container->get( $subscriber )->hooks();
}
}
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use WPDesk\Init\Plugin;
use WPDesk\WPHook\HookSubscriber\HookSubscriber;
class ActivationDate implements HookSubscriber {
/** @var Plugin */
private $plugin;
public function __construct( Plugin $plugin ) {
$this->plugin = $plugin;
}
public static function register(): iterable {
yield 'activated_plugin' => 'save_activation_date';
}
public function save_activation_date( $plugin_file, $network_wide = false ): void {
if ( ! $network_wide && $this->plugin->get_basename() === $plugin_file ) {
$option_name = 'plugin_activation_' . $plugin_file;
$activation_date = get_option( $option_name, '' );
if ( '' === $activation_date ) {
$activation_date = current_time( 'mysql' );
update_option( $option_name, $activation_date );
}
}
}
}
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use WPDesk\Init\HookProvider\HooksProvider;
use WPDesk\Init\Plugin;
abstract class ActivationHook implements HooksProvider {
/** @var Plugin */
private $plugin;
public function __construct( Plugin $plugin ) {
$this->plugin = $plugin;
}
public function register_hooks(): void {
register_activation_hook(
$this->plugin->get_basename(),
[$this, 'activate']
);
}
abstract public function activate(): void;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use Psr\Container\ContainerInterface;
use WPDesk\Init\HookProvider\HooksProvider;
use WPDesk\Init\Plugin;
class ContainerHookProvider implements HooksProvider {
/** @var Plugin */
private $plugin;
/** @var ContainerInterface */
private $container;
public function __construct( Plugin $plugin, ContainerInterface $container ) {
$this->plugin = $plugin;
$this->container = $container;
}
public function register_hooks(): void {
add_action(
'plugins_loaded',
function () {
if ( $this->container->has( 'hooks' ) ) {
$this->plugin->register_hooks( ...$this->container->get( 'hooks' ) );
}
}
);
}
}
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use WPDesk\Init\HookProvider\HooksProvider;
use WPDesk\Init\Plugin;
abstract class DeactivationHook implements HooksProvider {
/** @var Plugin */
private $plugin;
public function __construct( Plugin $plugin ) {
$this->plugin = $plugin;
}
public function register_hooks(): void {
register_deactivation_hook(
$this->plugin->get_basename(),
[$this, 'deactivate']
);
}
abstract public function deactivate(): void;
}
\ No newline at end of file
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use WPDesk\Init\Plugin;
use WPDesk\WPHook\HookSubscriber\HookSubscriber;
class I18n implements HookSubscriber {
/** @var Plugin */
private $plugin;
public function __construct( Plugin $plugin ) {
$this->plugin = $plugin;
}
public static function register(): iterable {
yield 'plugins_loaded' => 'load_textdomain';
}
public function load_textdomain(): void {
$plugin_rel_path = dirname( $this->plugin->get_basename() ) . '/lang';
\load_plugin_textdomain( $this->plugin->get_slug(), false, $plugin_rel_path );
}
}
<?php
declare( strict_types=1 );
namespace WPDesk\Init\HookProvider\ConcreteProviders;
use WPDesk\Init\HookProvider\HooksProvider;
use WPDesk\Init\Plugin;
use WPDesk\WPHook\HookSubscriber\Conditional;
use WPDesk\WPHook\HookSubscriber\Deferred;
use WPDesk\WPHook\HookSubscriber\HookSubscriber;
class WooCommerceHPOSCompatibility implements HookSubscriber, Deferred, Conditional {
/** @var Plugin */
private $plugin;
public function __construct( Plugin $plugin ) {
$this->plugin = $plugin;
}
public static function register(): iterable {
yield 'before_woocommerce_init' => 'register_hooks';
}
public static function register_after() {
return 'woocommerce_init';
}
public function is_needed(): bool {
return class_exists( 'WooCommerce' );
}
public function register_hooks(): void {
$features_util_class = '\\' . 'Automattic' . '\\' . 'WooCommerce' . '\\' . 'Utilities' . '\\' . 'FeaturesUtil';
if ( class_exists( $features_util_class ) ) {
$features_util_class::declare_compatibility( 'custom_order_tables', $this->plugin->get_basename(), true );
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment