Index

ThemeConfigurator

MMO\client · 1 symbol · generated 5 Oct 2026

class

ThemeConfigurator

ThemeConfigurator.php:64

Reads the theme's token layer, lets an application change any value in it, and writes the result back as Simple CSS.

class MMO\client\ThemeConfigurator extends MMO\core\classes\aModule
Namespace
MMO\client
Extends
aModule
See
apply writes the json, writes the .scs, then asks Theme to recompile the stylesheet. In dev mode the target is also rebuilt whenever a source file becomes more recent than it (isUpToDate), so a token added to the library is never hidden by a stale generated copy.
### What it configures The theme is `.scs`, so "what can be customised" is not a hand written list: it is every value the token sources declare. The configurator parses them ([`$sources`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-sources) -- `tokens.scs` and `dark.scs` by default) and exposes, as one flat model: | in the source | what the model calls it | editable | |----------------------------------------|--------------------------------------|----------| | `@let $brand = (s500: #2F62F0, ...)` | `tokens:let::brand.s500` | yes | | `@let $ink = #0E1730` | `tokens:let::ink` | yes | | `@theme { color {bg: $palette.n50} }` | `tokens:theme::color.bg` | yes | | `@theme dark { ... }` | `tokens:theme:dark:color.bg` | yes | | `--mmo-shadow-sm` inside an `@style` | `dark:style:dark-tokens:--mmo-shadow-sm` | yes | | `@config varPrefix = "mmo-"` | `tokens:config::varPrefix` | **no** | `@config` is shown but locked: it is a compiler setting, not a theme value -- renaming the custom property prefix would leave every compiled element referencing `var(--mmo-*)` while the document declares something else, and nothing on the page would have a colour any more. ### How a change is applied An override is stored as the *expression* the user wrote, against the entry's key, in [`$config`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-config) (json). From there [`generate`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-generate) parses each source, substitutes the overridden expressions into the ast and prints the whole thing back ([`Printer`](https://mcp.mmoframework.fr/documentation/mmo.client/php/scs/Printer.md#printer)) into [`$target`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-target) -- a single `.scs` file the application's stylesheet imports **last**: ```css Printing the sources rather than emitting a list of literals is what keeps the derivations intact: a token written `bg: $palette.n50` stays that expression, so changing `$palette.n50` moves every token derived from it. Only the entries someone actually changed become literals. ### Regenerating
$configurator = ThemeConfigurator::Shared(); $configurator->apply(['tokens:let::brand.s500' => '#8B5CF6']); foreach ($configurator->getModel()['sections'] as $section) {...}

Index

MAX_LENGTH
config
enabled
labels
model
overrides
sources
target
varPrefix
apply($overrides)Store a new configuration, regenerate the token layer, and recompile the application's css.
build()Write $target from the stored overrides, without touching the configuration.
DefaultConfig() static
Dig($values, $path) static
Filename($file) static
generate($overrides)The generated token layer: every source, printed back with the overrides substituted into its ast.
getConfigFile($resolve)
getModel($reload)The whole customizable surface, read from the sources.
getOverrides()
getResolvedValues($overrides)What every entry would really be worth under a candidate configuration.
getSources()
getTarget($resolve)
isEnabled()
isUpToDate()
MapOf($node) static
preview($overrides)Compile a candidate token layer without writing anything.
printExpression($key, $entry, $value)Parse one submitted value the way the source parses the entry it replaces, and print it back.
readStatement($statement, $namespace, $printer)The entries one top-level statement declares.
reject($key, $node)Refuse the two things a *value* must never reach for, whatever an application authorized for its own stylesheets: the php bridge, and the compilation context.
replacement($key, $kind, $overrides)Parse one stored override back into the ast node that replaces the source's own.
reset()Drop every override and rebuild: the theme goes back to what the sources declare.
resolve()Fill in what only a compilation knows: the value each token really ends up with once the expressions are evaluated.
resolveWith($entries, $source)
Short($file) static
substitute($statement, $namespace, $overrides)Replace, in place, the value nodes of a statement whose entry is overridden.
TypeOf($entry) staticWhat kind of control an entry deserves: this is a hint for the interface, never something the compiler reads -- a value the model calls a colour is still just an expression.
validate($overrides)Validate a set of overrides against the model.
ValueOf($statement) staticThe single value node a synthetic snippet declared.
variableName($path)
__wakeup()

Constants1

MAX_LENGTH

const MAX_LENGTH = 512

Properties8

config

protected string $config
Type
string

enabled

protected bool $enabled = false
Type
bool
It is off by default because turning it on is a decision about the *stylesheet*: the generated token layer only reaches the page if the application's own stylesheet imports it ([`$target`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-target)), and [`Theme`](https://mcp.mmoframework.fr/documentation/mmo.client/php/Theme.md#theme) keeps that file current only for an application that says it wants one.

