How to launch a basic PHP cli

Hello,

I just need to set up a simple PHP call from a command line or a cron job. I do no need interactive console, only launch the command and this time, Google is not my friend :slight_smile: .

How can I do ?

Thank in adcance.

Michel.

Although laminas-console is deprecated, I still use it in my mvc projects. If anyone has a quick how to for another console component with laminas-mvc, please let me know!

This is what I do:

composer require laminas/laminas-mvc-console
composer require laminas/laminas-console

Then create a file meil128.php in the root of your project:
<?php

use Laminas\Mvc\Application;
use Laminas\Stdlib\ArrayUtils;

// Composer autoloading
include __DIR__ . '/vendor/autoload.php';

if (! class_exists(Application::class)) {
    throw new RuntimeException(
        "Unable to load application.\n"
        . "- Type `composer install` if you are developing locally.\n"
        . "- Type `vagrant ssh -c 'composer install'` if you are using Vagrant.\n"
        . "- Type `docker-compose run zf composer install` if you are using Docker.\n"
    );
}

// Retrieve configuration
$appConfig = require __DIR__ . '/config/application.config.php';
if (file_exists(__DIR__ . '/config/development.config.php')) {
    $appConfig = ArrayUtils::merge($appConfig, require __DIR__ . '/config/development.config.php');
}

// Run the application!
Application::init($appConfig)->run();

Then add console routing to your config.module.conf:

'console' => [
    'router' => [
        'routes' => [
            // Console routes go here
            'diagnostics' => [
                'options' => [
                    'route' => 'mel diagnostics',
                    'defaults' => [
                        'controller' => Controller\IndexController::class,
                        'action' => 'diagnostics',
                    ],
                ],
            ],
        ],
    ],
],

Call it with: php mel128.php mel diagnostics

You can have a look at ConsoleUsageProviderInterface, ConsoleBannerProviderInterface to make it more fancy. Also see the console module page.

Thank you for your fast reply.

I did not use laminas-console because it is deprecated but it may a temporary workaround.

Michel.

What exactly are you trying to do? Do you need access to application services, or can everything be done in vanilla PHP (or with some autoloaded classes)? Will you need to vary how the command works based on arguments passed via the command line?

I ask, because it can be as simple as creating a PHP script, and having CRON invoke it. Or it can require something more full-blown, such as bootstrapping your DI container and pulling services from it.

Tell us what you’re trying to build, with a little more detail, so we can give you more targeted direction.

Hello Matthew and thank for your reply,

The MVC application is stricly object oriented and the script should need to use these objects including all methods implemented.

Meanwhile, the scripts do not need any parameter.

To summarize, it’s basically calling a route without any parameters.

Michel

Hi @mel128,

If I have understand what you searching for is how to execute a PHP script com a cron job or simular. I have two solucions :wink:

The fisrt one is to point to the php executable and php.ini from the cron job as so:

0 0 * * * /usr/local/zend/bin/php -c /usr/local/zend/etc/php.ini //file.php

This example is using Zend Server Developer Edition

The second one is to point the cron to a PHP file. At the begining of that file you have to put the location of the php executable such:

#!/usr/local/zend/bin/php

<?php And the de script. The cron job need to have the entry as such: 10 1 1 * * //file.php Hope that helps :relaxed: Sergi

Hello Sergi, thank for your reply,

The problem is not the cron job but how to run the application. I do not want to run a single PHP script but doing more complex operations even if there are any parameters needed.

Michel

So, this sounds like:

  • You need to bootstrap the application container.
  • You will pull a service from it.
  • You will do something with that service.

As such, here’s an example bin/console script:

#!/usr/bin/env php
<?php

use Zend\Mvc\Application;
use Zend\Stdlib\ArrayUtils;

/**
 * This makes our life easier when dealing with paths. Everything is relative
 * to the application root now.
 */
chdir(dirname(__DIR__));

// Composer autoloading
include __DIR__ . '/../vendor/autoload.php';

if (! class_exists(Application::class)) {
    throw new RuntimeException(
        "Unable to load application.\n"
        . "- Type `composer install` if you are developing locally.\n"
        . "- Type `vagrant ssh -c 'composer install'` if you are using Vagrant.\n"
        . "- Type `docker-compose run zf composer install` if you are using Docker.\n"
    );
}

// Retrieve configuration
$appConfig = require __DIR__ . '/../config/application.config.php';
if (file_exists(__DIR__ . '/../config/development.config.php')) {
    $appConfig = ArrayUtils::merge($appConfig, require __DIR__ . '/../config/development.config.php');
}

// Run the application!
/** @var Application $application */
$application = Application::init($appConfig);
$container   = $application->getServiceManager();

