Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to initiate Robo tasks from within a php script? #880

Open
derekyle opened this issue Jul 13, 2019 · 9 comments
Open

Is there a way to initiate Robo tasks from within a php script? #880

derekyle opened this issue Jul 13, 2019 · 9 comments

Comments

@derekyle
Copy link

I realize that Robo is primarily for initiating tasks via the CLI, but I have some cases where it would be nice to be able to programmatically initiate some of these tasks from within my existing php application. Obviously one could do something like shell_exec('robo foo'); but not having to use a raw shell_exec is one of the reasons I am using robo.

@greg-1-anderson
Copy link
Member

greg-1-anderson commented Jul 13, 2019

You can use Robo tasks from classes that use the \Robo\Tasks \Robo\LoadAllTasks trait. Be sure that you inject all of the needed dependencies if you do this.

@derekyle
Copy link
Author

Ok, is that what this portion of the documentation is talking about? https://robo.li/framework/#using-your-own-dependency-injection-container-with-robo-advanced

@greg-1-anderson
Copy link
Member

No, you do not need your own DI container to run tasks.

Note that \Robo\Tasks uses the following traits:

  • ContainerAwareTrait
  • TaskAccessor
  • BuilderAwareTrait
  • IO

Each of these traits have one or more set methods that must be called before the trait will function correctly. Calling a 'set' method and providing a dependency that the class may use is one way to do dependency injection.

The other way to do dependency injection is to pull your objects from the DI container. Again, you do not need a custom container to do this; you can simply add your class to the standard Robo DI container.

See Runner::instantiateCommandClasses for an example, particularly https://github.com/consolidation/Robo/blob/1.4.9/src/Runner.php#L319,L321.

This technique is not great coding style (it is "easy" not "simple"). Usually a better model to follow is for a framework to provide static factory methods that take a reference to the container and return a properly constructed instance. Perhaps Robo 2.x will clean up the "Robo as a framework" use case; Robo 1.x is aimed mainly at the RoboFile.php use-case.

@derekyle
Copy link
Author

Thank you. You've been very helpful.

One other question if you don't mind. I have been unable to figure out how to capture cli output. For example, I have a task where I need to check what the current git branch is: "git rev-parse --abbrev-ref HEAD" and then do different things based on the result.

So far, the only thing I can do with the result is $result->wasSuccessful();

I have explored around with the result object but I can't see any way to get at the actual text of the command result.

@greg-1-anderson
Copy link
Member

In most cases, you are better off using exec or the Symfony Process component to run programs to fetch current state in advance of defining your tasks. This will make your code easier to read. Running multiple collections, chaining the results of one task into a later task, conditionals, etc. are all possible, but reduce readability and maintainability.

If you really really want to use an exec task for this (don't), you'll find the output of the command in the "message" of the result. Note that you must be careful to disable 'interactive' and 'printed' modes, or you will not capture any output at all.

@c33s
Copy link
Contributor

c33s commented Dec 12, 2020

@greg-1-anderson may you be so kind to add a section in the framework section of the documentation about what are the exact current steps to run a task from a php file?

i try to do an integration test with codeception and all provided modules for filesystem operations are not as handy as the one of robo (for example copy but exclude some files/dirs). i also thought about adding a codeception robo module where you can easily access some (all?) robo tasks. so i am looking for the minimal and sane code to run tasks from a codeception cest and later maybe a codeception module.

i had a look for the Tasks trait you wrote above but there is only a Tasks class now.

@greg-1-anderson
Copy link
Member

Sorry, that was a typo; I meant the \Robo\LoadAllTasks trait. I fixed my original commit.

I don't know when I'll have a chance to update the documentation on this. I'm currently working through PHP 8 support (upgrading phpunit and switching to github actions) for the Consolidation projects right now, and I have a couple other tasks on my list after that. Docs PRs would be welcome if anyone figures this out and cares to write something up.

@c33s
Copy link
Contributor

c33s commented Dec 13, 2020

@greg-1-anderson thanks for your answer and all your hard work. php8 support is of course much more important than this :)

if you it's easy to solve for you and take not much time, may you give one example here in the ticket what the intended call would look like to run a copy task from a php file? so if people stumple upon here, there is at least one working example.

sidenote: for me the solution was to call robo from codeception via the cli modules runShellCommand method and ended up using symfony finder and symfony filesystem to copy the files at it was the most flexible way to do that (i am thinking about simply putting the symfony finder and filesystem in a codeception module).

IntegrationCest:

...
        $I->runShellCommand("robo init:integration-test $baseDir project package");
...

RoboFile:

...
    /**
     * Must only be called via codeception.
     */
    public function initIntegrationTest(string $baseDir, string $projectDirName, string $packageDirName)
    {
        $projectDir = "$baseDir/$projectDirName";
        $packageDir = "$baseDir/$packageDirName";
        $this->say($projectDir);
        $this->say($packageDir);
        if (!file_exists($baseDir) && !mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
            throw new \RuntimeException(sprintf('Directory "%s" was not created', $baseDir));
        }
        if (!file_exists($packageDir) && !mkdir($packageDir, 0777, true) && !is_dir($packageDir)) {
            throw new \RuntimeException(sprintf('Directory "%s" was not created', $packageDir));
        }
        $this->say(getcwd());
        $finder = new Symfony\Component\Finder\Finder();
        $finder
            ->in(getcwd())
            ->depth(0)
            ->ignoreDotFiles(true)
            ->ignoreVCS(true)
//            ->ignoreVCSIgnored(true)
            ->notPath('tests')
            ->notPath('vendor')
        ;
        $filesystem = new Symfony\Component\Filesystem\Filesystem();
        foreach ($finder as $file) {
            if ($file->isFile()) {
                $filesystem->copy($file->getRelativePathname(), "$packageDir/{$file->getFilename()}", true);
                continue;
            }
            $filesystem->mirror($file->getRelativePathname(), "$packageDir/{$file->getFilename()}");
        }
    }
...

2nd note while writing this i moved the code from robo in the test class

@ivangretsky
Copy link

The other way to do dependency injection is to pull your objects from the DI container. Again, you do not need a custom container to do this; you can simply add your class to the standard Robo DI container.

What is the best place to do it in a project that had Robo as a composer dependency?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants