Skip to content

Commit 25a643e

Browse files
committed
feat(reflect): add libreflect incubator
1 parent 2d3671a commit 25a643e

13 files changed

Lines changed: 663 additions & 0 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ elprobe run <package>
141141
main-thread callback registry, generic `AsyncTask` wrapper and dispatcher.
142142
This validates async rules for DB, HTTP, Agent, scheduler and package/update
143143
jobs before creating a standalone `LibAsync` repository.
144+
- Added the OP-31 `LibReflect` incubator inside the EasyLibrary core. The new
145+
experimental `imperazim\library\reflect\*` classes provide explicit
146+
class/interface/trait inspection, class contract validation, attribute access
147+
and safe instantiation helpers without global scanning or hidden runtime
148+
auto-discovery.
144149

145150
### Package manager
146151

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,22 @@ and package/update work:
867867
See [`docs/libasync-incubator.md`](docs/libasync-incubator.md) for examples,
868868
rules and graduation criteria.
869869

870+
## LibReflect incubator
871+
872+
EasyLibrary 3.0-dev also includes an experimental reflection incubator under
873+
`imperazim\library\reflect`. It is not an official `LibReflect` package yet.
874+
875+
The incubator validates a small, explicit reflection shape:
876+
877+
- `ClassInspector` inspects one class/interface/trait name at a time.
878+
- `ClassInspection` exposes metadata, attributes and controlled instantiation.
879+
- `ClassContract` validates inheritance, interfaces and instantiability.
880+
- `ClassContractResult` carries validation errors.
881+
- `ClassFactory` instantiates only classes that pass the contract.
882+
883+
It intentionally avoids global class scans, hidden auto-discovery and runtime
884+
magic. See [`docs/libreflect-incubator.md`](docs/libreflect-incubator.md).
885+
870886
## EasyLibrary Agent Bridge
871887