// Grab a service, and do something with it:
// $container->get(\My\ServiceName::class)->doSomething();

After creating it, call chmod 755 bin/console to make it executable.

From there, create your cronjob:

0 0 * * * /path/to/application/bin/console

And you’re done.

You mention that the idea is to essentially call a route without any parameters. To make this work correctly, you should extract into a separate class the code that does something from the controller, and then have the controller compose that class and invoke.

As an example:

class TheControllerThatSharesFunctionalityIWantToInvokeInTheConsole extends AbstractActionController
{
    /** @var ClassWithExtractedFunctionality */
    private $instanceWithFunctionality;

    public function __construct(ClassWithExtractedFunctionality $instanceWithFunctionality)
    {
        $this->instanceWithFunctionality = $instanceWithFunctionality;
    }

    public function aRoutedAction()
    {
        // Marshal some parameters from the request...
        // And pass them on to our extracted class:
        return $this->instanceWithFunctionality->doSomething($withData);
    }
}

From here, you’ll have factories for your ClassWithExtractedFunctionality and your controller, and your controller factory will inject the ClassWithExtractedFunctionality service into your controller. This allows you to re-use that functionality outside of the context of your controller.

If you find yourself needing arguments and options, you can evolve the above bin/console script by dropping the following symfony/console code in, following the point where you obtain your $container:

use Symfony\Component\Console\Application as ConsoleApplication;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;


$commandLoader = new ContainerCommandLoader($container, [
    // Map command:name => service name here.
]);

$application = new ConsoleApplication();
$application->setCommandLoader($commandLoader);
$application->run();

From here, you would build symfony/console commands that compose your services, and then do something with them. This approach gives you the ability to specify arguments and options.

(We’ve actually migrated all tooling in Laminas and Mezzio to symfony/console, and will be expanding it in the near future.)

1 Like

Thank you very much for your time Matthew, I will check it tomorrow.

Michel.

I believe you would be in a better position if you setup symfony console application once and then use it.
Look at the doctrine module for how symfony application is setup within mvc application.
This is the entry point that bootstraps the application https://github.com/doctrine/DoctrineModule/blob/570a915b5de014c94c01633622f7d34ad500f640/bin/doctrine-module.php

Doctrine module setup is more complex than what you need, but you should be able to adapt parts if it if you are familiar with symfony console

2 Likes

I’m with xerkus here. I’ve written different “single file” scripts, but in the end symfony/console is the best if your cli scripts evolve. And it’s amazingly easy to setup. What I’ve done is to let the ServiceManager create the Symfony console app, so my CLI entry point (at bin/console) looks like this:

#!/usr/bin/env php
<?php

declare(strict_types=1);

use Laminas\Mvc\Application as ZfApp;
use Symfony\Component\Console\Application as ConsoleApp;

chdir(dirname(__DIR__));

require dirname(__DIR__) . '/vendor/autoload.php';

$zfApp = ZfApp::init(require dirname(__DIR__) . '/config/application.config.php');
/** @var ConsoleApp $consoleApp */
$consoleApp = $zfApp->getServiceManager()->get(ConsoleApp::class);
return $consoleApp->run();

As you can see, I build the Laminas App (called ZfApp here, it’s pre-Laminas), get the ServiceManager from there and the Symfony console from the ServiceManager.
The Service Factory looks like this

<?php

declare(strict_types=1);

namespace Eventjet\Factory;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;

final class ConsoleApplicationFactory
{
    public function __invoke(ContainerInterface $container): Application
    {
        $app = new Application(
            'Eventjet Console Legacy System',
            '1.0'
        );
        foreach ($this->createCommands($container) as $command) {
            $app->add($command);
        }
        return $app;
    }

    /**
     * @return Command[]
     */
    private function createCommands(ContainerInterface $container): array
    {
        $commandNames = $this->config($container)['ej-console']['commands'];
        return array_map(
            static function (string $commandName) use ($container) {
                return $container->get($commandName);
            },
            $commandNames
        );
    }

    /**
     * @return mixed[]
     */
    private function config(ContainerInterface $container): array
    {
        return $container->get('config');
    }
}

That way, I can just create a new Console Class, register as class-string in the config under the [‘ej-console’][‘commands’] config key and have it availabe right away :slight_smile:
The config would look something like that:

return [
    'ej-console' => [
        'commands' => [
            SomeCommand::class,
        ],
    ],
];

Hope that helps :slight_smile:

1 Like

I thank everybody for the help.

I followed Matthew’s post ( the first part ) and it is perfect for what I need right now. I tested it this morning and it’s doing the job.

I keep the posts from Aleksei and Thomas for futher usage.

Take care about you.

Michel.