diff --git a/app/Config/Options.php b/app/Config/Options.php new file mode 100644 index 00000000..ef08eb84 --- /dev/null +++ b/app/Config/Options.php @@ -0,0 +1,37 @@ +pf_options_container_loader(); + } + + private function pf_options_container_loader(): void + { + Container::bind( + OptionStorageInterface::class . ':wp', + fn($c) => new WPOptionStorage() + ); + Container::bind( + OptionStorageInterface::class . ':custom', + fn($c) => new CustomTableOption() + ); + Container::bind( + OptionManager::class, + fn($c) => new OptionManager($c) + ); + Container::bind( + OptionStorageInterface::class, + fn($c) => $c->get(OptionStorageInterface::class . ':wp') + ); + } +} diff --git a/app/Config/index.php b/app/Config/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/API/index.php b/app/Core/API/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/Migrations/index.php b/app/Core/DB/Migrations/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/Models/index.php b/app/Core/DB/Models/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/Pagination/index.php b/app/Core/DB/Pagination/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/Seeders/index.php b/app/Core/DB/Seeders/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/Utils/index.php b/app/Core/DB/Utils/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/WP/index.php b/app/Core/DB/WP/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/DB/index.php b/app/Core/DB/index.php deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Core/Helpers/BootstrapHelper.php b/app/Core/Helpers/BootstrapHelper.php index 9639eafe..52eda21a 100644 --- a/app/Core/Helpers/BootstrapHelper.php +++ b/app/Core/Helpers/BootstrapHelper.php @@ -109,6 +109,9 @@ private function load_features(): void { (new \PluginFrame\Config\Config())->priority_load_first(); + // Initialize Options Container and Storage Services + new \PluginFrame\Config\Options(); + // Initialize providers and configuration. new \PluginFrame\Config\Providers(); diff --git a/app/Core/Helpers/OptionsHelper.php b/app/Core/Helpers/OptionsHelper.php new file mode 100644 index 00000000..d681d1eb --- /dev/null +++ b/app/Core/Helpers/OptionsHelper.php @@ -0,0 +1,22 @@ + */ + private array $definitions = []; + + /** @var array */ + private array $instances = []; + + /** Private to enforce singleton */ + private function __construct() {} + + /** + * Retrieve the singleton instance. + */ + public static function getInstance(): Container + { + return self::$instance ??= new Container(); + } + + /** + * Bind a service ID to a resolver callable. + * + * @param string $id Service identifier. + * @param callable $resolver function(ContainerInterface): mixed + */ + public static function bind(string $id, callable $resolver): void + { + self::getInstance()->definitions[$id] = $resolver; + } + + /** + * Resolve a service by its ID. + * + * @param string $id Identifier of the entry to look for. + * @return mixed The entry. + * @throws NotFoundExceptionInterface No entry was found for this identifier. + */ + public function get(string $id): mixed + { + if (isset($this->instances[$id])) { + return $this->instances[$id]; + } + + if (! isset($this->definitions[$id])) { + throw new class("Service {$id} not found") + extends \Exception + implements NotFoundExceptionInterface {}; + } + + $resolver = $this->definitions[$id]; + $service = $resolver($this); + $this->instances[$id] = $service; + return $service; + } + + /** + * Static shortcut to resolve a service. + * + * @param string $id + * @return mixed + */ + public static function resolve(string $id): mixed + { + return self::getInstance()->get($id); + } + + /** + * Does this container have a resolver bound for $id? + * + * @param string $id Identifier to check. + * @return bool + */ + public function has(string $id): bool + { + return isset($this->definitions[$id]); + } +} diff --git a/app/Core/Services/Options/CompositeOptionStorage.php b/app/Core/Services/Options/CompositeOptionStorage.php new file mode 100644 index 00000000..a5f8cdba --- /dev/null +++ b/app/Core/Services/Options/CompositeOptionStorage.php @@ -0,0 +1,57 @@ +storages = $storages; + } + + public function register(string $key, $default = null, array $args = []): void { + foreach ($this->storages as $storage) { + $storage->register($key, $default, $args); + } + } + + public function get(string $key, $default = null) { + foreach ($this->storages as $storage) { + $value = $storage->get($key, null); + if (null !== $value) { + return $value; + } + } + return $default; + } + + public function update(string $key, $value): bool { + $ok = true; + foreach ($this->storages as $storage) { + $ok = $ok && $storage->update($key, $value); + } + return $ok; + } + + public function delete(string $key): bool { + $ok = true; + foreach ($this->storages as $storage) { + $ok = $ok && $storage->delete($key); + } + return $ok; + } + + public function all(): array { + $all = []; + foreach ($this->storages as $storage) { + $all = array_merge($all, $storage->all()); + } + return $all; + } +} diff --git a/app/Core/Services/Options/CustomTableOption.php b/app/Core/Services/Options/CustomTableOption.php new file mode 100644 index 00000000..4b9a71b4 --- /dev/null +++ b/app/Core/Services/Options/CustomTableOption.php @@ -0,0 +1,66 @@ +db = $wpdb; + $this->table = $wpdb->prefix . 'plugin_options'; + } + + public function register(string $key, $default = null, array $args = []): void + { + $exists = $this->db->get_var( + $this->db->prepare("SELECT COUNT(*) FROM {$this->table} WHERE option_key=%s", $key) + ); + if (!$exists) { + $this->db->insert( + $this->table, + ['option_key' => $key, 'option_value' => maybe_serialize($default)], + ['%s','%s'] + ); + } + } + + public function get(string $key, $default = null) + { + $row = $this->db->get_row( + $this->db->prepare("SELECT option_value FROM {$this->table} WHERE option_key=%s", $key), + ARRAY_A + ); + return $row ? maybe_unserialize($row['option_value']) : $default; + } + + public function update(string $key, $value): bool + { + return (bool) $this->db->update( + $this->table, + ['option_value' => maybe_serialize($value)], + ['option_key' => $key], + ['%s'], + ['%s'] + ); + } + + public function delete(string $key): bool + { + return (bool) $this->db->delete($this->table, ['option_key' => $key], ['%s']); + } + + public function all(): array + { + $rows = $this->db->get_results("SELECT * FROM {$this->table}", ARRAY_A); + return array_column($rows, 'option_value', 'option_key'); + } +} diff --git a/app/Core/Services/Options/Interfaces/OptionInterface.php b/app/Core/Services/Options/Interfaces/OptionInterface.php new file mode 100644 index 00000000..63d3dd6a --- /dev/null +++ b/app/Core/Services/Options/Interfaces/OptionInterface.php @@ -0,0 +1,49 @@ +c = $container; + } + + public function register(string $key, $default = null, array $args = []): void { + $drivers = $args['storage'] ?? ['wp']; + $instances = array_map(fn($alias) => + $this->c->get(OptionStorageInterface::class . ':' . $alias), + $drivers + ); + $storage = count($instances) > 1 + ? new CompositeOptionStorage($instances) + : $instances[0]; + + $storage->register($key, $default, $args); + $this->registered[$key] = ['default'=>$default,'args'=>$args]; + } + + public function get(string $key, $default = null) { + // delegate to the selected storage(s) + // ... + } + + public function update(string $key, $value): bool { + // delegate... + } + + public function delete(string $key): bool { + // delegate... + } + + public function all(): array { + return $this->registered; + } +} diff --git a/app/Core/Services/Options/WPOptionStorage.php b/app/Core/Services/Options/WPOptionStorage.php new file mode 100644 index 00000000..e87bca9e --- /dev/null +++ b/app/Core/Services/Options/WPOptionStorage.php @@ -0,0 +1,39 @@ +