定制 laikait/laika-relay 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

laikait/laika-relay

Composer 安装命令:

composer require laikait/laika-relay

包简介

Laika PHP Framework Relay Service

README 文档

README

Relay is the service container and static proxy system built for the Laika Framework. It gives you a lightweight dependency injection container (RelayRegistry), a clean static proxy base (Relay), and a two-phase provider system (RelayProvider + ProviderRegistry) that lets third-party packages register their own services into the framework.

Part of laikait/laika-core · Requires PHP 8.1+

Table of Contents

How It Works

Your Code
    │
    ▼
Auth::check()                    ← Relay proxy  (static call)
    │
    ▼
RelayRegistry::make('auth')      ← Container    (resolves & caches)
    │
    ▼
Laika\Core\Auth\Auth::check()    ← Real class   (real method)

There are three independent pieces:

Class Role
RelayRegistry The container. Holds bindings, resolves and caches instances.
Relay Abstract base. Forwards static calls to the resolved instance.
RelayProvider Integration point. Packages extend this to register services.
ProviderRegistry Manages provider lifecycle — calls register() then boot().

File Structure & Default Services

services/       # NAMESPACE: Laika\Service
└── Template/
    ├── Asset.php                       # HTML Template Asset Relay Class Container
    ├── Meta.php                        # HTML Template Meta Relay Class Container
└── Activity.php                        # Activity Relay Class Container
└── Api.php                             # Api Relay Class Container
└── ClientAuth.php                      # ClientAuth Relay Class Container
└── Config.php                          # Config Relay Class Container
└── Cookie.php                          # Cookie Relay Class Container
└── CSRF.php                            # CSRF Relay Class Container
└── Date.php                            # Date Relay Class Container
└── DB.php                              # DB Relay Class Container
└── Directory.php                       # Directory Relay Class Container
└── Email.php                           # Email Relay Class Container
└── File.php                            # File Relay Class Container
└── Hook.php                            # Hook Relay Class Container
└── Image.php                           # Image Relay Class Container
└── Infra.php                           # Infra Relay Class Container
└── IP.php                              # IP Relay Class Container
└── Local.php                           # Local Relay Class Container
└── Math.php                            # Math Relay Class Container
└── Meta.php                            # Meta Relay Class Container (Get Class Container Comments)
└── Nav.php                             # Nav Relay Class Container
└── Option.php                          # Option Relay Class Container
└── Page.php                            # Page Relay Class Container
└── Redirect.php                        # Redirect Relay Class Container
└── Regex.php                           # Regex Relay Class Container
└── Request.php                         # Request Relay Class Container
└── Response.php                        # Response Relay Class Container
└── StaffAuth.php                       # StaffAuth Relay Class Container
└── Token.php                           # Token Relay Class Container
└── Unique.php                          # Unique Relay Class Container
└── Upload.php                          # Upload Relay Class Container
└── Url.php                             # Url Relay Class Container
└── Vault.php                           # Vault Relay Class Container
└── Visitor.php                         # Visitor Relay Class Container

src/            # NAMESPACE: Laika\Relay
└── Exceptions/
    ├── RelayException.php              # Exception Class
└── Relays/
    ├── Relay.php                       # Abstract base — extend to create a proxy
    ├── RelayRegistry.php               # The container
    ├── RelayProvider.php               # Abstract base for service providers
    └── ProviderRegistry.php            # Manages provider loading and booting

services/ [Laika\Service\*] is a convention, not a requirement. Relay classes can live anywhere.

RelayRegistry

The container. All services are registered here before the application starts handling requests.

use Laika\Relay\RelayRegistry;

$registry = new RelayRegistry();

instance()

Register an already-constructed object directly.

$registry->instance(string $key, object $instance): static

The registry stores exactly the object you give it — it never calls new. The object is available immediately on make().

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$registry->instance('db', $pdo);

Use when the object already exists before the registry is set up, or when construction has side effects that must be controlled manually.

singleton()

Register a singleton binding — built once on the first make() call, then cached and reused for the lifetime of the request.

$registry->singleton(string $key, Closure|string $concrete, array $args = []): static
// Class string — no args needed
$registry->singleton('session', Session::class);

// Class string — with primitive args (positional)
$registry->singleton('mailer', Mailer::class, ['smtp']);

// Class string — with primitive args (named)
$registry->singleton('queue', DatabaseDriver::class, [
    'table'   => 'async_jobs',
    'retries' => 3,
]);