labels

protected array $labels = […17 items]
Type
array

model

protected ?array $model = null
Type
null | array

overrides

protected ?array $overrides = null
Type
null | array

sources

protected array $sources
Type
array

target

protected string $target
Type
string

varPrefix

protected string $varPrefix = ''
Type
string

Methods30

apply($overrides)

ThemeConfigurator.php:783

Store a new configuration, regenerate the token layer, and recompile the application's css.

function apply(array $overrides): arrayNothing is kept if the result does not compile: the previous configuration and the previous generated file are put back, so a theme that cannot be built never replaces one that could.
$overridesarrayentry key => expression, an empty value dropping the override
returnsarray['overrides' => [...], 'css' => the compiled stylesheet, 'generated' => the .scs written]
throwswhen the configurator is disabled
throwswhen an override is not valid
throwswhen the theme, or the application's stylesheet, does not compile

build()

ThemeConfigurator.php:766

Write $target from the stored overrides, without touching the configuration.

function build(): voidThis is the repair path: it is what the dev-mode job runs when a source file has changed under a generated copy, and what creates the file the first time -- an application whose stylesheet imports a target that does not exist yet does not compile at all.
returnsvoid

Dig($values, $path)

staticThemeConfigurator.php:540
protected static function Dig(array $values, array $path): ?MMO\client\scs\Value
$valuesarraythe compile-time variables of a compilation
$patharray['brand', 's500']
returnsnull | Value

Filename($file)

staticThemeConfigurator.php:155
protected static function Filename(string $file): string
$filestringa configured path
returnsstringthe file, with its placeholders resolved and its .. folded away -- these paths are read back by a human, in the generated file's header and in every error this module reports

generate($overrides)

ThemeConfigurator.php:613

The generated token layer: every source, printed back with the overrides substituted into its ast.

function generate(?array $overrides = null): string
$overrides = nullnull | arraythe overrides to apply, defaulting to the stored ones
returnsstringthe .scs source, ready to be written into $target
throwswhen a source is missing

getModel($reload)

ThemeConfigurator.php:335

The whole customizable surface, read from the sources.

function getModel(bool $reload = false): array
$reload = falsebooltrue to parse the sources again rather than reuse what this request already read
returnsarray['sources' => [...], 'sections' => [...], 'entries' => [key => entry]], where a section is ['key', 'label', 'source', 'kind', 'entries' => [key, ...]] and an entry is ['key', 'label', 'name', 'kind', 'section', 'source', 'base', 'value', 'resolved', 'type', 'variable', 'overridden', 'editable']

getOverrides()

ThemeConfigurator.php:181
function getOverrides(): array
returnsarraythe stored overrides: entry key => the expression that replaces the source's own

getResolvedValues($overrides)

ThemeConfigurator.php:489

What every entry would really be worth under a candidate configuration.

function getResolvedValues(array $overrides): arrayAn expression says nothing on its own -- `$brand.s500` is a colour only once the maps are evaluated, and moving a map moves every token derived from it. This is how an interface shows the *result* of a change it has not saved yet.
$overridesarray
returnsarrayentry key => the value the compiler produced for it
throwswhen an override is not valid

MapOf($node)

staticThemeConfigurator.php:554
protected static function MapOf(MMO\client\scs\nodes\Node $node): ?MMO\client\scs\nodes\MapNode
$nodeNode
returnsnull | MapNodethe map a @let declares, null when it declares anything else

preview($overrides)

ThemeConfigurator.php:747

Compile a candidate token layer without writing anything.

