nouveau-theatre-de-besancon/kirby/src/Cms/ModelCommit.php

243 lines
5.6 KiB
PHP

<?php
namespace Kirby\Cms;
use Closure;
use Kirby\Exception\Exception;
/**
* The ModelCommit class is used to commit a given model action
* in the model action classes. It takes care of running
* the `before` and `after` hooks and updating the state
* of the given model.
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
class ModelCommit
{
protected App $kirby;
protected string $prefix;
public function __construct(
protected ModelWithContent $model,
protected string $action
) {
$this->kirby = $this->model->kirby();
$this->prefix = $this->model::CLASS_ALIAS;
}
/**
* Runs the `after` hook and returns the result.
*/
public function after(mixed $state): mixed
{
// run the `after` hook
$arguments = $this->afterHookArguments($state);
$hook = $this->hook('after', $arguments);
// flush the page cache after any model action
$this->kirby->cache('pages')->flush();
return $hook['result'];
}
/**
* Returns the appropriate arguments for the `after` hook
* for the given model action. It's a wrapper around the
* more specific `afterHookArgumentsFor*Actions` methods.
*/
public function afterHookArguments(mixed $state): array
{
return match (true) {
$this->model instanceof File =>
$this->afterHookArgumentsForFileActions($this->model, $this->action, $state),
$this->model instanceof Page =>
$this->afterHookArgumentsForPageActions($this->model, $this->action, $state),
$this->model instanceof Site =>
$this->afterHookArgumentsForSiteActions($this->model, $state),
$this->model instanceof User =>
$this->afterHookArgumentsForUserActions($this->model, $this->action, $state),
default =>
throw new Exception('Invalid model class')
};
}
/**
* Returns the appropriate arguments for the `after` hook
* for the given page action.
*/
protected function afterHookArgumentsForPageActions(
Page $model,
string $action,
mixed $state
): array {
return match ($action) {
'create' => [
'page' => $state
],
'duplicate' => [
'duplicatePage' => $state,
'originalPage' => $model
],
'delete' => [
'status' => $state,
'page' => $model
],
default => [
'newPage' => $state,
'oldPage' => $model
]
};
}
/**
* Returns the appropriate arguments for the `after` hook
* for the given file action.
*/
protected function afterHookArgumentsForFileActions(
File $model,
string $action,
mixed $state
): array {
return match ($action) {
'create' => [
'file' => $state
],
'delete' => [
'status' => $state,
'file' => $model
],
default => [
'newFile' => $state,
'oldFile' => $model
]
};
}
/**
* Returns the appropriate arguments for the `after` hook
* for the given site action.
*/
protected function afterHookArgumentsForSiteActions(
Site $model,
mixed $state
): array {
return [
'newSite' => $state,
'oldSite' => $model
];
}
/**
* Returns the appropriate arguments for the `after` hook
* for the given user action.
*/
protected function afterHookArgumentsForUserActions(
User $model,
string $action,
mixed $state
): array {
return match ($action) {
'create' => [
'user' => $state
],
'delete' => [
'status' => $state,
'user' => $model
],
default => [
'newUser' => $state,
'oldUser' => $model
]
};
}
/**
* Runs the `before` hook and modifies the arguments
*/
public function before(array $arguments): array
{
// check model rules
$this->validate($arguments);
// run the `before` hook
$hook = $this->hook('before', $arguments);
// check model rules again, after the hook got applied
$this->validate($hook['arguments']);
return $hook['arguments'];
}
/**
* Handles the full call of the given action,
* runs the `before` and `after` hooks and updates
* the state of the given model.
*/
public function call(array $arguments, Closure $callback): mixed
{
// run the before hook
$arguments = $this->before($arguments);
// run the commit action
$state = $callback(...array_values($arguments));
// update the state for the after hook
ModelState::update(
method: $this->action,
current: $this->model,
next: $state
);
// run the after hook and return the result
return $this->after($state);
}
/**
* Runs the given hook and modifies the first argument
* of the given arguments array. It returns an array with
* `arguments` and `result` keys.
*/
public function hook(string $hook, array $arguments): array
{
// the very first argument (which should be the model)
// is modified by the return value from the hook (if any returned)
$appliedTo = array_key_first($arguments);
// run the hook and modify the first argument
$arguments[$appliedTo] = $this->kirby->apply(
// e.g. page.create:before
$this->prefix . '.' . $this->action . ':' . $hook,
$arguments,
$appliedTo
);
return [
'arguments' => $arguments,
'result' => $arguments[$appliedTo],
];
}
/**
* Checks the model rules for the given action
* if there's a matching rule method.
*/
public function validate(array $arguments): void
{
$rules = match (true) {
$this->model instanceof File => FileRules::class,
$this->model instanceof Page => PageRules::class,
$this->model instanceof Site => SiteRules::class,
$this->model instanceof User => UserRules::class,
default => throw new Exception('Invalid model class') // @codeCoverageIgnore
};
if (method_exists($rules, $this->action) === true) {
$rules::{$this->action}(...array_values($arguments));
}
}
}