Oihana PHP Arango

EdgesController extends Controller uses AuthorizationContextTrait, CapabilityContextTrait, ModelCallTrait, PermissionAuthorizerTrait

Generic controller for managing edge relationships between two vertex collections.

Provides post() and delete() methods to create and remove edges between a source vertex (from) and a target vertex (to).

Both source and target vertex IDs are read from the URL route placeholders:

  • {id} for the source vertex (Schema::ID)
  • {targetId} for the target vertex

POST also accepts an optional body for edge properties.

Like the other controllers, it carries an authorization seat: the model calls are wrapped by the ModelCallTrait hooks, so a consumer can refuse to link — or to unlink — documents its scope hides. Without that seat this was both a write surface reaching outside any scope, and an existence oracle: the three refusals (404 source, 404 target, 409 edge exists) told a caller what a scoped GET withholds.

One hook, three collections. Unlike the other controllers this one talks to three models — the source vertices, the target vertices, and the edges — and a predicate written for one is meaningless on the others. Each hook call therefore carries self::CALL, whose value is self::FROM, self::TO or self::EDGES, so an override knows which collection it is scoping:

protected function beforeModelCall( ?Request $request , array &$init ) : void
{
    if ( ( $init[ self::CALL ] ?? null ) === self::FROM )
    {
        $init[ Arango::CONDITIONS ] = [ ...( $init[ Arango::CONDITIONS ] ?? [] ) , 'doc.status == @scope' ] ;
        $init[ Arango::BINDS      ] = [ ...( $init[ Arango::BINDS      ] ?? [] ) , 'scope' => 'published' ] ;
    }

    parent::beforeModelCall( $request , $init ) ;
}

The creation itself is deliberately left unhooked. An INSERT has no FOR and no FILTER, so there is nothing to narrow — and worse, Edges::insertEdge() forwards its $init to the existEdge() uniqueness check: a scope posed there would blind the 409 and let a duplicate through. A creation is refused upstream, by the two vertex probes, which are hooked. The request-scoped authorizer is still posed on it, so the returned edge is projected under the same Field::REQUIRES gates as a read.

Tags
author

Marc Alcaraz

Table of Contents

Constants

CALL  : string = 'call'
The init key naming the collection a hook call is about — its value is one of {@see self::FROM}, {@see self::TO} or {@see self::EDGES}.
EDGES  : string = 'edges'
Initialization key for the Edges model dependency.
FROM  : string = 'from'
Initialization key for the source vertex Documents model.
TARGET_ID  : string = 'targetId'
URL placeholder name for the target vertex ID.
TO  : string = 'to'
Initialization key for the target vertex Documents model.

Properties

$edges  : Edges|null
The Edges model for the edge collection.
$from  : Documents|null
The Documents model for the source vertex collection.
$to  : Documents|null
The Documents model for the target vertex collection.

Methods

__construct()  : mixed
Creates a new EdgesController instance.
delete()  : mixed
Removes an edge between two vertices.
post()  : mixed
Creates a new edge between two vertices.
beforeModelCall()  : void
Injects the request-scoped permission authorizer into the model `$init` before every hooked model call.
initializeAuthorizationContext()  : static
Resolves the capability enforcer and the permission-subject resolver from the container (each guarded by an `instanceof`, null when absent) and wires them through `initializeCapabilities()` and `initializePermissionSubjectResolver()`.
authorized()  : array<string, mixed>
The caller init carrying the request-scoped authorizer — the projection gate, posed on the calls the consumer hook deliberately does not reach.
vertexInit()  : array<string, mixed>
Builds the init of a vertex existence probe and runs the hook on it.

Constants

CALL

The init key naming the collection a hook call is about — its value is one of {@see self::FROM}, {@see self::TO} or {@see self::EDGES}.

public string CALL = 'call'

EDGES

Initialization key for the Edges model dependency.

public string EDGES = 'edges'

FROM

Initialization key for the source vertex Documents model.

public string FROM = 'from'

TARGET_ID

URL placeholder name for the target vertex ID.

public string TARGET_ID = 'targetId'

TO

Initialization key for the target vertex Documents model.

public string TO = 'to'

Properties

