MMOframework
Why AI Playground Libraries Documentation Search Agency
FR EN
Index

BoardController

MMO\admin\controllers · 1 symbol · generated 27 Oct 2026

class

BoardController

BoardController.php:43

The kanban boards a release follows: each one a <mmo-kanban>'s data, and its tickets' discussions, kept in a JSON file of its own, {folder}/{board}.json (KanbanBoard for the file). {MMO\admin}/templates/board.phtml shows one; a release checklist (ChecklistController) follows one, and labels its verified tickets.

class MMO\admin\controllers\BoardController extends MMO\core\classes\aDataController implements MMO\core\interfaces\iRoutable
Namespace
MMO\admin\controllers
Extends
aDataController
Implements
iRoutable
The page fetches `api/boards/{board}.json` and sends the whole board back on each change, with the `version` it was given: a board saved meanwhile from another tab answers 409, with the board as it is now, rather than losing what the other tab wrote. A ticket's comments are saved on their own routes, into the same file: they are not part of the board's data, which only counts them (`comments` on each ticket). A ticket's description may hold AI prompts: ```` ```claude ````, ```` ```chatgpt ````... blocks (mmo.client's `KanbanPrompts`). `api/boards/{board}/prompts.json` lists them, with their tickets, for an agent to treat them (`?agent=claude`, `&status=todo`, `&done=1` for a done ticket's too); its `markdown` is the same as a brief to read. The agent reports back on the ticket's discussion, and moves the ticket by saving the board. Declared like any controller, with where the files are (mmo.client is needed: the page and the prompts are its): "MMO\\admin\\controllers\\BoardController": {"folder": "{root}/boards", "scope": "admin"} A `folder` of null switches the boards off: every route answers 404 (an environment without them, production). > WARNING: whoever reaches these routes writes the boards. Give a [`$scope`](https://mmoframework.fr/en/documentation/mmo.admin/php/controllers/BoardController.md#boardcontroller-scope) on any application served beyond > localhost; [`$anonymous`](https://mmoframework.fr/en/documentation/mmo.admin/php/controllers/BoardController.md#boardcontroller-anonymous) opens them to whoever calls (mmo.oauth): only on a development machine, for the agents.
curl http://localhost/my.oviepro/api/boards/my.oviepro-deploy.json curl 'http://localhost/my.oviepro/api/boards/my.oviepro-deploy/prompts.json?agent=claude' curl -X PUT -H 'Content-Type: application/json' -d '{"version": 0, "data": {"tickets": []}}' http://localhost/my.oviepro/api/boards/acme.json

Index

METAwhat a board says of itself besides its data, kept at the top of its file
NAMEa board's name: what its file is called, nothing that leaves the folder
STATUSES
anonymous
author
default
folder
readonly
scope
syncUsers
writeScope
allows($scopes)
answer($route, $b)
canSync()
checklistOf($b)
checklistsFolder()
comment($c)
ConnectRoutes($routes, $config) static
createChecklist($name, $board, $title, $from, $refPrefix)Make a new board's checklist, following it, with the recipe of the checklist $from's follows: nothing when the application has no checklists (ChecklistController not declared) or the checklist ex…
defaultBoard()
edit($route, $action, $write)Run $action on the route's board, locked (the lock the release checklists take too): what it changes is written back when $write, unless it answers an error.
fail($route, $status, $error)
filename($board)
folder()
getAuthor()
guard($route, $write)
meta($b, $name)
moveRefusals($b, $board, $ticket, $role)What keeps a ticket from a column: À faire (ready) wants its prompt (a fenced block of an agent, mmo.client's KanbanPrompts); Recette (verify) a ``` `check ``` block (its description or i…
routeComment($route)
routeComments($route)
routeCreate($route)Make a new board and its checklist: the board's columns are STATUSES (or from's), its labels, categories and people from's; its checklist (checklist, the board's name by default) follows…
routeCurrent($route)The board the user works on, and its checklist: the one they picked (BoardSelection), else $default, else the developer layer's, else the first.
routeDeleteComment($route)
routeList($route)
routeLoad($route)
routeLoadPlan($route)
routeMove($route)Move a ticket the way its life goes, refused when it is not ready for that column (MoveRefusals): an agent moves tickets so -- the board's page drops them where the user wants.
routePlan($route)The plan of the tickets to do: which of the À faire tickets, grouped how, in what order, and what is left out.
routePrompts($route)
routeSave($route)
routeSetCurrent($route)Pick the board the user works on; the developer layer then files its tickets there.
routeSync($route)Sync the board with GitHub (mmo.git's BoardSync, the kanbanSync profile of the board): what changed on each side since the last sync is written on the other.
routeSyncInfo($route)Whether the board can be synced with GitHub from its page, by whom is asking.
routeSyncJob($route)Start the sync of the board with GitHub as a long job (mmo.git's BoardSyncJob, in its own process, with no time limit): the page follows it with <mmo-job> (mmo.core's JobController, which dec…
routeUpdateComment($route)
setStatus($route, $status)
syncJob()
syncProfile($board)
text($route)

Constants3

META

what a board says of itself besides its data, kept at the top of its file

const META = ['title', 'description', 'checklist']

NAME

a board's name: what its file is called, nothing that leaves the folder

const NAME = '/^[a-z0-9][a-z0-9._-]{0,99}$/i'

STATUSES

const STATUSES = […5 items]

Properties8

anonymous

bool $anonymous = false
Type
bool

folder

?string $folder = '{root}/boards'
Type
null | string

readonly

bool $readonly = false
Type
bool

syncUsers

array $syncUsers = []
Type
array

writeScope

?string $writeScope = null
Type
null | string

Methods38

allows($scopes)

BoardController.php:688
protected function allows(string $scopes): bool
$scopesstring
returnsboolwhether the user holds one of a|b (mmo.oauth); everybody, without it

answer($route, $b)

BoardController.php:649
protected function answer(MMO\core\server\Route $route, MMO\admin\helpers\KanbanBoard $b): array
$routeRoute
$bKanbanBoard
returnsarraythe board as the page wants it: its tickets saying how many comments they have

checklistOf($b)

BoardController.php:318
protected function checklistOf(MMO\admin\helpers\KanbanBoard $b): ?MMO\admin\helpers\Checklist
$bKanbanBoard
returnsnull | Checklistthe checklist the board names (checklist), null when it names none or it is not there

checklistsFolder()

BoardController.php:583
protected function checklistsFolder(): ?string
returnsnull | stringwhere the application keeps its checklists, null when it has none (ChecklistController not routed)

comment($c)

BoardController.php:660
protected function comment(array $c): array
$carray
returnsarraya comment as <mmo-kanban> takes it: the viewer may change their own, and every one on a local tool

ConnectRoutes($routes, $config)

staticBoardController.php:104
static function ConnectRoutes(array &$routes, array $config): void
See
$anonymous: every route is mmo.oauth's anonymous when the configuration says so
$routesarray
$configarray
returnsvoid

createChecklist($name, $board, $title, $from, $refPrefix)

BoardController.php:597

Make a new board's checklist, following it, with the recipe of the checklist $from's follows: nothing when the application has no checklists (ChecklistController not declared) or the checklist exists.

protected function createChecklist( string $name, string $board, string $title, ?string $from, mixed $refPrefix ): ?string
$namestringthe checklist
$boardstringthe board it follows
$titlestring
$fromnull | stringthe board whose checklist is copied
$refPrefixmixedwhat its refs start with
returnsnull | string$name when it was made

edit($route, $action, $write)

BoardController.php:627

Run $action on the route's board, locked (the lock the release checklists take too): what it changes is written back when $write, unless it answers an error.

protected function edit(MMO\core\server\Route $route, callable $action, bool $write = false): array
$routeRoute
$actioncallable
$write = falsebool
returnsarraywhat $action answers

guard($route, $write)

BoardController.php:673
protected function guard(MMO\core\server\Route $route, bool $write = false): ?array
$routeRoute
$write = falsebool
returnsnull | arraythe answer refusing the request: no boards here, or a client without $scope; null when it may

moveRefusals($b, $board, $ticket, $role)

BoardController.php:297

What keeps a ticket from a column: À faire (ready) wants its prompt (a fenced block of an agent, mmo.client's KanbanPrompts); Recette (verify) a ``` `check `` block (its description or its discussion) and, when the board names its checklist, items of it naming the ticket; Terminé (done`) every one of those items checked.

protected function moveRefusals( MMO\admin\helpers\KanbanBoard $b, string $board, string $ticket, string $role ): array
$bKanbanBoard
$boardstring
$ticketstring
$rolestringthe column's role (KanbanBoard::roles), '' for none: anything goes there
returnsarraywhy it may not go, empty when it may

routeComment($route)

BoardController.php:497
#[MMO\core\attributes\APIRoute('POST|api/boards/{board}/{ticket}/comments.json', 'Comment on a ticket: {body, parent}', mediaTypes: ['json'])] function routeComment(MMO\core\server\Route $route): array
$routeRoutea JSON body {body, parent}: markdown, and the id of the comment it answers (null for none)
returnsarray{comment}: the new comment

routeComments($route)

BoardController.php:488
#[MMO\core\attributes\APIRoute('GET|api/boards/{board}/{ticket}/comments.json', 'A ticket\'s discussion', mediaTypes: ['json'])] function routeComments(MMO\core\server\Route $route): array
$routeRoute
returnsarray{comments}: the ticket's comments, oldest first

routeCreate($route)

BoardController.php:194

Make a new board and its checklist: the board's columns are STATUSES (or from's), its labels, categories and people from's; its checklist (checklist, the board's name by default) follows it, with the recipe of from's checklist, when the application declares ChecklistController and the checklist does not exist yet.

#[MMO\core\attributes\APIRoute('POST|api/boards/{board}/create.json', 'Make a board and its checklist: {title, description, checklist, from, refPrefix}', mediaTypes: ['json'])] function routeCreate(MMO\core\server\Route $route): array
$routeRoutea JSON body {title, description, checklist, from, refPrefix}, every key optional: from a board to copy, refPrefix what the checklist's refs start with
returnsarrayas routeLoad, and createdChecklist: the checklist made, null when none was; 409 when the board exists
curl -X POST -H 'Content-Type: application/json' -d '{"title": "Facturation", "from": "acme-deploy"}' http://localhost/acme/api/boards/acme-billing/create.json

routeCurrent($route)

BoardController.php:225

The board the user works on, and its checklist: the one they picked (BoardSelection), else $default, else the developer layer's, else the first.

#[MMO\core\attributes\APIRoute('GET|api/current-board.json', 'The board the user works on, and its checklist', mediaTypes: ['json'])] function routeCurrent(MMO\core\server\Route $route): array
$routeRoute
returnsarray{board, title, checklist, picked}: picked whether the user chose it; board null when there is none

routeDeleteComment($route)

BoardController.php:532
#[MMO\core\attributes\APIRoute('DELETE|api/boards/{board}/{ticket}/comments/{comment}.json', 'Delete a comment', mediaTypes: ['json'])] function routeDeleteComment(MMO\core\server\Route $route): array
$routeRoute
returnsarray{}

routeList($route)

BoardController.php:117
#[MMO\core\attributes\APIRoute('GET|api/boards.json', 'The kanban boards', mediaTypes: ['json'])] function routeList(MMO\core\server\Route $route): array
$routeRoute
returnsarray{boards: [{name, title, description, checklist, version, updatedAt, tickets, columns}]}, by name: checklist the one the board's checks go to, columns how many tickets each status holds

routeLoad($route)

BoardController.php:143
#[MMO\core\attributes\APIRoute('GET|api/boards/{board}.json', 'A board\'s data', mediaTypes: ['json'])] function routeLoad(MMO\core\server\Route $route): array
$routeRoute
returnsarray{board, title, version, updatedAt, data}: an unknown board is an empty one, version 0

routeLoadPlan($route)

BoardController.php:373
#[MMO\core\attributes\APIRoute('GET|api/boards/{board}/plan.json', 'The board\'s last plan of the tickets to do', mediaTypes: ['json'])] function routeLoadPlan(MMO\core\server\Route $route): array
$routeRoute
returnsarraythe board's last plan (routePlan), {plan: null} when it has none

routeMove($route)

BoardController.php:262

Move a ticket the way its life goes, refused when it is not ready for that column (MoveRefusals): an agent moves tickets so -- the board's page drops them where the user wants.

#[MMO\core\attributes\APIRoute('POST|api/boards/{board}/{ticket}/move.json', 'Move a ticket, when it is ready for that column: {status, comment}', mediaTypes: ['json'])] function routeMove(MMO\core\server\Route $route): array
$routeRoutea JSON body {status, comment}: the column (its id, or a role: ready, verify...), and a comment saying why, added to the ticket's discussion
returnsarray{ticket, from, to, role}; 409 with reasons when the ticket may not go there
curl -X POST -H 'Content-Type: application/json' -d '{"status": "ready"}' http://localhost/acme/api/boards/acme-deploy/12/move.json

routePlan($route)

BoardController.php:333

The plan of the tickets to do: which of the À faire tickets, grouped how, in what order, and what is left out. Kept in the board (plan, the last one), shown on its page; each ticket planned gets a comment saying its group and rank.

#[MMO\core\attributes\APIRoute('PUT|api/boards/{board}/plan.json', 'Plan the tickets to do: {title, groups: [{title, tickets, note}], left: [{ticket, why}], note}', mediaTypes: ['json'])] function routePlan(MMO\core\server\Route $route): array
$routeRoutea JSON body {title, groups: [{title, tickets, note}], left: [{ticket, why}], note}: the tickets in the order they are done, every one of them in the board's À faire column
returnsarraythe plan as kept: {title, groups, left, note, at, by}; 400 naming the tickets that are not to do
curl -X PUT -H 'Content-Type: application/json' -d '{"groups": [{"title": "PDF", "tickets": ["12", "15"]}]}' http://localhost/acme/api/boards/acme-deploy/plan.json

routePrompts($route)

BoardController.php:382
#[MMO\core\attributes\APIRoute('GET|api/boards/{board}/prompts.json', 'The AI prompts of a board\'s tickets: ?agent=claude&status=todo&done=1', mediaTypes: ['json'])] function routePrompts(MMO\core\server\Route $route): array
$routeRouteits query: agent, status, done (mmo.client's KanbanPrompts::Answer)
returnsarray{board, agents, prompts: [{agent, info, prompt, line, ticket}], markdown}

routeSave($route)

BoardController.php:154
#[MMO\core\attributes\APIRoute('PUT|api/boards/{board}.json', 'Save a board\'s data: {version, data, title, description, checklist}', mediaTypes: ['json'])] function routeSave(MMO\core\server\Route $route): array
$routeRoutea JSON body {version, data}: the version the page was given, and the board's data now; title, description, checklist change what the board says of itself (META), null forgets one; roles the column of each step of a ticket's life (KanbanBoard::roles)
returnsarrayas routeLoad; 409 with the board as it is when it was saved meanwhile

routeSetCurrent($route)

BoardController.php:239

Pick the board the user works on; the developer layer then files its tickets there.

#[MMO\core\attributes\APIRoute('PUT|api/current-board.json', 'Pick the board the user works on: {board}', mediaTypes: ['json'])] function routeSetCurrent(MMO\core\server\Route $route): array
$routeRoutea JSON body {board}: an existing board, or null to forget the choice
returnsarrayas routeCurrent

routeSync($route)

BoardController.php:450

Sync the board with GitHub (mmo.git's BoardSync, the kanbanSync profile of the board): what changed on each side since the last sync is written on the other. {dryRun: true} only says what would be.

#[MMO\core\attributes\APIRoute('POST|api/boards/{board}/sync.json', 'Sync the board with GitHub: {dryRun}', mediaTypes: ['json'])] function routeSync(MMO\core\server\Route $route): array
$routeRouteits body: {dryRun}
returnsarray{report, board}: the sync's report (BoardSync::run) and the board as it is now

routeSyncInfo($route)

BoardController.php:396

Whether the board can be synced with GitHub from its page, by whom is asking.

#[MMO\core\attributes\APIRoute('GET|api/boards/{board}/sync.json', 'Whether the board can be synced with GitHub, and when it last was', mediaTypes: ['json'])] function routeSyncInfo(MMO\core\server\Route $route): array
$routeRoute
returnsarray{available, profile, syncedAt, job}: available when a kanbanSync profile syncs the board and the user is one of $syncUsers; syncedAt the end of the last sync, from its state file; job the name mmo.core's JobController gives mmo.git's BoardSyncJob (routeSyncJob), null when it does not declare it

routeSyncJob($route)

BoardController.php:414

Start the sync of the board with GitHub as a long job (mmo.git's BoardSyncJob, in its own process, with no time limit): the page follows it with <mmo-job> (mmo.core's JobController, which declares it start: false: only this route, which checks $syncUsers, starts it). {dryRun: true} only says what would be.

#[MMO\core\attributes\APIRoute('POST|api/boards/{board}/sync/job.json', 'Start the sync of the board with GitHub as a long job: {dryRun}', mediaTypes: ['json'])] function routeSyncJob(MMO\core\server\Route $route): array
$routeRouteits body: {dryRun}
returnsarray{job}: the job's record (its id to follow); its result, the sync's report

routeUpdateComment($route)

BoardController.php:513
#[MMO\core\attributes\APIRoute('PUT|api/boards/{board}/{ticket}/comments/{comment}.json', 'Change a comment: {body}', mediaTypes: ['json'])] function routeUpdateComment(MMO\core\server\Route $route): array
$routeRoutea JSON body {body}
returnsarray{comment}: the comment as it is now

syncJob()

BoardController.php:433
protected function syncJob(): ?string
returnsnull | stringthe name mmo.core's JobController declares mmo.git's BoardSyncJob under, null when none

syncProfile($board)

BoardController.php:476
protected function syncProfile(string $board): ?string
$boardstring
returnsnull | stringthe kanbanSync profile syncing the board, null when there is none (or no mmo.git)

text($route)

BoardController.php:666
protected function text(MMO\core\server\Route $route): ?string
$routeRoute
returnsnull | stringthe request's body: the markdown sent, null when there is none
Inherited members (62)
ConstantsDeclared byDescription
ALERTiLogLevelAlert: Action must be taken immediately.
ALLiLogLevelMask for all
ALL_PLUSiLogLeveldebug everything
BACKTRACEiLogLevel
COLORSaDataControllerNamed color palettes for chart/report endpoints, e.g.
CRITICALiLogLevelCritical: Critical conditions.
DEBUGiLogLevelDebug: Detailed debug information.
DEEPiLogLeveldebug everything with arguments
EMERGENCYiLogLevelEmergency: System is unusable.
ERRORiLogLevelError: Runtime errors that do not require immediate action but should typically be logged and monitored.
ERROR_PLUSiLogLevelMask for all error
FATALiLogLevelMask for all fatal error
INFOiLogLevelInfo: Interesting events.
INFO_PLUSiLogLevelMask for all errors/warning/notices/info
MEMORYiLogLevelinclude backtrace object
NOTICEiLogLevelNotice: Normal but significant events.
NOTICE_PLUSiLogLevelMask for all errors/warning/notices/info
TRACEiLogLevelinclude backtrace as origins
WARNINGiLogLevelWarning: Exceptional occurrences that are not errors.
WARNING_PLUSiLogLevelMask for all errors and warnings
PropertiesDeclared byDescription
repositoriesaDataController
verbosityaModule
MethodsDeclared byDescription
alert($message, $context)aModuleAction must be taken immediately.
backtraces($message, $context)aModuleForce-log a message at INFO level together with a full stack trace/memory snapshot (iLogLevel::BACKTRACE), bypassing the verbosity mask entirely (any non-zero verbosity is enough to trigger it).
Configure($config)aModuleMerge $config into this module's configuration in the shared Configuration.
critical($message, $context)aModuleCritical conditions. No-op if CRITICAL is currently filtered out by the verbosity mask.
debug($message, $context)aModuleDetailed debug information.
debugName()aModuleHuman-readable identifier used as the default {origin} in this instance's log messages.
DefaultConfig()aModuleThe config a module starts from before anything loaded via Configuration is merged in.
emergency($message, $context)aModuleSystem is unusable. No-op if EMERGENCY is currently filtered out by the verbosity mask.
error($message, $context)aModuleRuntime errors that do not require immediate action but should typically be logged and monitored.
ExtractRoutes($config)aControllerExtract all the APIRoute from this class, regardless of the fact they are registered in the application
getMainRepository()aDataController
getRepositories()aDataController
getRepository($repo)aDataController
getVerbosity()aModule
HasRoutes()aControllerTells if any APIRoute are registered in the application
info($message, $context)aModuleInteresting events. No-op if INFO is currently filtered out by the verbosity mask.
Instantiate($config)aModule(Re)create the shared instance immediately, configured with $config merged into the class's existing config.
IsShared()aModule
Link($method, $data)aControllerBuild a Link (URL + label/icon/description/title, taken from the route's APIRoute metadata) for a registered route.
log($level, $message, $context)aModuleLogs with an arbitrary level.
notice($message, $context)aModuleNormal but significant events.
offsetExists($key)aModule\ArrayAccess::offsetExists -- tells whether $key is a declared property at all, regardless of whether it is currently readable/writable through array access.
offsetGet($key)aModule\ArrayAccess::offsetGet -- reads a property directly, unless it has a #[Serialize(serialize: 'methodName')] attribute, in which case that zero-argument method is called instead (e.g.
offsetSet($key, $value)aModule\ArrayAccess::offsetSet -- sets a property directly, unless it has a #[Serialize(unserialize: 'methodName')] attribute, in which case that method is called with $value instead (e.g.
offsetUnset($key)aModule\ArrayAccess::offsetUnset -- equivalent to $module[$key] = null.
onRoute($route)aDataControllerSame as aController::onRoute, plus forcing the response to JSON (Response::Json) for every route this controller claims.
Route($method)aControllerGet a registered APIRoute WARNING: if this class was not registered in the application, it will return null.
Routes()aControllerGet a list of registered APIRoutes (from this class) in the application WARNING: if this class was not registered in the application, it will return an empty array.
serialize($path, $serializer)aModule\MMO\core\interfaces\iSerializable::serialize -- exports every property (via
setRepositories($repositories)aDataControllerRegister the repositories this controller can query, keyed by name (e.g.
setVerbosity($verb)aModuleSet the current verbosity mask, replacing whatever it was before.
Shared()aModuleGet the shared instance, creating (and configuring) it on first call.
testVerbosity($level)aModuleTest whether a given level (or set of levels) is currently enabled by this instance's verbosity mask.
traitUnshare($serialize)aModuleRemove the shared instance, so the next Shared() call creates a fresh one.
unserialize($data, $path, $instances)aModule\MMO\core\interfaces\iSerializable::unserialize -- merges $data onto DefaultConfig, runs it through the (possibly overridden) constructor, then applies each resulting value to its property (via
Unshare($serialize)aModuleLike tSingleton::Unshare, but also disconnects this module's instance from every event it was listening to (via Event::DisconnectListeners) before dropping it.
Url($method, $data)aControllerBuild the URL for a registered route, filling in any {placeholder} from $data.
warning($message, $context)aModuleExceptional occurrences that are not errors.
__construct($config)aModuleprotected constructor, so it is not possible to do new Module(...) called by the serialization process just before being injected with data.
__wakeup()aControllerCalled automatically right after the controller's shared instance is built (aModule::unserialize): registers onRoute as a Route listener with a low priority (-10), so it…