How to create a console command in Symfony2 application

3.8k views Asked by At

I need to create a console command for a Symfony2 application and I read docs here and here though I am not sure what of those I should follow. So this is what I did.

  • Create a file under /src/PDI/PDOneBundle/Console/PDOneSyncCommand.php
  • Write this code:

    namespace PDI\PDOneBundle\Console\Command;
    
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputArgument;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Input\InputOption;
    use Symfony\Component\Console\Output\OutputInterface;
    
    class PDOneSyncCommand extends Command
    {
        protected function configure()
        {
            $this
                ->setName('pdone:veeva:sync')
                ->setDescription('Some description');
        }
    
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            $name = $input->getArgument('name');
            if ($name) {
                $text = 'Hello '.$name;
            } else {
                $text = 'Hello';
            }
    
            if ($input->getOption('yell')) {
                $text = strtoupper($text);
            }
    
            $output->writeln($text);
        }
    }
    
    • Create a file under /bin
    • Write this code:

      ! /usr/bin/env php

      require __ DIR __ .'/vendor/autoload.php';

      use PDI\PDOneBundle\Console\Command\PDOneSyncCommand; use Symfony\Component\Console\Application;

      $application = new Application(); $application->add(new PDOneSyncCommand()); $application->run();

But when I go to console by running php app/console --shell and hit ENTER I can't see the command registered, what I am missing?

NOTE: Can someone with more experience than me format the second piece of code properly?

UPDATE 1

Ok, following suggestions and taking answer as a start point I built this piece of code:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $container = $this->getContainer();

    $auth_url = $container->get('login_uri')."/services/oauth2/authorize?response_type=code&client_id=".$container->get('client_id')."&redirect_uri=".urlencode($container->get('redirect_uri'));

    $token_url = $container->get('login_uri')."/services/oauth2/token";
    $revoke_url = $container->get('login_uri')."/services/oauth2/revoke";

    $code = $_GET['code'];

    if (!isset($code) || $code == "") {
        die("Error - code parameter missing from request!");
    }

    $params = "code=".$code
        ."&grant_type=".$container->get('grant_type')
        ."&client_id=".$container->get('client_id')
        ."&client_secret=".$container->get('client_secret')
        ."&redirect_uri=".urlencode($container->get('redirect_uri'));

    $curl = curl_init($token_url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $params);

    $json_response = curl_exec($curl);

    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    if ($status != 200) {
        die("Error: call to token URL $token_url failed with status $status, response $json_response, curl_error ".curl_error(
                $curl
            ).", curl_errno ".curl_errno($curl));
    }

    curl_close($curl);

    $response = json_decode($json_response, true);

    $access_token = $response['access_token'];
    $instance_url = $response['instance_url'];

    if (!isset($access_token) || $access_token == "") {
        die("Error - access token missing from response!");
    }

    if (!isset($instance_url) || $instance_url == "") {
        die("Error - instance URL missing from response!");
    }

    $output->writeln('Access Token ' . $access_token);
    $output->writeln('Instance Url ' . $instance_url);
}

But any time I invoke the task I got this error:

[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException] You have requested a non-existent service "login_uri".

Why? Can't I access paramters on parameter.yml file? Where I am failing?

2

There are 2 answers

7
Artamiel On BEST ANSWER

You are reading article about Console Component. This is slightly different than registering a command in your bundle.

First, your class should live in Namespace Command, and it must include the Command prefix in classname. You've mostly done that. I will show you a sample command to grasp the idea so you can continue working with that as a base.

<?php

namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

// I am extending ContainerAwareCommand so that you can have access to $container
// which you can see how it's used in method execute
class HelloCommand extends ContainerAwareCommand {

    // This method is used to register your command name, also the arguments it requires (if needed)
    protected function configure() {
        // We register an optional argument here. So more below:
        $this->setName('hello:world')
            ->addArgument('name', InputArgument::OPTIONAL);
    }

    // This method is called once your command is being called fron console.
    // $input - you can access your arguments passed from terminal (if any are given/required)
    // $output - use that to show some response in terminal
    protected function execute(InputInterface $input, OutputInterface $output) {
        // if you want to access your container, this is how its done
        $container = $this->getContainer();

        $greetLine = $input->getArgument('name') 
            ? sprintf('Hey there %s', $input->getArgument('name')) 
            : 'Hello world called without arguments passed!'
        ;

        $output->writeln($greetLine);
    }

}

Now, running app/console hello:world' you should see a simple Hello world at your terminal.

Hope you got the idea, dont hesitate to ask if you have questions.

Edit

In Commands you cant directly access request, because of scopes. But you can pass arguments when you call your command. In my example I've registered optional argument which leads to two different outputs.

If you call your command like this app/console hello:world you get this output

Hello world called without arguments passed!

but if you provide a name like this app/console hello:world Demo you get the following result:

Hey there Demo

0
D4V1D On

Following Artamiel's answer and the comments below, here what you would need to build a command run as a CRON task (at least, this is how I've done it):

  • First, declare your SalesforceCommand class:

    <?php
    class SalesforceCommand extends ContainerAwareCommand
    {
        protected function configure()
        {
            $this
                ->setName('pdone:veeva:sync')
                ->setDescription('Doing some tasks, whatever...');
        }
    
        protected function execute(InputInterface $input, OutputInterface $output)
        {    
            $myService = $this->getContainer()->get('my.service');
    
            $returnValue = $myService->whateverAction();
    
            if($returnValue === true)
                $output->writeln('Return value of my.service is true');
            else
                $output->writeln('An error occured!');
        }
    }
    
  • Then, create your controller in whatever bundle you want:

        <?php
        namespace My\MyBundle\Service;          
    
        use Symfony\Component\HttpFoundation\RequestStack;
    
        class ServiceController extends Controller
        {
            private $_rs;
    
            public function __construct(RequestStack $rs)
            {
                $this->_rs = $rs;
            }
    
            public function whateverAction()
            {
                $request = $this->_rs->getCurrentRequest();
    
                // do whatever is needed with $request.
    
                return $expectedReturn ? true : false;
            }
        }
    
  • Finally, register your Controller as a Service in app/config/services.yml

    services:
        my.service:
            class: My\MyBundle\Service\ServiceController
            arguments: ["@request_stack"]
    

(as of Symfony 2.4, instead of injecting the request service, you should inject the request_stack service and access the Request by calling the getCurrentRequest() method)

  • You are finally able to use run it in as a CRON job by adding the following to your crontab (for it to run every minute):

    * * * * * /usr/local/bin/php /path/to/your/project/app/console pdone:veeva:sync 1>>/path/to/your/log/std.log 2>>/path/to/your/log/err.log
    

Hope that helps!