872888
The **EasyLibrary Agent Bridge** is an optional network bridge between PocketMine-MP plugins and the external [`EasyLibraryAgent`](https://github.com/ImperaZim/EasyLibraryAgent) Go service.
@@ -985,6 +1001,7 @@ Use compute for expensive/global tasks. Keep small local or same-tick logic insi
9851001
- [`docs/module-boundary.md`](docs/module-boundary.md)
9861002
- [`docs/module-smoke-matrix.md`](docs/module-smoke-matrix.md)
9871003
- [`docs/libasync-incubator.md`](docs/libasync-incubator.md)
1004+
- [`docs/libreflect-incubator.md`](docs/libreflect-incubator.md)
9881005
- [`docs/api.md`](docs/api.md)
9891006
- [`docs/public-api.md`](docs/public-api.md)
9901007
- [`docs/package-contract.md`](docs/package-contract.md)

changelogs/3.0.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,65 @@ Compatibility impact:
527527
- Future `LibAsync` extraction must still prove at least two real libs can use
528528
the shared contract safely.
529529

530+
### LibReflect incubator
531+
532+
EasyLibrary now includes a small `LibReflect` incubator under
533+
`imperazim\library\reflect`.
534+
535+
What changed:
536+
537+
- Added `ClassInspector` for inspecting one explicit class/interface/trait name.
538+
- Added `ClassInspection` as a read-only snapshot of class metadata, attributes
539+
and controlled instantiation.
540+
- Added `ClassContract` for fluent inheritance/interface/instantiability
541+
validation.
542+
- Added `ClassContractResult` for structured validation errors and assertion.
543+
- Added `ClassFactory` for instantiating only classes that pass the contract.
544+
- Added `docs/libreflect-incubator.md` with examples, rules, candidate uses and
545+
graduation criteria.
546+
- Added lightweight tests for class metadata, attributes, valid/invalid
547+
contracts and rejection of abstract class instantiation.
548+
549+
Why it changed:
550+
551+
EasyLibrary already has legitimate reflection needs in module class loading,
552+
package entry loading, provider diagnostics and plugin migration/debug
553+
contexts. The risk is turning reflection into hidden runtime magic. This
554+
incubator defines a deliberately small, explicit shape before any standalone
555+
`LibReflect` package exists.
556+
557+
How to use the incubator shape:
558+
559+
```php
560+
use imperazim\library\reflect\ClassContract;
561+
use imperazim\library\reflect\ClassFactory;
562+
use imperazim\module\BaseModule;
563+
564+
$contract = ClassContract::for($className)
565+
->mustExtend(BaseModule::class)
566+
->mustBeInstantiable()
567+
->validate();
568+
569+
if (!$contract->isOk()) {
570+
foreach ($contract->getErrors() as $error) {
571+
$logger->warning($error);
572+
}
573+
return;
574+
}
575+
576+
$module = ClassFactory::newInstance($className, [$plugin, $config]);
577+
```
578+
579+
Compatibility impact:
580+
581+
- This is additive and marked `@experimental`.
582+
- No official standalone lib API changed.
583+
- No `LibReflect` repo or package has been created yet.
584+
- The incubator intentionally does not scan all declared classes or arbitrary
585+
source trees.
586+
- A future extraction must prove at least two real call sites become smaller or
587+
safer without making runtime behavior invisible.
588+
530589
### Compatibility policy
531590

532591
EasyLibrary now has a dedicated compatibility policy at

docs/compatibility.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,21 @@ standalone package, or be rejected before stable `3.0.0` if real DB/HTTP/Agent
227227
or scheduler migrations do not justify it.
228228

229229
See [`libasync-incubator.md`](libasync-incubator.md).
230+
231+
## LibReflect Incubator
232+
233+
The `imperazim\library\reflect\*` namespace is experimental during 3.0-dev.
234+
235+
It exists to test whether a future official `LibReflect` package should share:
236+
237+
- explicit class inspection;
238+
- class contract validation;
239+
- attribute access;
240+
- controlled instantiation helpers.
241+
242+
This is not a stable public promise yet. The incubator must not be treated as a
243+
global class scanner, dependency injection container or automatic command/module
244+
loader. It may change, move to a standalone package, or be rejected before
245+
stable `3.0.0` if real call sites do not justify it.
246+
247+
See [`libreflect-incubator.md`](libreflect-incubator.md).

docs/getting-started.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ composer run quality
7777
- [`module-boundary.md`](module-boundary.md) for the current module runtime and future `LibModule` boundary plan.
7878
- [`module-smoke-matrix.md`](module-smoke-matrix.md) for module runtime validation.
7979
- [`libasync-incubator.md`](libasync-incubator.md) for the experimental async worker/result shape.
80+
- [`libreflect-incubator.md`](libreflect-incubator.md) for the experimental reflection/class-contract shape.
8081

8182
## LibPlaceholder package features
8283

docs/libreflect-incubator.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# LibReflect Incubator
2+
3+
`LibReflect` is not an official standalone package yet.
4+
5+
EasyLibrary 3.0-dev now has a small internal incubator under:
6+
7+
```text
8+
imperazim\library\reflect
9+
```
10+
11+
The goal is to validate whether a future `LibReflect` should exist without
12+
turning reflection into hidden framework magic.
13+
14+
## Why This Exists
15+
16+
Several parts of the ecosystem already need cautious reflection:
17+
18+
- package entry classes need existence and constructor checks;
19+
- module loaders need to validate module classes;
20+
- provider diagnostics need to identify class file ownership;
21+
- plugin migration tools may need controlled class metadata;
22+
- optional attributes can reduce boilerplate when used explicitly.
23+
24+
Those are legitimate cases. The risky part is uncontrolled auto-discovery,
25+
global class scans and silent instantiation. The incubator intentionally avoids
26+
those patterns.
27+
28+
## Current Experimental Pieces
29+
30+
| Class | Role |
31+
|---|---|
32+
| `ClassInspector` | Entry point for inspecting one class/interface/trait name. |
33+
| `ClassInspection` | Read-only snapshot of class metadata, attributes and safe instantiation. |
34+
| `ClassContract` | Fluent validation for inheritance, interfaces and instantiability. |
35+
| `ClassContractResult` | Validation result with errors and structured output. |
36+
| `ClassFactory` | Small helper for instantiating only validated, instantiable classes. |
37+
38+
All of these are `@experimental`.
39+
40+
## Example
41+
42+
```php
43+
use imperazim\library\reflect\ClassContract;
44+
use imperazim\library\reflect\ClassFactory;
45+
use imperazim\module\BaseModule;
46+
47+
$contract = ClassContract::for($className)
48+
->mustExtend(BaseModule::class)
49+
->mustBeInstantiable()
50+
->validate();
51+
52+
if (!$contract->isOk()) {
53+
foreach ($contract->getErrors() as $error) {
54+
$logger->warning($error);
55+
}
56+
return;
57+
}
58+
59+
$module = ClassFactory::newInstance($className, [$plugin, $config]);
60+
```
61+
62+
## Attribute Example
63+
64+
```php
65+
use Attribute;
66+
use imperazim\library\reflect\ClassInspector;
67+
68+
#[Attribute(Attribute::TARGET_CLASS)]
69+
final class FeatureInfo {
70+
public function __construct(public string $id) {}
71+
}
72+
73+
#[FeatureInfo('economy')]
74+
final class EconomyFeature {}
75+
76+
$inspection = ClassInspector::inspect(EconomyFeature::class);
77+
$info = $inspection->newAttributeInstances(FeatureInfo::class)[0] ?? null;
78+
```
79+
80+
## Rules
81+
82+
- Inspect explicit class names only.
83+
- Do not scan all declared classes by default.
84+
- Do not recursively scan arbitrary source directories in runtime code.
85+
- Do not instantiate classes unless a contract has passed.
86+
- Do not hide runtime behavior behind attributes without a visible registration
87+
path.
88+
- Prefer normal `use` imports and explicit PHP code when reflection does not
89+
remove real duplication.
90+
91+
## Candidate Uses
92+
93+
| Area | Candidate use |
94+
|---|---|
95+
| Module loader | Replace ad-hoc `ReflectionClass` checks for module class validation. |
96+
| Package entry loader | Standardize zero-argument or host-argument entry instantiation checks. |
97+
| Provider diagnostics | Standardize file/class metadata reporting. |
98+
| Command tooling | Optional explicit attributes for command metadata if it reduces boilerplate. |
99+
| Migration/debug docs | Safer class metadata output for diagnostics and reports. |
100+
101+
## What This Does Not Promise
102+
103+
This incubator is not:
104+
105+
- a dependency injection container;
106+
- a global class scanner;
107+
- an annotation framework;
108+
- an automatic command/module loader;
109+
- a replacement for explicit package/module manifests;
110+
- a stable public API yet.
111+
112+
## Graduation Criteria
113+
114+
Only create `LibReflect` as an official repository if the incubator proves all
115+
of these:
116+
117+
- at least two real EasyLibrary/libs call sites become smaller or safer;
118+
- no runtime path depends on scanning all classes;
119+
- docs define which metadata can be used in production;
120+
- tests cover missing classes, abstract classes, inheritance, interfaces,
121+
attributes and instantiation;
122+
- migration docs explain when not to use reflection;
123+
- release/package assets follow the same official lib standard as
124+
`LibCommons`, `LibHttp` and `LibAssets`.
125+
126+
Until then, this stays inside EasyLibrary as a 3.0-dev experiment.

docs/public-api.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,26 @@ Current experimental classes:
120120
See [`libasync-incubator.md`](libasync-incubator.md) for the rules and
121121
graduation criteria.
122122

123+
### LibReflect Incubator
124+
125+
The `imperazim\library\reflect\*` namespace is an `@experimental` incubator for
126+
a possible future `LibReflect` package. It exists to test explicit class
127+
inspection, class-contract validation, attribute access and safe instantiation
128+
without introducing global scanners or hidden runtime magic.
129+
130+
Current experimental classes:
131+
132+
| Class | Status | Notes |
133+
|---|---|---|
134+
| `imperazim\library\reflect\ClassInspector` | `@experimental` | Entry point for inspecting one explicit class/interface/trait name. |
135+
| `imperazim\library\reflect\ClassInspection` | `@experimental` | Read-only metadata, attributes and instantiation wrapper. |
136+
| `imperazim\library\reflect\ClassContract` | `@experimental` | Fluent validation for inheritance, interfaces and instantiability. |
137+
| `imperazim\library\reflect\ClassContractResult` | `@experimental` | Structured validation result and assertion helper. |
138+
| `imperazim\library\reflect\ClassFactory` | `@experimental` | Instantiates only validated, instantiable classes. |
139+
140+
See [`libreflect-incubator.md`](libreflect-incubator.md) for the rules and
141+
graduation criteria.
142+
123143
## Internal Implementation Surfaces
124144

125145
The following namespaces are implementation details of EasyLibrary. They are
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace imperazim\library\reflect;
6+
7+
/**
8+
* Fluent class validation contract for the LibReflect incubator.
9+
*
10+
* @experimental 3.0-dev LibReflect incubator helper.
11+
*/
12+
final class ClassContract {
13+
14+
/** @var string[] */
15+
private array $parents = [];
16+
17+
/** @var string[] */
18+
private array $interfaces = [];
19+
20+
private bool $mustBeInstantiable = false;
21+
22+
private bool $autoload = true;
23+
24+
private function __construct(private string $className) {}
25+
26+
public static function for(string $className): self {
27+
return new self($className);
28+
}
29+
30+
public function autoload(bool $autoload): self {
31+
$clone = clone $this;
32+
$clone->autoload = $autoload;
33+
return $clone;
34+
}
35+
36+
public function mustExtend(string $parent): self {
37+
$clone = clone $this;
38+
$clone->parents[] = $parent;
39+
return $clone;
40+
}
41+
42+
public function mustImplement(string $interface): self {
43+
$clone = clone $this;
44+
$clone->interfaces[] = $interface;
45+
return $clone;
46+
}
47+
48+
public function mustBeInstantiable(bool $required = true): self {
49+
$clone = clone $this;
50+
$clone->mustBeInstantiable = $required;
51+
return $clone;
52+
}
53+
54+
public function validate(): ClassContractResult {
55+
$inspection = ClassInspector::inspect($this->className, $this->autoload);
56+
$errors = $inspection->getErrors();
57+
58+
if (!$inspection->exists()) {
59+
return new ClassContractResult($inspection, $errors);
60+
}
61+
62+
foreach ($this->parents as $parent) {
63+
if (!$inspection->isSubclassOf($parent)) {
64+
$errors[] = "{$inspection->getName()} must extend $parent.";
65+
}
66+
}
67+
68+
foreach ($this->interfaces as $interface) {
69+
if (!$inspection->implementsInterface($interface)) {
70+
$errors[] = "{$inspection->getName()} must implement $interface.";
71+
}
72+
}
73+
74+
if ($this->mustBeInstantiable && !$inspection->isInstantiable()) {
75+
$errors[] = "{$inspection->getName()} must be instantiable.";
76+
}
77+
78+
return new ClassContractResult($inspection, $errors);
79+
}
80+
}

0 commit comments

Comments
 (0)