spoova / ghost
A GhostProxy class for anonymous objects.
README
Ghost is a small PHP utility for creating dynamic, proxy-backed objects without needing a concrete class upfront. The package is centered around four components:
- GhostFunction: defines dynamic methods and properties using closures and simple array definitions.
- GhostDraft: a lightweight bridge that exposes the proxy id and the underlying GhostFunction payload.
- GhostProxy: creates a proxy object and maps it to a concrete class or anonymous object.
- GhostClass: a convenience base class that automatically performs the GhostProxy mapping and exposes a protected proxy object for initialization.
Installation
composer require spoova/ghost
<?php require 'vendor/autoload.php';
GhostFunction
GhostFunction lets you create callable members dynamically. It accepts an array of names and optional property values. Methods are registered by calling their name as a method and passing a closure. Properties are defined by passing an array entry with a single key/value pair.
<?php require 'vendor/autoload.php'; use Ghost\GhostFunction; $ghost = new GhostFunction(['greet', ['name' => 'Ghost']]); $ghost->greet(fn(string $who = 'world') => 'Hello ' . $who); echo $ghost->greet('Ada'); // Hello Ada echo PHP_EOL; echo $ghost->name; // Ghost
Useful helpers:
$ghost->ghosts(GhostFunction::methods); // list registered methods $ghost->ghosts(GhostFunction::properties); // list registered properties
GhostFunction is flexible, but it is not IDE-friendly. Use it when you want lightweight runtime behavior, or wrap it with GhostProxy and GhostClass for more structured usage.
GhostProxy
GhostProxy creates a dynamic proxy container and maps it to a class or anonymous object. The mapping callback receives a GhostDraft instance, which can be used to retrieve the proxy id and the underlying GhostFunction data.
<?php require 'vendor/autoload.php'; use Ghost\GhostDraft; use Ghost\GhostFunction; use Ghost\GhostProxy; $ghost = new GhostFunction(['greet']); $ghost->greet(fn(string $who = 'world') => 'Hello ' . $who); GhostProxy::new($ghost, function (GhostDraft $draft) { return new class($draft) { public function __construct(protected GhostDraft $get) {} public function hello(string $name = 'world'): string { return $this->get->ghost()->greet($name); } }; }); $object = GhostProxy::object(); echo $object->hello('Ada');
A few important points:
- Call GhostProxy::object() immediately after GhostProxy::new() to retrieve the mapped object.
- The mapping callback is responsible for creating the final object instance.
- GhostProxy::map() is used internally by GhostClass to connect the proxy state with the mapped object.
GhostDraft
GhostDraft is the minimal abstraction used by GhostProxy to hand a mapped object the proxy identity and its GhostFunction payload.
abstract class GhostDraft { abstract public function id(): int; abstract public function ghost(): GhostFunction; }
In practice, you rarely need to implement GhostDraft yourself. GhostProxy builds a compatible instance and passes it into the mapping callback.
GhostClass
GhostClass is the recommended base class when you want a clean and structured way to use GhostProxy. Its constructor is final, so child classes should implement ghostInit() instead of overriding the constructor.
<?php require 'vendor/autoload.php'; use Ghost\GhostClass; use Ghost\GhostDraft; use Ghost\GhostFunction; use Ghost\GhostProxy; class GreetingService extends GhostClass { protected function ghostInit(): void { echo $this->proxy->greet('Bob'); } } $ghost = new GhostFunction(['greet']); $ghost->greet(fn(string $who = 'world') => 'Hello ' . $who); GhostProxy::new($ghost, fn(GhostDraft $draft) => new GreetingService($draft)); $service = GhostProxy::object();
Notes about GhostClass
- The protected property proxy is created automatically during construction.
- The proxy is available inside ghostInit() and remains intact during initialization.
- The proxy property should not be overwritten manually.
Summary
If you want a lightweight runtime-only API, use GhostFunction. If you want a cleaner object-oriented integration with IDE-aware usage, prefer GhostProxy together with GhostClass.