function preview(array $overrides): arrayThis is the preview: the generated file *is* the token layer, so compiling it alone produces exactly the `:root` rule and the dark theme's rules, and nothing else -- a stylesheet a page can append to its head to see the theme it is about to save.
$overridesarray
returnsarray['css' => the token layer's css, 'resolved' => [entry key => value]]
throwswhen an override does not parse
throwswhen the resulting token layer does not compile

printExpression($key, $entry, $value)

ThemeConfigurator.php:248

Parse one submitted value the way the source parses the entry it replaces, and print it back.

protected function printExpression(string $key, array $entry, string $value): string
$keystring
$entryarraythe model entry being overridden
$valuestringthe submitted expression
returnsstringthe expression, as this package would have written it
throwswhen it does not parse, or reaches outside what a value may name

readStatement($statement, $namespace, $printer)

ThemeConfigurator.php:396

The entries one top-level statement declares.

protected function readStatement( MMO\client\scs\nodes\Node $statement, string $namespace, MMO\client\scs\Printer $printer ): Generator
$statementNode
$namespacestringthe source's namespace, which is its own file name
$printerPrinter
returnsGenerator

reject($key, $node)

ThemeConfigurator.php:302

Refuse the two things a *value* must never reach for, whatever an application authorized for its own stylesheets: the php bridge, and the compilation context. Both are for code the application wrote.

protected function reject(string $key, MMO\client\scs\nodes\Node $node): void
$keystring
$nodeNode
returnsvoid
throws

replacement($key, $kind, $overrides)

ThemeConfigurator.php:711

Parse one stored override back into the ast node that replaces the source's own.

protected function replacement(string $key, string $kind, array $overrides): ?MMO\client\scs\nodes\NodeThe stored text was printed from an ast that [`validate`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-validate) had already checked, so this re-parses this package's own output -- but it is parsed in the entry's context all the same, because that is what decides whether `16px -4px` is two lengths or a subtraction.
$keystring
$kindstring
$overridesarray
returnsnull | Nodenull when this entry carries no override
throwswhen the stored value no longer parses (a hand edited json)

resolve()

ThemeConfigurator.php:473

Fill in what only a compilation knows: the value each token really ends up with once the expressions are evaluated. A failed compilation is not fatal here -- the model still describes the sources, it simply shows the expressions instead of the values they produce.

protected function resolve(): void
returnsvoid

resolveWith($entries, $source)

ThemeConfigurator.php:498
protected function resolveWith(array $entries, string $source): array
$entriesarraythe model's entries, which say what to look for
$sourcestringa generated token layer
returnsarrayentry key => resolved value, empty when the source does not compile

Short($file)

staticThemeConfigurator.php:530
protected static function Short(string $file): string
$filestring
returnsstringthe file, relative to the application's base path when it lives under it

substitute($statement, $namespace, $overrides)

ThemeConfigurator.php:661

Replace, in place, the value nodes of a statement whose entry is overridden.

protected function substitute( MMO\client\scs\nodes\Node $statement, string $namespace, array $overrides ): void
$statementNodea statement of a freshly parsed source -- never a shared ast
$namespacestring
$overridesarray
returnsvoid

TypeOf($entry)

staticThemeConfigurator.php:570

What kind of control an entry deserves: this is a hint for the interface, never something the compiler reads -- a value the model calls a colour is still just an expression.

protected static function TypeOf(array $entry): string
$entryarray
returnsstringcolor, length, number, duration, easing, font, shadow or text

validate($overrides)

ThemeConfigurator.php:210

Validate a set of overrides against the model.

function validate(array $overrides): arrayEvery value is re-parsed *in the context of the entry it replaces* -- a theme token as a theme token, a map entry as a map entry -- and then printed back from the ast. What is stored is that printed form, never the text the request carried: an expression that cannot be parsed is refused, and one that can cannot carry anything the parser did not put in the tree (no stray brace closing the `@theme` block, no second declaration smuggled after a `;`).
$overridesarrayentry key => expression, an empty value dropping the override
returnsarraythe sanitized overrides
throwson an unknown key, a locked entry, or an expression that does not parse

ValueOf($statement)

staticThemeConfigurator.php:285

The single value node a synthetic snippet declared.

protected static function ValueOf(?MMO\client\scs\nodes\Node $statement): ?MMO\client\scs\nodes\NodeThe snippet is written so that the submitted text is the only value in it. Anything the parser managed to read *beyond* that value -- a second token because the text carried a `;` or a newline, a second declaration, a rule -- means the text was not one value, and the whole override is refused rather than silently truncated to its first term.
$statementnull | Nodethe only statement of the parsed snippet
returnsnull | Nodethe value node, null when the snippet declared anything else than exactly one value

variableName($path)

ThemeConfigurator.php:600
protected function variableName(string $path): stringThe prefix is the one *these sources* compile with, not the compiler's own default: `tokens.scs` sets it with `@config varPrefix = "mmo-"`, and that is an entry of this very model ([`readStatement`](https://mcp.mmoframework.fr/documentation/mmo.client/php/ThemeConfigurator.md#themeconfigurator-readstatement)), so the name shown next to a token is the name the page really carries.
$pathstringa token's dotted path
returnsstringthe css custom property it is emitted as
Inherited members (47)
ConstantsDeclared byDescription
ALERTiLogLevelAlert: Action must be taken immediately.
ALLiLogLevelMask for all
ALL_PLUSiLogLeveldebug everything
BACKTRACEiLogLevel
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
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.
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.
getVerbosity()aModule
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
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.
serialize($path, $serializer)aModule\MMO\core\interfaces\iSerializable::serialize -- exports every property (via
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.
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.