Installation:
composer require hyperf/macroable
Register the service provider in config/autoload/services.php:
'providers' => [
Hyperf\Macroable\MacroableServiceProvider::class,
],
First Use Case:
Extend a class (e.g., App\Services\MyService) with macro capabilities:
use Hyperf\Macroable\Macroable;
class MyService extends Macroable
{
// Your existing methods
}
Define a Macro:
MyService::macro('customMethod', function () {
return 'Macro result';
});
Use the Macro:
$result = (new MyService())->customMethod();
Hyperf\Macroable\Macroable trait for core functionality.tests/ for usage patterns.Dynamic Method Injection: Use macros to add methods to classes without modifying their source:
// Add a 'toJson' macro to a model
App\Models\User::macro('toJson', function () {
return json_encode($this->toArray());
});
Service-Level Extensions:
Extend Hyperf services (e.g., Container, Request) globally:
// In a service provider's boot method
$container->get(\Hyperf\Contract\ContainerInterface::class)
->macro('singletonWith', function ($abstract, $concrete = null) {
return $this->singleton($abstract, $concrete);
});
Conditional Macros: Define macros conditionally (e.g., based on environment):
if (app()->environment('local')) {
App\Models\User::macro('debug', function () {
return $this->toArray() + ['debug' => true];
});
}
Macro Composition: Chain macros or reuse logic:
App\Models\User::macro('fullName', function () {
return "{$this->first_name} {$this->last_name}";
});
App\Models\User::macro('greet', function () {
return "Hello, {$this->fullName()}!";
});
Hyperf-Specific:
Use macros to extend Hyperf’s built-in components (e.g., Request, Response):
\Hyperf\HttpMessage\Stream\SwooleStream::macro('toJson', function () {
return json_encode($this->getContents());
});
Middleware: Add macros to middleware for reusable logic:
class CustomMiddleware implements MiddlewareInterface {
use Macroable;
public function __construct() {
$this->macro('logRequest', function ($request) {
logger()->info('Custom log:', ['path' => $request->getUri()]);
});
}
}
Testing: Mock macros in tests:
$mock = Mockery::mock(App\Models\User::class);
$mock->shouldReceive('customMacro')->andReturn('Mocked!');
Namespace Collisions: Macros with the same name across classes will override each other. Use unique names or namespaces:
// Avoid:
App\Models\User::macro('format', ...);
App\Models\Post::macro('format', ...); // Overrides User's macro
// Prefer:
App\Models\User::macro('userFormat', ...);
Late Binding Issues: Macros defined in traits or parent classes may not be available in child classes unless explicitly extended:
// Parent class
class ParentClass {
use Macroable;
}
// Child class (macros not inherited by default)
class ChildClass extends ParentClass {
// Must redefine macros if needed
}
Performance: Overusing macros can bloat method lookups. Benchmark critical paths.
Static Analysis Tools:
Some static analyzers (e.g., PHPStan) may flag macros as "undefined methods." Add @method PHPDoc annotations:
/**
* @method string customMacro()
*/
class MyClass {}
Check Registered Macros:
Use get_macros() to inspect registered macros:
$macros = (new MyService())->get_macros();
Disable Macros Temporarily:
Override the macro() method to debug:
MyService::macro('macro', function ($name, $macro) {
logger()->debug("Macro registered: $name");
return parent::macro($name, $macro);
});
Custom Macro Storage:
Extend the Macroable trait to use a custom storage backend (e.g., database):
class CustomMacroable {
protected $macros = [];
public function loadMacros() {
$this->macros = Cache::get('app_macros', []);
}
public function macro($name, $macro) {
$this->macros[$name] = $macro;
Cache::put('app_macros', $this->macros);
}
}
Macro Events: Trigger events when macros are registered/called:
MyService::macro('onMacroRegistered', function ($name) {
event(new MacroRegistered($name));
});
MyService::macro('customMethod', function () {
$this->onMacroRegistered('customMethod');
return 'Result';
});
Hyperf Context: Leverage Hyperf’s DI container to resolve dependencies in macros:
MyService::macro('withDependency', function () {
return $this->container->get(MyDependency::class);
});
MacroableServiceProvider is registered after the class you’re extending (if using autoloading). php bin/hyperf.php cache:warmup
How can I help you explore Laravel packages today?