定制 x3p0-dev/x3p0-class-registry 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

x3p0-dev/x3p0-class-registry

Composer 安装命令:

composer require x3p0-dev/x3p0-class-registry

包简介

A tiny, type-safe registry of class names for building extensible subsystems in WordPress plugins and themes.

README 文档

README

A tiny, type-safe registry of class names for building extensible subsystems in WordPress plugins and themes.

It gives you one dependable place to map a string key to a class — and it refuses to store anything a factory couldn't later build. One part of your plugin registers the classes it knows about; other code (yours or a third party's) adds, replaces, or removes entries by key, without any of them referencing each other directly.

What a class registry does

A class registry stores string key => class-string mappings. It does not create objects — it holds the names of classes so that something else (a factory) can instantiate them lazily, only when they're actually needed.

That indirection is what makes a subsystem extensible. Instead of a switch statement or a hard-coded list of new calls, you keep an open map of keys to classes. Want to add a new behavior? Register a class against a new key. Want to swap the built-in one? Register your class against the existing key. Nothing that uses the registry has to change.

It's one third of a small, familiar pattern:

Piece Responsibility
Registry Stores key => class-string and guards what goes in
Factory Looks a key up and instantiates the class (lazily)
Registrar Seeds the registry with the built-in keys on startup

This package is the registry — the storage-and-validation half. The factory and registrar are yours to write, because instantiation is where your container (or plain new) and your startup lifecycle live. On its own, a registry is a validated array of class names; paired with a factory, it's an extension point.

Why not just an array?

Because an array will happily store a typo. This registry guards every registration and fails loudly — at wire-up time, not deep inside a request — when a class can't fulfill its purpose. A stored entry is guaranteed to be:

  • The right type — a subclass of the base type the registry is built around.
  • Instantiable — not abstract, not an interface, and constructable (no private/protected constructor).

So by the time a factory pulls a class-string out, building it can't fail for either of those reasons. The guarantee lives at the boundary instead of in every call site.

Quick start

Extend the abstract Registry once per subsystem and name the base type every entry must satisfy by returning it from contract():

use X3P0\ClassRegistry\Registry;

// The contract: every registered channel must be a subclass of this.
abstract class Channel
{
	abstract public function send(string $message): bool;
}

/**
 * @extends Registry<Channel>
 */
final class ChannelRegistry extends Registry
{
	protected function contract(): string
	{
		return Channel::class;
	}
}

Now register classes by key, and look them up when you need to build one:

final class EmailChannel extends Channel
{
	public function send(string $message): bool
	{
		return wp_mail(get_option('admin_email'), 'Notification', $message);
	}
}

$channels = new ChannelRegistry();
$channels->register('email', EmailChannel::class);

// A factory resolves the key and builds the class — lazily, only now.
$className = $channels->get('email');   // 'EmailChannel' — or null if nothing is registered
$channel   = new $className();          // your factory/container does this step

You can also seed the registry at construction time:

$channels = new ChannelRegistry([
	'email' => EmailChannel::class,
	'sms'   => SmsChannel::class,
]);

Pairing it with a factory

The registry stores and validates; a factory turns a key into an instance. This package intentionally ships no factory, because construction is where your world lives — a DI container, constructor arguments, a startup lifecycle — and a generic base would either couple this package to your container or bury that work in ceremony. It's a handful of lines you're better off owning:

final class ChannelFactory
{
	public function __construct(private readonly ChannelRegistry $registry) {}

	public function make(string $key): ?Channel
	{
		$className = $this->registry->get($key);

		return $className ? new $className() : null;
	}
}

Swap the new $className() for a container call (e.g. $container->make($className)) when your classes have their own dependencies. Because the registry already guaranteed every entry is the right type and instantiable, the factory never has to re-check either — it just builds. Keep the factory typed to your base class (?Channel here) so call sites stay type-safe.

Extending and overriding

Because everything is keyed, a third party can reshape a subsystem without touching its source:

// Add a brand-new type.
$channels->register('slack', SlackChannel::class);

// Replace a built-in with your own (same key, different class).
$channels->register('email', QueuedEmailChannel::class);

// Remove one entirely.
$channels->unregister('sms');

A common convention is for a subsystem to seed its built-in keys only if they aren't already registered, so extensions that ran earlier win:

if (! $channels->isRegistered('email')) {
	$channels->register('email', EmailChannel::class);
}

When registration fails

register() throws a RegistrationException (a LogicException) the moment you hand it something unusable — a programmer error meant to surface in development, not to be caught at runtime:

use X3P0\ClassRegistry\RegistrationException;

abstract class AsyncChannel extends Channel {}

$channels->register('bad', \stdClass::class);      // not a subclass of Channel
$channels->register('nope', AsyncChannel::class);  // a subclass, but abstract

Both throw with a message naming the offending class and why it was rejected.

API

The registry is iterable (key => class-string) and countable, so it plays well with foreach, count(), and iterator functions.

Method Returns Description
register(string, string) void Map a key to a class; throws if invalid
unregister(string) void Remove a key's mapping, if any
isRegistered(string) bool Whether a class is registered under a key
get(string) ?class-string The class for a key, or null
all() array Every key => class-string mapping
count() int How many classes are registered
getIterator() ArrayIterator Iterate key => class-string

The single extension point is the abstract contract() method, which returns the base class-string every registered class must be a subclass of.

Requirements

  • PHP 8.1+
  • WordPress (exception messages use esc_html() and __())

License

GPL-2.0-or-later. Copyright © Justin Tadlock.

统计信息

  • 总下载量: 1
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: GPL-2.0-or-later
  • 更新时间: 2026-07-08

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固