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::COMMONis rewritten to the constant's value at parse time, against the source file'suse/namespacecontext. 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
PluginMetadatawithAnnotationRegistryandMethodInfo[]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()→AnnotationRegistrygetMethods()→array<string, MethodInfo>getMethodInfo(string $methodName)→MethodInfo|nullhasAnnotation(string $name)→ boolgetAnnotationMethodNames(string $name)→string[]
MethodInfo
File: src/Annotation/MethodInfo.php
Per-method parsed data:
getDescription()— Docblock descriptiongetParamsTypes()— Parameter types from reflectiongetReturnType()— Return typehasAnnotation(string $name)→ boolgetAnnotationValues(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).