drupflare/worker - v1.0.4
    Preparing search index...

    Variable ENABLE_MODULE_PHPConst

    ENABLE_MODULE_PHP: "\nuse Drupal\\cfw_do_sqlite\\Driver\\Database\\cfw_do_sqlite\\Connection;\nuse Drupal\\Core\\Extension\\ExtensionDiscovery;\nuse Drupal\\drupflare\\Routing\\CfwMatcherDumper;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\n$out = ['ok' => false];\n$name = $GLOBALS['__cfw_enable_module'] ?? '';\n$dryRun = !empty($GLOBALS['__cfw_enable_dry']);\n$out['module'] = $name;\n$out['dryRun'] = $dryRun;\n\n$stopAt = (string) ($GLOBALS['__cfw_enable_stop'] ?? '');\n$halt = static function (string $stage) use (&$out, $stopAt): bool {\n\tif ($stopAt !== $stage) {\n\t\treturn false;\n\t}\n\t$out['ok'] = true;\n\t$out['stoppedAt'] = $stage;\n\techo json_encode($out);\n\treturn true;\n};\nif ($halt('boot')) {\n\treturn;\n}\n\nif ($name === '') {\n\t$out['error'] = 'no module named';\n\techo json_encode($out);\n\treturn;\n}\n\n// THE FILE SCAN IS A STATIC AND THIS SAPI NEVER TEARS ONE DOWN, so it is cleared first: the theme\n// check below reads it too, and a theme /install delivered after the first render was never found.\n// No public reset exists in Drupal 11 and the property is protected, so reflection is the only way\n// in. Guarded: a core that drops the property must not take the enable down with it\ntry {\n\t$prop = new ReflectionProperty(ExtensionDiscovery::class, 'files');\n\t$prop->setAccessible(true);\n\t$prop->setValue(null, []);\n\t$out['discoveryScanCleared'] = true;\n} catch (Throwable $e) {\n\t$out['discoveryScanCleared'] = false;\n}\n\n// A THEME IS A DIFFERENT INSTALLER, and it was not reachable at all.\n// composer/installers puts a drupal-theme under themes/contrib, extension.list.module does not\n// list themes and module_installer cannot install one -- so a contrib theme had no route in and\n// could not be verified however well it would have worked. Handled here and returned, so the\n// module path below is untouched.\ntry {\n\t$themes = Drupal::service('extension.list.theme')->reset()->getList();\n} catch (Throwable $e) {\n\t$themes = [];\n}\nif (isset($themes[$name])) {\n\t$out['kind'] = 'theme';\n\t$out['modulePath'] = $themes[$name]->getPath();\n\t$enabledThemes = array_keys(Drupal::config('core.extension')->get('theme') ?? []);\n\t$out['alreadyEnabled'] = in_array($name, $enabledThemes, true);\n\tif ($dryRun) {\n\t\t$out['ok'] = true;\n\t\techo json_encode($out);\n\t\treturn;\n\t}\n\ttry {\n\t\t// the same three the module path needs: install() rebuilds the router, which builds a request\n\t\t// context from the current request and reaches functions only a real request has loaded\n\t\t$kernel = $GLOBALS['__pw_kernel'] ?? null;\n\t\tif ($kernel !== null && method_exists($kernel, 'loadLegacyIncludes')) {\n\t\t\t$kernel->loadLegacyIncludes();\n\t\t}\n\t\t$stack = Drupal::service('request_stack');\n\t\tif ($stack->getCurrentRequest() === null) {\n\t\t\t$stack->push(Request::create('/', 'GET'));\n\t\t}\n\t\tDrupal::moduleHandler()->loadAll();\n\t\t$result = Drupal::service('theme_installer')->install([$name], true);\n\t\t$out['installReturned'] = $result;\n\t\t$nowThemes = array_keys(\n\t\t\tDrupal::configFactory()->getEditable('core.extension')->get('theme') ?? [],\n\t\t);\n\t\t$out['nowEnabled'] = in_array($name, $nowThemes, true);\n\t\t$out['ok'] = $out['nowEnabled'];\n\t} catch (Throwable $e) {\n\t\t$out['throwClass'] = get_class($e);\n\t\t$out['throwMessage'] = $e->getMessage();\n\t\t$out['throwAt'] = $e->getFile() . ':' . $e->getLine();\n\t\t$out['ok'] = false;\n\t}\n\techo json_encode($out);\n\treturn;\n}\n\n$listBefore = [];\ntry {\n\t$extension = Drupal::configFactory()->get('core.extension');\n\t$listBefore = array_keys($extension->get('module') ?? []);\n\t$out['moduleCountBefore'] = count($listBefore);\n\t$out['alreadyEnabled'] = in_array($name, $listBefore, true);\n} catch (Throwable $e) {\n\t$out['error'] = 'cannot read core.extension: ' . $e->getMessage();\n\techo json_encode($out);\n\treturn;\n}\n\n// is the module even discoverable? A module Drupal cannot see fails with a confusing\n// \"missing dependency\" rather than \"not found\", so it is separated out\ntry {\n\t// THE FILE SCAN IS A STATIC AND THIS SAPI NEVER TEARS ONE DOWN. pib_run performs no\n\t// php_request_startup/shutdown, so ExtensionDiscovery keeps its scanned file list for the life of\n\t// the interpreter. A site that rendered anything before the install therefore holds a scan taken\n\t// without the new module, and extension.list.module's own reset() does not reach it. Measured:\n\t// /install wrote 27 files, the boot mounted them, and discoverable stayed false forever.\n\t// (the scan itself is cleared above, before the theme check reads it)\n\t// whether the module is on disk at all, which separates a mount failure from a stale scan. Without\n\t// it both read as \"discoverable: false\" and the two have completely different fixes\n\t$out['filesMounted'] = is_dir('/drupal/modules/contrib/' . $name);\n\t$available = Drupal::service('extension.list.module')->reset()->getList();\n\t$out['discoverable'] = isset($available[$name]);\n\tif (isset($available[$name])) {\n\t\t$info = $available[$name]->info ?? [];\n\t\t$out['coreRequirement'] = $info['core_version_requirement'] ?? null;\n\t\t$out['declaredDependencies'] = $info['dependencies'] ?? [];\n\t\t$out['modulePath'] = $available[$name]->getPath();\n\t}\n} catch (Throwable $e) {\n\t$out['discoverError'] = $e->getMessage();\n}\nif ($halt('discover')) {\n\treturn;\n}\n\n// the legacy includes, and leaving them out is what this probe failed on first: the install died\n// with \"Call to undefined function module_config_sort()\" at ModuleInstaller.php:277.\n// DrupalKernel::loadLegacyIncludes() requires common.inc, module.inc, theme.inc, form.inc and\n// errors.inc, and it is called from preHandle(), NOT from boot() -- so any path that boots the\n// kernel without handling a request has none of those functions. This project has been requiring\n// common.inc by hand in two separate places for the same reason; calling the one method a real\n// request calls is both faithful and stops the next missing function being a separate discovery.\ntry {\n\t$kernel = $GLOBALS['__pw_kernel'] ?? null;\n\tif ($kernel !== null && method_exists($kernel, 'loadLegacyIncludes')) {\n\t\t$kernel->loadLegacyIncludes();\n\t\t$out['legacyIncludes'] = 'loaded via kernel';\n\t} else {\n\t\t// a kernel that cannot do it is reported rather than worked around, because the fallback\n\t\t// would hide the fact that the boot path changed\n\t\t$out['legacyIncludes'] = 'kernel unavailable';\n\t}\n\t$out['moduleConfigSort'] = function_exists('module_config_sort');\n} catch (Throwable $e) {\n\t$out['legacyIncludesError'] = $e->getMessage();\n}\nif ($halt('includes')) {\n\treturn;\n}\n\n// hook_requirements, which ModuleInstaller will NOT run for us\ntry {\n\trequire_once '/drupal/core/includes/install.inc';\n\tif (function_exists('drupal_check_module')) {\n\t\t$out['requirementsPass'] = (bool) drupal_check_module($name);\n\t} else {\n\t\t$out['requirementsPass'] = null;\n\t\t$out['requirementsNote'] = 'drupal_check_module absent after including install.inc';\n\t}\n} catch (Throwable $e) {\n\t$out['requirementsError'] = $e->getMessage();\n}\nif ($halt('requirements')) {\n\treturn;\n}\n\nif ($dryRun) {\n\t$out['ok'] = ($out['discoverable'] ?? false) === true;\n\t$out['note'] = 'dry run; nothing was installed';\n\techo json_encode($out);\n\treturn;\n}\n\n// the .module files have to be loaded, which was the fourth blocker. The final step of\n// ModuleInstaller::install() invokes hook_modules_installed, and the 'update' module's\n// implementation calls\n// update_storage_clear() -- a plain function in update.module. A bare kernel boot has loaded no\n// .module file at all, so that dies with \"Call to undefined function ...update_storage_clear()\"\n// AFTER the router rebuild has already been written. loadAll() is what a real request does.\ntry {\n\tDrupal::moduleHandler()->loadAll();\n\t$out['modulesLoaded'] = true;\n} catch (Throwable $e) {\n\t$out['moduleLoadError'] = $e->getMessage();\n}\n\n// A REQUEST HAS TO BE ON THE STACK, and this was the third blocker rather than a precaution.\n// ModuleInstaller rebuilds the router, and the route builder builds a RequestContext from the\n// current request -- with none, it dies on\n// \"RequestContext::fromRequest(): Argument #1 ($request) must be of type Request, null given\"\n// partway through the rebuild. An enable driven outside a request has to supply one, the same way\n// cfw_serve() does for a render.\ntry {\n\t$stack = Drupal::service('request_stack');\n\tif ($stack->getCurrentRequest() === null) {\n\t\t$stack->push(Request::create('/', 'GET'));\n\t\t$out['pushedRequest'] = true;\n\t} else {\n\t\t$out['pushedRequest'] = false;\n\t}\n} catch (Throwable $e) {\n\t$out['requestStackError'] = $e->getMessage();\n}\nif ($halt('preinstall')) {\n\treturn;\n}\n\n// the driver's own counters, snapshotted around the install rather than read after it. A total\n// read once cannot tell an install's cost from a boot's, and the replay counter is the one that\n// matters: statementCount() counts a replay as ONE call, replayedStatementCount() counts what the\n// host executed inside it, so the gap between them IS the O(W*R) term\n// COMPILED SOURCE IS COUNTED ALONGSIDE THE SQL, because there is no opcache in this build: every\n// PHP file an install pulls in is lexed and compiled inside the invocation that pulls it. A cost\n// that scales with bytes-of-source is invisible to both a statement counter and a row counter\n$meter = static function (): array {\n\t$out = ['files' => count(get_included_files()), 'sourceBytes' => 0, 'peakBytes' => 0];\n\tforeach (get_included_files() as $file) {\n\t\t$size = @filesize($file);\n\t\tif (is_int($size)) {\n\t\t\t$out['sourceBytes'] += $size;\n\t\t}\n\t}\n\t$out['peakBytes'] = memory_get_peak_usage(true);\n\ttry {\n\t\t$db = Drupal::database();\n\t\tif ($db instanceof Connection) {\n\t\t\t$out['statements'] = $db->statementCount();\n\t\t\t$out['transactions'] = $db->transactionCount();\n\t\t\t$out['speculative'] = $db->speculativeCount();\n\t\t\t$out['replayed'] = $db->replayedStatementCount();\n\t\t}\n\t} catch (Throwable $e) {\n\t\t// a connection that cannot be read leaves the file counters, which need no database\n\t}\n\treturn $out;\n};\n$before = $meter();\n\n// THE ATTEMPT. Wrapped tightly and reporting class + file:line, because the interesting outcome is\n// the failure: this call has never been made in this runtime and a bare message would not say which\n// of the four steps (config write, schema, container rebuild, router rebuild) died.\ntry {\n\t$installer = Drupal::service('module_installer');\n\t$out['installerClass'] = get_class($installer);\n\t$with = array_values(array_diff((array) ($GLOBALS['__cfw_enable_with'] ?? []), [$name]));\n\t$result = $installer->install(array_merge([$name], $with), true);\n\t$out['installReturned'] = $result;\n\t$out['ok'] = $result === true;\n} catch (Throwable $e) {\n\t$out['throwClass'] = get_class($e);\n\t$out['throwMessage'] = $e->getMessage();\n\t$out['throwAt'] = $e->getFile() . ':' . $e->getLine();\n\t$out['ok'] = false;\n}\n\n$after = $meter();\nforeach ($after as $key => $value) {\n\t$out['driver'][$key] = $value - ($before[$key] ?? 0);\n}\n// absolutes as well as the delta: the delta says what the install added, these say what it was\n// added to, and the compile cost is a property of the total rather than of the increment\n$out['driver']['filesTotal'] = $after['files'];\n$out['driver']['sourceBytesTotal'] = $after['sourceBytes'];\n$out['driver']['peakBytesTotal'] = $after['peakBytes'];\n\n// how many times the router was DUMPED and how many of those were skipped, read out of the dumper\n// rather than divided out of a row count. A statement total cannot tell a repeat from a wide write\ntry {\n\tif (class_exists(CfwMatcherDumper::class)) {\n\t\t$out['routerDumps'] = CfwMatcherDumper::$dumps;\n\t\t$out['routerSkips'] = CfwMatcherDumper::$skips;\n\t} else {\n\t\t$out['routerDumps'] = null;\n\t}\n} catch (Throwable $e) {\n\t$out['routerDumpError'] = $e->getMessage();\n}\n\n// what actually changed, read back rather than assumed\ntry {\n\t$after = Drupal::configFactory()->get('core.extension');\n\t$listAfter = array_keys($after->get('module') ?? []);\n\t$out['moduleCountAfter'] = count($listAfter);\n\t$out['nowEnabled'] = in_array($name, $listAfter, true);\n\t$out['added'] = array_values(array_diff($listAfter, $listBefore));\n} catch (Throwable $e) {\n\t$out['readbackError'] = $e->getMessage();\n}\n\necho json_encode($out);\n"