// Closure — manual control, receives the registry
$registry->singleton('db', function (RelayRegistry $r) {
    $config = $r->make('config');
    $pdo    = new PDO(
        $config->get('db.dsn'),
        $config->get('db.user'),
        $config->get('db.pass')
    );
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    return $pdo;
});

// Closure — conditional factory
$registry->singleton('cache', function (RelayRegistry $r) {
    return match ($r->make('config')->get('cache.driver')) {
        'redis' => new RedisCache(),
        'file'  => new FileCache(),
        default => new ArrayCache(),
    };
});

Use for stateful services shared across the entire request: session, auth, config, mailer, cache.

bind()

Register a transient binding — a brand-new instance is created on every make() call. Nothing is ever cached.

$registry->bind(string $key, Closure|string $concrete, array $args = []): static
$registry->bind('validator', Validator::class);

$v1 = $registry->make('validator');
$v2 = $registry->make('validator');
// $v1 !== $v2  — completely independent instances

Use for stateless, disposable objects where shared state would be a bug: validators, form request objects, DTOs, value objects.

make()

Resolve a binding by key and return the object.

$auth = $registry->make('auth');

Resolution order:

1. Pre-bound instance      (instance())
2. Cached singleton        (already resolved on a prior make())
3. Singleton binding       → build, cache, return
4. Transient binding       → build fresh, return (no cache)
5. Bare class name         → attempt direct auto-wire if class exists
6. RelayException          → nothing matched

has()

Check whether a key has any binding registered.

if ($registry->has('payment')) {
    $gateway = $registry->make('payment');
}

forgetInstance()

Clear a cached singleton instance, forcing re-resolution on the next make().

$registry->forgetInstance('date');
$registry->singleton('date', Date::class, ['America/New_York']);

// Next make('date') builds a fresh instance with the new args

Lifetime Comparison

Method Who builds it How many instances When built Cached
instance() You 1 (yours) Before registration Yes — immediately
singleton() Registry 1 On first make() Yes — after first use
bind() Registry N (one per call) On every make() Never

Prefer singleton() over instance() for most services. singleton() is lazy — if nothing ever calls make('x'), the object is never constructed. instance() is eager — the object exists the moment you register it, whether anything uses it or not.

Auto-Wiring

When a class string is registered (not a Closure), the registry uses PHP reflection to resolve constructor parameters automatically.

// Auth::__construct(Session $session, Config $config)
// Both 'session' and 'config' are already in the registry → auto-wired

$registry->singleton('session', Session::class);
$registry->singleton('config',  Config::class);
$registry->singleton('auth',    Auth::class);   // Session and Config injected automatically

Per-parameter resolution order:

1. Type-hinted class found in registry        → make() it
2. Type-hinted class not in registry, exists  → build() recursively
3. Primitive — named key in $args             → use it
4. Primitive — positional in $args            → use it
5. Has a default value                        → use it
6. Nullable                                   → pass null
7. Nothing matched                            → throw RelayException

Mixed — auto-wire objects, supply primitives:

// Mailer::__construct(Config $config, string $driver)
// Config is in the registry; 'smtp' cannot be auto-wired
$registry->singleton('mailer', Mailer::class, ['smtp']);

RelayProvider

The integration point for packages. Extend RelayProvider and implement register(). Optionally override boot().

<?php

namespace YourPackage;

use Laika\Relay\RelayProvider;

class YourRelayProvider extends RelayProvider
{
    public function register(): void
    {
        // Bind your services here
        $this->registry->singleton('your-service', YourService::class);
    }

    public function boot(): void
    {
        // Use other services here — everything is registered by now
        $config = $this->registry->make('config');
        $this->registry->make('your-service')->configure($config->get('your.key'));
    }
}

register()

Only call bind(), singleton(), or instance() here. Do not call make().

When register() runs, other providers may not have registered their services yet. Calling make() at this stage risks a RelayException if the dependency is not yet bound.

public function register(): void
{
    // ✅ Correct
    $this->registry->singleton('payment', PaymentGateway::class);
    $this->registry->bind('payment.invoice', Invoice::class);

    // ❌ Wrong — 'config' may not be registered yet
    $key = $this->registry->make('config')->get('payment.key');
}

boot()

Called after all providers have run register(). Safe to call make() freely.

