Skip to content

[Runtime] Bootstrapping of standalone ConsoleApplication as a globally installed composer package/tool is cumbersome #44235

@keichinger

Description

@keichinger

Symfony version(s) affected

5.4

Description

We're currently in the process of automating some of our more cumbersome tasks when doing deployments (updating the deployed Jira issues to reflect the most recent status). So we've created a small Symfony\Component\Console\Application-based standalone application, which we wanted to install globally on our dev machines (composer global require becklyn/deploy-message-generator). And since it's a new tool, we wanted to spend the time having a closer look at the awesome new runtime component.

Installing the tool within an existing Symfony application works exactly as expected. However, installing it globally is where the headache began:

1) Runtime selection

Following the official docs (https://symfony.com/doc/current/components/runtime.html#selecting-runtimes), there are two ways of selecting which runtime is being used to bootstrap the application: Either via an environment variable or by specifying the extra.runtime.class key within our composer.json (which we did).

Trying to just execute our tool resulted in a very strange behaviour where it was instantiating SymfonyRuntime, even though our configuration did specify to use the GenericRuntime (see https://github.com/Becklyn/deploy-message-generator/blob/1.0.0/composer.json#L26-L30).

2) Unable to pass $runtimeOptions to any runtime via CLI arguments

Currently, the SymfonyRuntime (and most likely others) are configurable by passing in an array, which is generated by parsing the $_SERVER and $_ENV super globals (see

$runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + %runtime_options%);
). In the default autoload_runtime.php, there is actually a single semi-hardcoded value, which defines the project_dir option.

If I'd now go ahead and try to use some options, I cannot possibly figure out how to turn my regular CLI arguments into a proper PHP array, which is expected to make checks such as this

if (!($options['disable_dotenv'] ?? false) && isset($options['project_dir']) && !class_exists(MissingDotenv::class, false)) {
work.

3) SymfonyRuntime always loads DotEnv variables

This is more or less a follow-up from the previous problem since I can't configure the default behaviour of the SymfonyRuntime.

Since our console application also makes use of the DotEnv (with a different path on where to look for the .env file), the SymfonyRuntime just tries to load a .env file within my global composer folder, which causes in an error since the file doesn't exist.

How to reproduce

(Assuming the global composer directory is within your shell's $PATH)

  • composer global require becklyn/deploy-message-generator
  • deploy-message-generator deployment:send-deploy-message staging a81842dc..9368c076

You should see a stacktrace that looks like this:

CleanShot 2021-11-23 at 18 45 23

Possible Solution

One workaround for us would be to no longer require autoload_runtime.php (and load autoload.php again) and copy over some of the logic from it to ultimately end up with something like this:

<?php declare(strict_types=1);

use Becklyn\DeployMessageGenerator\Commands\SendDeployMessageCommand;
use Composer\InstalledVersions;
use Symfony\Component\Console\Application;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\Runtime\GenericRuntime;

// Regular project installation
if (is_file($autoloader = dirname(__DIR__) . "/vendor/autoload.php"))
{
    require_once $autoloader;
}
// Composer global install
else if (is_file($autoloader = dirname(__DIR__, 3) . "/autoload.php"))
{
    require_once $autoloader;
}

$app = static function (array $context) : Application {
    $home = "Windows" === \PHP_OS_FAMILY ? $context["USERPROFILE"] : $context["HOME"];
    (new Dotenv())->load("{$home}/.deploy-message-generator.env");
    $context += $_ENV;

    $name = "becklyn/deploy-message-generator";
    $version = InstalledVersions::getVersion($name) ?? "UNKNOWN";
    $application = new Application($name, $version);
    $application->add(new SendDeployMessageCommand($context));

    return $application;
};

$runtime = new GenericRuntime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    "disable_dotenv" => true,
    "project_dir" => dirname(__DIR__, 1),
]);

[$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();

$app = $app(...$args);

exit(
    $runtime
        ->getRunner($app)
        ->run()
);

This would work for us for now, but this is most likely not how this component has been envisioned :) Surely it sounds like I've found an edge case :D

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy