Plugin Annotations & Plugin Context (Workbench)

Workbench layer for parsing and syncing plugin method annotations into the system. Used by system plugins to register URL rules, areas, sections, and permissions from plugin docblocks.

Code location: src/ (namespace Workbench). Extracted from core.

PluginContext

PluginContext holds metadata and runtime state for a single plugin. It is the context passed to PluginAnnotations for parsing.

File: src/PluginContext.php

Constructor

new PluginContext(string $name, string $path)
  • $name — Plugin name (e.g. "Invoices")
  • $path — Base path containing the plugin (e.g. plugins/Invoices/). The plugin file must exist at {path}{name}Plugin.php.

Throws SystemException if the plugin file is not found.

Methods

Method Returns Description
getBasePath() string Base directory path of the plugin
getName() string Plugin name
setSystemFlag(bool $flag) void Mark plugin as system plugin
isSystem() bool Whether the plugin is a system plugin
getInstance() AbstractPlugin Lazy-loaded plugin instance via Core::getInstance()->getPluginInstance() (returns by reference)
setVersion(int $version) void Set plugin version
getVersion() int Current version (-1 if not set)

Usage

$context = new PluginContext('Invoices', 'plugins/Invoices/');
$plugin = $context->getInstance();

PluginAnnotations

PluginAnnotations parses docblock annotations from plugin methods and can sync them into an ISystemObject (e.g. URL rules, areas, sections).

File: src/PluginAnnotations.php

Constructor

new PluginAnnotations(PluginContext $context)

Built-in Annotations — prefer PHP 8 Attributes

Plugin routing/permission metadata can be declared in two forms. PHP 8 attributes are the preferred form for new code; PHPDoc tags remain supported for existing plugins.

Why attributes are preferred: - FQN / constant resolution for free. #[SectionAttribute(AttendanceSections::COMMON)] carries the constant's actual value ("attendance_common"). The doc-tag form is a raw string until the parser resolves it (see Phase 4 note below). - IDE & refactor support. Renaming a constant or class updates every attribute usage; PHPDoc strings are silently invisible to refactor tools. - Static analysis. phan / phpstan understand attribute types; doc-tag values are opaque.

The three attribute classes ship in festi-framework-core under core\plugin\attribute:

Attribute Replaces Constructor
AreaAttribute @area (string $name)
UrlRouteAttribute @urlRule (string $pattern)
SectionAttribute @section (string $name, string $mask = SectionAttribute::MASK_EXEC)

SectionAttribute::MASK_READ / MASK_WRITE / MASK_EXEC hold the raw permission codes '2' / '4' / '6'.

Preferred form:

use core\plugin\attribute\AreaAttribute;
use core\plugin\attribute\SectionAttribute;
use core\plugin\attribute\UrlRouteAttribute;

#[UrlRouteAttribute('~^/company/([0-9]+)/attendance/$~')]
#[SectionAttribute(AttendanceSections::COMMON)]
#[AreaAttribute('backend')]
public function onDisplayBySchool(Response &$response, int $idSchool): bool
{
    // ...
}

Precedence on conflict: when a method declares both an attribute and a doc-tag for the same annotation name, the attribute's value wins; orthogonal annotation names from either source coexist on the same MethodInfo.

Legacy: PHPDoc annotations

The framework still parses PHPDoc tags for backward compatibility. New code should use attributes.

Annotation Description
@area URL area(s) for the method (e.g. @area default, @area admin)
@urlRule Regex pattern for URL routing (e.g. @urlRule ~/test/([0-9])/~)
@section Section name and mask for permissions (e.g. @section sectionName\|read)

Section annotation format:

@section <sectionName>|<mask>
  • sectionName — Section identifier. Class constants are supported: @section AttendanceSections::COMMON is rewritten to the constant's value at parse time, against the source file's use / namespace context. Brings doc-tag plugins to the same FQN-correctness floor attributes have natively.
  • mask — Symbolic (read / write / exec) or numeric (2 / 4 / 6). Default: exec.

Doc-tag @return / @param values also receive class-name resolution: a leading short class name is rewritten to its FQN against the file's use aliases (e.g. @return SomeDto with use App\Dto\SomeDto; lands as App\Dto\SomeDto in MethodInfo::getAnnotationValues('return')).

parse()

public function parse(
    array $externalAnnotations = [],
    array $externalAttributes = []
): ?PluginMetadata

Parses the plugin class via reflection and merges annotations from both doc-tags and PHP 8 attributes into a single PluginMetadata.

  • $externalAnnotations — Additional doc-tag annotation names to parse (e.g. ['rpc'] for RPC plugin).
  • $externalAttributes — Plugin-supplied attribute classes (FQN class-string => annotation name) merged on top of the built-in core map.
  • Returns PluginMetadata with AnnotationRegistry and MethodInfo[] per method.

Both extension points are typically wired through BeforePluginAnnotationsParseEvent, allowing plugins to extend the parser without touching workbench internals.

sync()

public function sync(ISystemObject $object): bool

Syncs parsed annotations into the system object. Must call parse() first.

  • _onAreaMethodAnnotation — Adds URL areas
  • _onUrlRuleMethodAnnotation — Registers URL rules with patterns and areas
  • _onSectionMethodAnnotation — Registers sections and section actions with masks

PluginMetadata (parse result)

File: src/Annotation/PluginMetadata.php

  • getAnnotations() → AnnotationRegistry
  • getMethods() → array<string, MethodInfo>
  • getMethodInfo(string $methodName) → MethodInfo|null
  • hasAnnotation(string $name) → bool
  • getAnnotationMethodNames(string $name) → string[]

MethodInfo

File: src/Annotation/MethodInfo.php

Per-method parsed data:

  • getDescription() — Docblock description
  • getParamsTypes() — Parameter types from reflection
  • getReturnType() — Return type
  • hasAnnotation(string $name) → bool
  • getAnnotationValues(string $name) → array

AnnotationRegistry

File: src/Annotation/AnnotationRegistry.php

Registry of annotation names to method names. Methods: hasAnnotation(string $name), getMethodNames(string $name), add(string $annotationName, string $methodName), toArray().

Workbench

File: src/Workbench.php

Orchestrates plugin scanning, installation, DB dumps, and annotation processing.

Main methods

Method Description
install(array $options) Full install: scan plugins, install system plugin, run dumps, then install each non-system plugin. Options: skip_annotations, skip_validation_system_plugin, dump_type
doScanPlugins() Scans plugins_path, builds PluginContext[], sets system plugin context
getSystemPluginContext() Returns (by reference) the system plugin PluginContext; creates it from Core options if needed
getPluginsContexts() Returns all plugin contexts from last scan
doInstallPlugin(PluginContext $context) Install a single plugin: version, dump, change plugin in system object, process annotations
doProcessingPluginAnnotations(PluginContext $context) new PluginAnnotations($context)->parse()->sync($object)
doPluginDump(PluginContext $context) Runs SQL from install/ (e.g. install.{type}.sql, updates{N}.{type}.sql) and updates plugin version

Exceptions

  • WorkbenchException (src/Exception/WorkbenchException.php) — Thrown by Workbench (e.g. system plugin not found, undefined db transaction, wrong dump filename, forbidden path).