public function boot(): void
{
    // ✅ Config-driven setup
    $config  = $this->registry->make('config');
    $gateway = $this->registry->make('payment');
    $gateway->setApiKey($config->get('payment.stripe_key'));

    // ✅ Attach event listeners
    $events = $this->registry->make('events');
    $events->listen('order.placed', SendPaymentRequestListener::class);

    // ✅ Register routes
    Http::group('/payment', function () {
        Http::post('/charge',  'PaymentController@charge');
        Http::post('/webhook', 'PaymentController@webhook');
    });
}

register() vs boot()

register() boot()
Purpose Promise a service exists Use services that others promised
When called Before other providers boot After ALL providers have registered
Call make()? ⚠️ Risky — others may not be ready ✅ Safe — everything is registered
Typical use singleton, bind, instance Config setup, routes, middleware, events

One-line rule: register() = bind things. boot() = use things.

ProviderRegistry

Manages the full provider lifecycle. Register providers in any order — the two-phase approach guarantees correctness.

use Laika\Relay\ProviderRegistry;

$providers = new ProviderRegistry($registry);

// Register accepts class string or instance
$providers->register(CoreRelayProvider::class);
$providers->register(new PaymentRelayProvider($registry));

// Boot all providers after all register() calls
$providers->boot();

Duplicate registrations are silently ignored — registering the same provider class twice is safe.

has() — check if a provider is registered:

if ($providers->has(PaymentRelayProvider::class)) {
    // ...
}

Bootstrap

Complete application bootstrap sequence:

<?php

use Laika\Relay\Relay;
use Laika\Relay\RelayRegistry;
use Laika\Relay\ProviderRegistry;
use Laika\Relay\Relays\CoreRelayProvider;

// 1. Build the container
$registry  = new RelayRegistry();

// 2. Build the provider manager
$providers = new ProviderRegistry($registry);

// 3. Register the Laika core provider (always first)
$providers->register(CoreRelayProvider::class);

// 4. Register providers declared in config/app.php
foreach (config('app.providers') as $providerClass) {
    $providers->register($providerClass);
}

// 5. Boot all providers (runs after all register() calls are complete)
$providers->boot();

// 6. Wire the registry into the Relay system
Relay::setRegistry($registry);

config/app.php:

return [
    'providers' => [
        // Third-party packages
        Acme\LaikaPayment\PaymentRelayProvider::class,
        Acme\LaikaBarcode\BarcodeRelayProvider::class,
    ],
];

Third-Party Integration

This is how an external package integrates with Laika.

1. Create a RelayProvider

<?php

namespace Acme\LaikaPayment;

use Laika\Relay\RelayProvider;

class PaymentRelayProvider extends RelayProvider
{
    public function register(): void
    {
        $this->registry->singleton('payment',         PaymentGateway::class);
        $this->registry->singleton('payment_webhook', WebhookHandler::class);
        $this->registry->bind('payment_invoice',      Invoice::class);
    }

    public function boot(): void
    {
        $key = $this->registry->make('config')->get('stripe_key');
        $this->registry->make('payment')->setApiKey($key);
    }
}

2. Ship a Relay class (optional but recommended)

<?php

namespace Acme\LaikaPayment\Relay;

use Laika\Relay\Relay;

/**
 * @method static string charge(int $amount, string $currency)
 * @method static bool   verify(string $token)
 * @method static array  history(int $limit = 10)
 */
class Payment extends Relay
{
    protected static function getRelayAccessor(): string
    {
        return 'payment';
    }
}

Full flow

composer require acme/laika-payment
        ↓
Add PaymentRelayProvider to config/app.php
        ↓
Bootstrap: ProviderRegistry calls register() → binds 'payment'
        ↓
Bootstrap: ProviderRegistry calls boot()     → configures with API key
        ↓
Relay::setRegistry($registry)
        ↓
Payment::charge() → __callStatic → make('payment') → PaymentGateway::charge()

Relay — The Static Proxy

Creating a Relay Class

Extend Relay, implement getRelayAccessor(), and document every proxied method with @method static tags.

<?php

declare(strict_types=1);

namespace Laika\Relay\Relays;

use Laika\Relay\Relay;

/**
 * @method static bool   attempt(array $credentials)
 * @method static bool   check()
 * @method static bool   guest()
 * @method static mixed  user()
 * @method static void   logout()
 */
class Auth extends Relay
{
    protected static function getRelayAccessor(): string
    {
        return 'auth';
    }
}

