MacroMix (Mixin Trait)#
Have you ever wished your PHP class had a certain method—even though it’s
not defined in the class? MacroMix lets you dynamically add methods
(macros) at runtime to any class without requiring a base class. It works
purely via PHP’s __call / __callStatic magic under the hood.
Advantages#
Zero coupling — You don’t need to extend a base class. Just
use MacroMix;.Method chaining — If your macro returns
null, it automatically returns$thisso you can continue chaining.Config or annotation loading — Bulk-load macros from arrays or
@Macro("name")annotations.Thread safety (optional) — Enable a lock if you care about concurrency.
Basic Usage#
Include the trait in any class:
namespace App;
use Infocyph\InterMix\Remix\MacroMix;
class House
{
use MacroMix;
// Optional: enable thread-safe macro registration
public const ENABLE_LOCK = true;
protected string $color = 'gold';
}
Registering a Macro#
public static function macro(string $name, callable|object $macro): void
$name: method name you want to add.$macro: a callable or object. Closures are wrapped so that if they returnnull,$thisis returned instead for chaining.
Example:
House::macro('fill', function (string $color) {
$this->color = $color;
return $this;
});
$house = new House();
$house->fill('blue')->fill('green');
echo $house->color; // "green"
Checking and Removing Macros#
public static function hasMacro(string $name): bool
public static function removeMacro(string $name): void
Example:
House::macro('sayHello', fn() => 'Hello');
echo House::hasMacro('sayHello'); // true
House::removeMacro('sayHello');
echo House::hasMacro('sayHello'); // false
Calling Macros#
Any time you call an undefined method (static or instance), MacroMix checks
its internal registry:
If the macro exists, it invokes it—binding
$thisif needed.If the macro returns
null, the trait yields$this(or the class name, if invoked statically).If the macro is not found, it throws:
Exception: Method ClassName::missingMacro does not exist.
Example:
House::macro('floor', fn() => 'I am the floor');
echo House::floor(); // “I am the floor”
Loading from Configuration#
public static function loadMacrosFromConfig(array $config): void
$config: associative array of['name' => callable, ...]If
ENABLE_LOCKis set, a lock is acquired during registration.
Example:
$config = [
'toUpper' => fn($s) => strtoupper($s),
'reverse' => fn($s) => strrev($s),
];
House::loadMacrosFromConfig($config);
$h = new House();
echo $h->toUpper('gtk'); // “GTK”
echo $h->reverse('php'); // “php”
Loading from Annotations#
public static function loadMacrosFromAnnotations(string|object $class): void
Scans public method PHPDoc for
@Macro("name").Registers the method under the given name.
Example:
class MyMixin
{
/**
* @Macro("shout")
*/
public function shout(string $text): string
{
return strtoupper($text) . '!';
}
}
House::loadMacrosFromAnnotations(MyMixin::class);
$h = new House();
echo $h->shout('hello'); // “HELLO!”
Retrieving All Macros#
public static function getMacros(): array
Returns a list of all registered macros in the form:
['macroName' => callable, ...]
Example:
House::macro('one', fn() => 1);
House::macro('two', fn() => 2);
$macros = House::getMacros();
// ['one' => callable, 'two' => callable]
Mixing in an Entire Class or Object#
public static function mix(object|string $mixin, bool $replace = true): void
$mixin: either an object instance or class name.Public and protected methods are reflected.
Static methods wrap static invocation; non-static methods bind to instance.
$replace = falseskips existing macro names.
Example:
$mixin = new class {
public function greet(string $name): string {
return "Hello, $name!";
}
protected function whisper(string $msg): string {
return "psst... $msg";
}
};
House::mix($mixin);
$h = new House();
echo $h->greet('World'); // “Hello, World!”
echo $h->whisper('John'); // “psst... John”
Error on Undefined Macro#
If you call a macro that doesn’t exist:
$house->nonexistent();
Results in:
Exception: Method App\House::nonexistent does not exist.
Thread Safety#
If you define the constant:
class House {
use MacroMix;
public const ENABLE_LOCK = true;
}
Then:
Write operations (
macro(),removeMacro(),loadMacrosFromConfig()) acquire an exclusive file lock on the trait source.Read operations (e.g.
hasMacro(),getMacros(), macro calls) skip locking.