didPrepare()) { $c->beforePrepare(); $c->prepare(); if ($afterPrepare) $c->afterPrepare(); return true; } return false; } protected static function ensure_configc(IComponent $c, array &$config, bool $afterConfig=false): bool { if (!$c->didConfig()) { $c->beforeConfig($config); $c->config($config); if ($afterConfig) $c->afterConfig(); return true; } return false; } protected static function ensure_setupc(IComponent $c, bool $afterSetup=false): bool { if (!$c->didSetup()) { $c->beforeSetup(); $c->setup(); if ($afterSetup) $c->afterSetup(); return true; } return false; } protected static function ensure_teardownc(IComponent $c, bool $afterTeardown=false): bool { if (!$c->didTeardown()) { $c->beforeTeardown(); $c->teardown(); if ($afterTeardown) $c->afterTeardown(); return true; } return false; } protected static function printc(IComponent $c, ?array &$config): void { self::ensure_preparec($c, true); self::ensure_configc($c, $config, true); self::ensure_setupc($c, true); try { if ($c->haveContent()) vo::write($c); } finally { self::ensure_teardownc($c, true); } } ############################################################################# /** configurer les classes utilisées dans le cadre de ce container */ protected abstract function initToolkit(): void; function __construct(?array $config=null) { $this->initToolkit(); $this->config = [ "configure_options" => null, "error_page_class" => null, "self" => null, "prefix" => prefix::compute(null), "title" => null, "css" => [], "js" => [], "plugins" => [], ]; $css = A::getdel($config, "css"); $js = A::getdel($config, "js"); $plugins = A::getdel($config, "plugins"); A::merge_nz($this->config, $config); A::merge($this->config["css"], $css); A::merge($this->config["js"], $js); A::merge($this->config["plugins"], $plugins); $this->params = new GenericArray(); $this->components = []; } /** @return array les options par défaut pour {@link config::configure()} */ protected function CONFIGURE_OPTIONS(): ?array { return static::CONFIGURE_OPTIONS; } const CONFIGURE_OPTIONS = null; /** @return string la page utilisée pour afficher les erreurs */ protected function ERROR_PAGE_CLASS(): string { return static::ERROR_PAGE_CLASS; } const ERROR_PAGE_CLASS = Html5BasicErrorPage::class; /** * @return string chemin du script correspondant à cette page, depuis la racine * de l'application e.g. index.php, ou null s'il faut utiliser la valeur par * défaut */ protected function SELF(): ?string { return static::SELF; } const SELF = null; /** @return string titre de la page */ protected function TITLE(): ?string { return static::TITLE; } const TITLE = null; /** @return ?string|array liste de feuilles CSS à charger dans la page */ protected function CSS() { return static::CSS; } const CSS = null; /** @return ?string|array liste de scripts à charger dans la page */ protected function JS() { return static::JS; } const JS = null; /** @return ?string|array liste de plugins à ajouter à cette page */ protected function PLUGINS() { return static::PLUGINS; } const PLUGINS = null; /** @var array */ protected $config; protected function initConfig(): void { A::replace_z($this->config, "configure_options", $this->CONFIGURE_OPTIONS()); A::replace_z($this->config, "error_page_class", $this->ERROR_PAGE_CLASS()); $self = $this->SELF(); if ($self !== null) { A::merge($this->config, [ "self" => $self, "prefix" => prefix::compute($self), ]); } A::replace_z($this->config, "title", $this->TITLE()); A::merge($this->config["css"], $this->CSS()); A::merge($this->config["js"], $this->JS()); A::merge($this->config["plugins"], $this->PLUGINS()); } function getConfig(): array { return $this->config; } function getErrorPage(): IErrorPage { # NB: cette fonction peut être appelée AVANT initConfig() $error_page_class = $this->config["error_page_class"]; if ($error_page_class === null) $error_page_class = $this->ERROR_PAGE_CLASS(); if ($error_page_class === null) $error_page_class = Html5BasicErrorPage::class; $error_page = new $error_page_class(); $error_page->initContainer($this); return $error_page; } function getSelf(): ?string { return $this->config["self"]; } function getSelfRelativePrefix(): string { return $this->config["prefix"]; } function getTitle(): string { return $this->config["title"]; } function getCssUrls(): array { return $this->config["css"]; } function getJsUrls(): array { return $this->config["js"]; } function getPlugins(): array { return $this->config["plugins"]; } /** @var BaseArray */ protected $params; function getParams(): BaseArray { return $this->params; } function haveError(): bool { return boolval($this->params["page_error"]); } function setError(?string $message, ?Throwable $exception=null): void { $this->params["page_error"] = [ "message" => $message, "exception" => $exception, ]; } function getError(): ?array { return $this->params["page_error"]; } # null = init, 0 = prepare, 1 = config, 2 = setup, 3 = print, 4 = teardown const INIT_PHASE = null; const PREPARE_PHASE = 0; const CONFIG_PHASE = 1; const SETUP_PHASE = 2; const PRINT_PHASE = 3; const TEARDOWN_PHASE = 4; protected $phase; protected function ensure_phasec(IComponent $c) { $phase = $this->phase; if ($phase !== self::INIT_PHASE) { if ($phase >= self::PREPARE_PHASE) self::ensure_preparec($c); if ($phase >= self::CONFIG_PHASE) self::ensure_configc($c, $this->config); if ($phase >= self::SETUP_PHASE) self::ensure_setupc($c); if ($phase >= self::TEARDOWN_PHASE) self::ensure_teardownc($c); } } /** @var array composants enregistrés dans ce container, indexés par nom */ protected $components; function addPlugin($plugin, ?string $name=null): IPlugin { if (is_string($plugin)) $plugin = new $plugin(); elseif (is_array($plugin)) $plugin = func::cons(...$plugin); if ($plugin instanceof IChildComponent) $plugin->initContainer($this); if ($plugin instanceof IComponent) $this->ensure_phasec($plugin); A::set($this->components, $name, $plugin); return $plugin; } /** @var IPage composant de page à afficher dans ce container */ protected $page; function setPage(IPage $page): void { if ($page instanceof IChildComponent) $page->initContainer($this); $this->page = $page; $this->haveOutput = false; } /** @var bool ce container a-t-il déjà commencé à afficher du contenu? */ protected $haveOutput; function haveOutput(): bool { return $this->haveOutput; } function print(): void { $page = $this->page; page::set_current_page($page); try { $this->phase = self::PREPARE_PHASE; if (self::ensure_preparec($page)) { $this->overridePrepare($page); $page->afterPrepare(); } $this->phase = self::CONFIG_PHASE; $this->initConfig(); if (self::ensure_configc($page, $this->config)) { $this->overrideConfig($page); $page->afterConfig(); } config::configure($this->config["configure_options"]); $this->phase = self::SETUP_PHASE; if (self::ensure_setupc($page)) { $this->overrideSetup($page); $page->afterSetup(); } $this->phase = self::PRINT_PHASE; $this->overridePrint($page); } catch (Throwable $e) { if ($e instanceof ExitException && !$e->isError()) { # NOP } else { $this->setError(null, $e); } } finally { if ($this->haveError()) { $errorPage = $this->getErrorPage(); if ($this->haveOutput()) { # n'afficher que l'erreur si on a déjà du contenu $errorPage->printError(); } else { # sinon afficher la page entière $errorPage->print(); } } if ($page->didSetup()) { $this->phase = self::TEARDOWN_PHASE; if (self::ensure_teardownc($page)) { $this->overrideTeardown($page); $page->afterTeardown(); } } } } protected function overridePrepare(IPage $page): void { } protected function overrideConfig(IPage $page): void { } protected function overrideSetup(IPage $page): void { } protected function overridePrint(IPage $page): void { if ($page->haveContent()) { $this->haveOutput = true; co::_print([$page]); } } protected function overrideTeardown(IPage $page): void { } }