Search by

sotvokun / webman-aop

sotvokun

This package is abandoned and no longer maintained. The author suggests using the sotvokun/container package instead.

Webman plugin sotvokun/webman-aop

Package info

github.com/sotvokun/webman-aop

pkg:composer/sotvokun/webman-aop

Statistics

Installs: 14

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.2 2026-09-08 17:40 UTC

This package is auto-updated.

Last update: 2026-09-11 14:40:18 UTC


README

sotvokun/webman-aop integrates Ray.Aop with Webman and provides an Illuminate-based container. Mark a public service method with an Aspect Attribute and the interceptor is applied automatically when the service is resolved from the container.

Requirements

  • PHP 8.3+
  • Webman 2.1+

Installation

composer require sotvokun/webman-aop

Webman automatically exports the plugin configuration to config/plugin/sotvokun/webman-aop/ during installation.

Configure the generated proxy directory and directories to scan in config/plugin/sotvokun/webman-aop/app.php:

<?php

return [
    'enable' => true,
    'class_path' => runtime_path('aop'),
    'scan_dirs' => [
        ...glob(base_path() . '/module/*/service'),
        ...glob(base_path() . '/module/*/query'),
    ],
];

Create or replace the container in config/container.php with the package container:

<?php

use Sotvokun\Webman\Aop\Container;

return new Container();

Define an Aspect

An Aspect is a PHP Attribute extending Sotvokun\Webman\Aop\Aspect. Its interceptors() method returns interceptor instances or class names.

<?php

namespace module\order\aspect;

use Attribute;
use module\order\interceptor\ExampleInterceptor;
use Sotvokun\Webman\Aop\Aspect;

#[Attribute(Attribute::TARGET_METHOD)]
final class ExampleAspect extends Aspect
{
    public static function interceptors(): array
    {
        return [ExampleInterceptor::class];
    }
}

An interceptor implements Sotvokun\Webman\Aop\MethodInterceptor:

<?php

namespace module\order\interceptor;

use Sotvokun\Webman\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

final class ExampleInterceptor implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation): mixed
    {
        // Before invoking the target method.
        $result = $invocation->proceed();
        // After invoking the target method.

        return $result;
    }
}

Apply the Aspect to a service method located in a configured scan directory:

<?php

namespace module\order\service;

use module\order\aspect\ExampleAspect;

class OrderService
{
    #[ExampleAspect]
    public function create(array $input): void
    {
        // Business logic.
    }
}

Resolve OrderService through Webman's container or constructor injection as usual. No factory call is needed:

final class OrderController
{
    public function __construct(private readonly OrderService $orderService)
    {
    }
}

Dependency Injection in Interceptors

When interceptors() returns a class name, the package creates it through the Webman container. Constructor dependencies are therefore injected normally:

final class ExampleInterceptor implements MethodInterceptor
{
    public function __construct(private readonly ConnectionInterface $connection)
    {
    }

    public function invoke(MethodInvocation $invocation): mixed
    {
        return $invocation->proceed();
    }
}

Returning new ExampleInterceptor() bypasses container construction for that interceptor.

Lazy Injection

Mark a class-typed constructor dependency with #[Sotvokun\Webman\Aop\Attribute\Lazy]. The container injects a proxy and resolves the real service only when its state is first accessed:

use Sotvokun\Webman\Aop\Attribute\Lazy;

final class ReportController
{
    public function __construct(#[Lazy] private ReportService $reports)
    {
    }
}

The dependency type must be an instantiable, user-defined class with at least one non-static, non-virtual instance property (an inherited property also qualifies). Interfaces, union types, internal classes and their subclasses, and classes with no backed instance property are rejected immediately with a LogicException. Although PHP permits stdClass, it has no declared backed property and is therefore also rejected by this container.

PHP initializes a lazy proxy when its state is observed or changed. A method call that does not access object state does not initialize it. The factory must return a non-lazy instance of the proxy's class (or a compatible parent); the container guarantees this for normal services and for generated Ray.Aop classes.

The consuming service may use AOP attributes. The proxy is injected while the consuming service is constructed, and ReportService remains lazy:

use module\order\aspect\ExampleAspect;

final class ReportController
{
    public function __construct(#[Lazy] private ReportService $reports)
    {
    }

    #[ExampleAspect]
    public function show(): array
    {
        return $this->reports->latest();
    }
}

Lazy AOP services

#[Lazy] can be used on a dependency whose public methods have AOP attributes. The container first generates Ray.Aop's child class, then creates the PHP lazy proxy from that generated class. When the service is first initialized, the real object therefore has the same class as its lazy proxy and interceptors remain active:

use module\order\aspect\ExampleAspect;

final class ReportService
{
    public function __construct(private ReportClient $client)
    {
    }

    #[ExampleAspect]
    public function latest(): array
    {
        // ...
    }
}

Constraints

  • Only classes under scan_dirs are considered.
  • Target classes must be non-final and instantiable.
  • Intercepted methods must be public, non-static, and non-final.
  • Services must be resolved by the container. Direct new OrderService() calls bypass AOP.
  • Ray.Aop writes generated proxy classes to class_path.

Generated Proxy Cache

Each worker writes proxy classes to class_path/<worker-pid>. When a worker reloads, its replacement has a new PID and generates fresh proxy classes without affecting running workers. Bootstrap clears the entire class_path once per Webman restart, protected by a file lock so multiple workers do not clear it concurrently.