Purpose

The discovery-time record of one Scriptor plugin. Carries what the manager parsed out of installed.json for that plugin: the Composer package name, the package version, and the FQCN of the Plugin implementation. Persisted in the discovery cache; read back by the editor when it renders the installed-plugins table.

The DTO exists so the manager has a stable shape to cache, re-hydrate, and pass around without re-parsing installed.json every time something asks "which version of this plugin is installed?".

FQCN + file path

When to use

Two surfaces hand you a PluginManifest:

  • PluginManager::discover() returns a list<PluginManifest> of every discovered plugin.
  • PluginManager::manifestFor($pluginName) returns the manifest for one named plugin, or null if the plugin is a core plugin (which has no installed.json record).

You do not construct it yourself. Core plugins (FQCNs in the $corePlugins constructor argument of PluginManager) have no manifest because they ship with Scriptor, not as Composer packages.

Surface

public function __construct(
    public string $packageName,
    public string $packageVersion,
    public string $pluginClass,
)

final readonly. Three public fields:

Field Type Purpose
packageName string The Composer package name (e.g. bigins/scriptor-markdown-pages) from installed.json. Defaults to 'unknown' if the source line was malformed
packageVersion string The Composer-resolved version string (e.g. v0.1.7). Defaults to '0.0.0' if missing
pluginClass string The plugin FQCN read from extra.scriptor.plugin. This is what PluginManager instantiates

No methods. The DTO is just data.

Lifecycle

Created in two places inside PluginManager: by scanInstalledJson() (one per discovered plugin during a fresh scan) and by readCache() (one per cache-line during a cache hit). The cache file is a generated PHP file that returns the field arrays, which the loader walks to reconstruct the PluginManifest objects on the next request.

Instances are immutable. The manager stores them in $manifestsByPluginName keyed by Plugin::name() (only after boot, only for the plugins that successfully instantiated).

Common patterns

Showing what is installed in a diagnostic page

$manager = $site->container->get(\Scriptor\Boot\Plugin\PluginManager::class);

foreach ($manager->discover() as $manifest) {
    printf(
        "%-40s %-12s -> %s\n",
        $manifest->packageName,
        $manifest->packageVersion,
        $manifest->pluginClass,
    );
}

Preferring the manifest version over Plugin::version()

$manager = $site->container->get(\Scriptor\Boot\Plugin\PluginManager::class);

foreach ($manager->bootedPlugins() as $plugin) {
    $manifest = $manager->manifestFor($plugin->name());
    $version  = $manifest?->packageVersion ?? $plugin->version();
    echo $plugin->name(), ' ', $version, "\n";
}

The editor's PluginsModule uses exactly this shape. Composer's installed.json is the source of truth; Plugin::version() is informational and historically drifts (see Build a Module: Publishing on keeping the two in sync).

See also