@method tag rules:

  • Always include static — every call through a Relay is a static call.
  • Import types with use statements — same as real code.
  • Union return types are valid: @method static Item|Builder end()
  • Array types are valid: @method static Item[] all()
  • FQCNs or short names both work (short names require use).
use Laika\Nav\Helper\Item;
use Laika\Nav\Builder;

/**
 * @method static Item|Builder  end()
 * @method static Item[]        all()
 * @method static Item|null     find(string $name)
 */

Using a Relay

Import the Relay class, not the underlying service class.

// ✅ Correct — imports the Relay proxy
use Laika\Service\StaffAuth;
use Laika\Service\ClientAuth;
use Laika\Service\Config;
...

if (Auth::check()) {
    $name = Config::get('app', 'name');
    Session::set('last_page', '/dashboard');
}
// ❌ Wrong — imports the real class, static calls fail
use Laika\Core\Auth\Auth;

Auth::check(); // Fatal: Non-static method cannot be called statically

You do not need a Relay class to access a service. The registry is always available:

// Direct registry access — no Relay class needed
$session = Relay::getRegistry()->make('session');
$session->set('user_id', 42);

Method Chaining

Chaining works automatically. The first static call goes through __callStatic and returns whatever the underlying method returns. If that's $this, subsequent calls are normal instance calls.

// Entire chain — first call is static, rest are instance
Date::now()
    ->setTimezone('Asia/Dhaka')
    ->modify('+7 days')
    ->setFormat('d M Y')
    ->format();

Date::fromFormat('d/m/Y', '01/04/2025')
    ->toUtc()
    ->toIso8601();

Date::setTimestamp(time())
    ->modify('+1 hour')
    ->humanDiff();

Chaining ends when a method returns a non-object (string, int, array). Do not chain after terminal methods like format(), getTimestamp(), or toArray().

Switching Instances at Runtime

Use swap() to replace the resolved instance for a specific Relay — for example, switching auth guard in middleware.

<?php

namespace App\Middleware;

use Laika\Core\Auth\Auth as AuthService;
use Laika\Service\Auth;

class AdminMiddleware
{
    public function handle(): void
    {
        Auth::swap(new AuthService(guard: 'admin', table: 'admins'));
        // All subsequent Auth:: calls now resolve against the admin guard
    }
}

Or re-register via the registry for a cleaner approach:

Relay::getRegistry()->forgetInstance('auth');
Relay::getRegistry()->singleton('auth', AuthService::class, ['admin', 'admins']);

Testing

Inject a mock for one test

use Laika\Service\Auth;

protected function setUp(): void
{
    $mock = $this->createMock(\Laika\Core\Auth\Auth::class);
    $mock->method('check')->willReturn(true);

    Auth::swap($mock);
}

protected function tearDown(): void
{
    Auth::clearResolvedInstance();
}

public function test_dashboard_requires_auth(): void
{
    $this->assertTrue(Auth::check()); // uses mock
}

Replace the entire registry for full isolation

use Laika\Relay\Relay;
use Laika\Relay\RelayRegistry;

protected function setUp(): void
{
    $testRegistry = new RelayRegistry();
    $testRegistry->instance('auth',    $this->createMock(Auth::class));
    $testRegistry->instance('session', $this->createMock(Session::class));
    $testRegistry->instance('config',  $this->createMock(Config::class));

    Relay::swapRegistry($testRegistry);
}

swapRegistry() is intentionally separate from setRegistry() so that accidental double-calls in production code throw a RelayException, while tests can swap freely.

Exceptions

All errors throw Laika\Relay\Exceptions\RelayException.

Situation Message
setRegistry() called more than once RelayRegistry has already been set.
getRegistry() before setRegistry() RelayRegistry has not been set.
No binding found for key No binding registered for [key].
Class not found during build Class [ClassName] not found.
Unresolvable constructor parameter Cannot resolve parameter [$name] for [ClassName].
Method not found on resolved instance Method [method] does not exist on [ClassName].
RelayProvider class not found RelayProvider [ClassName] class not found.
Provider does not extend RelayProvider [ClassName] must extend Laika\Relay\RelayProvider.

License

MIT — see LICENSE for details.

Author: Showket Ahmed Email: riyadhtayf@gmail.com Package: laikait/laika-relay GitHub: laikait/laika-relay

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-21

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固