Methods

__construct()

Creates a new EdgesController instance.

public __construct(Container $container[, array<string|int, mixed> $init = [] ]) : mixed
Parameters
$container : Container

The DI container reference.

$init : array<string|int, mixed> = []

Supports:

  • self::EDGES: Edges model service ID or instance
  • self::FROM: Documents model for the source vertex
  • self::TO: Documents model for the target vertex
Tags
throws
ContainerExceptionInterface
DependencyException
NotFoundException
NotFoundExceptionInterface
ReflectionException

delete()

Removes an edge between two vertices.

public delete([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed

Reads both vertex IDs from the route placeholders:

  • {id} for the source vertex
  • {targetId} for the target vertex
Parameters
$request : ServerRequestInterface|null = null

The PSR-7 request object.

$response : ResponseInterface|null = null

The PSR-7 response object.

$args : array<string|int, mixed> = []

Route placeholders (expects Schema::ID and self::TARGET_ID).

$init : array<string|int, mixed> = []

Optional settings.

Tags
throws
ContainerExceptionInterface
NotFoundExceptionInterface
Return values
mixed

200 on success, 404 if vertex or edge not found.

post()

Creates a new edge between two vertices.

public post([ServerRequestInterface|null $request = null ][, ResponseInterface|null $response = null ][, array<string|int, mixed> $args = [] ][, array<string|int, mixed> $init = [] ]) : mixed

Reads both vertex IDs from the route placeholders:

  • {id} for the source vertex
  • {targetId} for the target vertex

The request body is optional and can contain additional edge properties.

Parameters
$request : ServerRequestInterface|null = null

The PSR-7 request object.

$response : ResponseInterface|null = null

The PSR-7 response object.

$args : array<string|int, mixed> = []

Route placeholders (expects Schema::ID and self::TARGET_ID).

$init : array<string|int, mixed> = []

Optional settings.

Tags
throws
ContainerExceptionInterface
NotFoundExceptionInterface
Throwable
Return values
mixed

201 on success, 400 if missing data, 404 if vertex not found, 409 if edge exists.

beforeModelCall()

Injects the request-scoped permission authorizer into the model `$init` before every hooked model call.

protected beforeModelCall(ServerRequestInterface|null $request, array<string, mixed> &$init) : void

Overrides the no-op ModelCallTrait::beforeModelCall(). Strictly the behaviour of DocumentsController::beforeModelCall(), with the same two guards — an authorizer already in the init wins, and nothing is posed without an authorization stack or an authenticated user.

A subclass is what turns the seat into an actual scope; it should branch on self::CALL before appending anything, since the three hooked calls target three different collections.

Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

$init : array<string, mixed>

The init array forwarded to the model (by reference).

initializeAuthorizationContext()

Resolves the capability enforcer and the permission-subject resolver from the container (each guarded by an `instanceof`, null when absent) and wires them through `initializeCapabilities()` and `initializePermissionSubjectResolver()`.

protected initializeAuthorizationContext([array<string, mixed> $init = [] ]) : static
Parameters
$init : array<string, mixed> = []

Same array passed to the controller constructor.

Tags
throws
DependencyException
NotFoundException
Return values
static

authorized()

The caller init carrying the request-scoped authorizer — the projection gate, posed on the calls the consumer hook deliberately does not reach.

private authorized(ServerRequestInterface|null $request, array<string, mixed> $init) : array<string, mixed>

An authorizer already supplied by the caller wins, and nothing is posed when there is no request, no enforcer, no resolver or no authenticated user: the projection then falls open, exactly as before the seat existed.

Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

$init : array<string, mixed>

The caller init.

Return values
array<string, mixed>

vertexInit()

Builds the init of a vertex existence probe and runs the hook on it.

private vertexInit(ServerRequestInterface|null $request, array<string, mixed> $init, string $value, string $call) : array<string, mixed>
Parameters
$request : ServerRequestInterface|null

The current PSR-7 request.

$init : array<string, mixed>

The caller init (definition-level conditions travel here).

$value : string

The probed document key.

$call : string

self::FROM or self::TO.

Return values
array<string, mixed>

The enriched probe init.

On this page

Search results