respect/fluent
Respect/Fluent is a small PHP library for building fluent, chainable APIs and expressive method chains. It helps create readable domain-specific syntax and cleaner interfaces with minimal boilerplate, making object interactions more natural and easier to compose.
Installation
composer require respect/fluent
No additional configuration is required—just autoload via Composer.
First Use Case
Create a fluent interface for a domain-specific namespace (e.g., App\Services\Payment):
use Respect\Fluent\Fluent;
$payment = Fluent::make('App\Services\Payment');
$result = $payment->charge(100)->refund(50);
This dynamically generates a fluent chain for App\Services\Payment::charge() and Payment::refund().
Where to Look First
Respect\Fluent\Fluent class docs (if generated via PHPDoc).Dynamic Method Chaining
Use Fluent::make() to auto-generate fluent interfaces for any class:
$user = Fluent::make('App\Models\User');
$user->find(1)->update(['name' => 'Alice']);
Maps to User::find(1)->update(...).
Laravel Service Container Integration Bind fluent interfaces to the container for dependency injection:
$this->app->bind('payment.fluent', function ($app) {
return Fluent::make('App\Services\Payment');
});
Inject via constructor:
public function __construct(public Fluent $payment) {}
Static vs. Instance Methods
Fluent::make('Class')->method().$payment = Fluent::make('App\Services\Payment')->new();
$payment->charge(100)->refund(50);
Customizing Fluent Behavior
Extend Respect\Fluent\Fluent to add middleware or validation:
class CustomFluent extends Fluent {
public function __call($method, $args) {
if ($method === 'charge' && $args[0] < 1) {
throw new \InvalidArgumentException('Amount must be > 0');
}
return parent::__call($method, $args);
}
}
Fluent in a facade for cleaner syntax:
Facades\Fluent::make('App\Services\Payment')->charge(100);
$stripe = Fluent::make('App\Services\StripeClient');
$stripe->customers()->create(['email' => '[email protected]']);
Mockery:
$mock = Mockery::mock('overload:App\Services\Payment');
$mock->shouldReceive('charge')->andReturnSelf();
Method Name Collisions
Fluent assumes all methods are chainable. If a method returns non-self (e.g., User::getId()), it breaks the chain. Fix: Use ->then() to reset the chain:
$user->find(1)->then()->update(['name' => 'Alice']);
Static Method Limitations Fluent doesn’t natively support static methods with arguments beyond the first. Workaround: Use a wrapper class or closure:
$payment = Fluent::make('App\Services\Payment');
$payment->callStatic('setApiKey', ['sk_live_123']);
Performance Overhead
Dynamic method calls add reflection overhead. Tip: Cache Fluent instances in Laravel’s container or use a factory pattern for heavy usage.
Namespace Ambiguity
Avoid namespaces with similar prefixes (e.g., App\Services\Payment vs. App\Service\Payment). Fix: Use fully qualified names:
Fluent::make('App\Services\PaymentService');
error_reporting(E_ALL) to catch undefined method issues early.__call to log method chains for debugging:
public function __call($method, $args) {
\Log::debug("Fluent called: {$method} with args: " . json_encode($args));
return parent::__call($method, $args);
}
Custom Callables
Pass a closure to Fluent::make() for pre-processing:
$fluent = Fluent::make('App\Services\Payment', function ($method, $args) {
if ($method === 'charge') {
$args[0] = $args[0] * 1.1; // Add 10% fee
}
return [$method, $args];
});
Middleware Pipeline Chain middleware to validate/modify calls:
$fluent = Fluent::make('App\Services\Payment')
->pipe(function ($callable) {
return function ($method, $args) {
if ($method === 'refund') {
\Log::info("Refund initiated for {$args[0]}");
}
return $callable($method, $args);
};
});
Fluent for Collections Combine with Laravel Collections for fluent-style data processing:
$users = Fluent::make('App\Models\User')
->all()
->filter(fn ($user) => $user->active)
->pluck('name');
How can I help you explore Laravel packages today?