Skip to content

Latest commit

 

History

History
454 lines (326 loc) · 11.9 KB

README.md

File metadata and controls

454 lines (326 loc) · 11.9 KB

Nette Console

symfony/console integration for Nette

list screenshot

Content

Setup

Install with Composer

composer require orisai/nette-console

Register extension

extensions:
	orisai.console: OriNette\Console\DI\ConsoleExtension

Entrypoint

Create an entrypoint for console

It's a script similar to www/index.php, it just gets Symfony\Component\Console\Application from DI container and runs it.

Create a file called bin/console and make sure it is executable - touch bin/console && chmod +x bin/console. After that just copy and paste one of following snippets into the file.

For Nette 3.0+ web-project structure, it should look like this:

#!/usr/bin/env php
<?php declare(strict_types = 1);

use App\Bootstrap;
use Symfony\Component\Console\Application;

require __DIR__ . '/../vendor/autoload.php';

$configurator = Bootstrap::boot();
$container = $configurator->createContainer();
$application = $container->getByType(Application::class);
exit($application->run());

For structure of Nette <=2.4:

#!/usr/bin/env php
<?php declare(strict_types = 1);

use Nette\DI\Container;
use Symfony\Component\Console\Application;

$container = require __DIR__ . '/../app/bootstrap.php';
$application = $container->getByType(Application::class);
exit($application->run());

Now you should be able to run console via php bin/console. In most of the environments should also work bin/console.

Debug mode

To see any changes dependent on debug mode, like adding new commands to config, you have to enable debug mode as it's not enabled by default.

With nette/bootstrap configurator it may look like this:

// Enable debug mode in console and on (http) localhost
$configurator->setDebugMode(
	PHP_SAPI === 'cli'
	|| $configurator::detectDebugMode()
);

With orisai/nette-di configurator:

use OriNette\DI\Boot\Environment;

// Enable debug mode in console and on (http) localhost
$configurator->setDebugMode(
	Environment::isConsole()
	|| Environment::isLocalhost()
);

Console configuration

Optionally set name and version, they will be shown on top of bin/console (alias for bin/console list) command.

orisai.console:

	# string | null | dynamic parameter
	# Default: null
	name: Application name

	# string | null | dynamic parameter
	# Default: null
	version: '0.1.0'

Usually symfony/console catches all exceptions and renders them nicely instead of letting them being handled by error handler. We have that option disabled in presumption you use Tracy already. Benefit is that Tracy is able not only to render exception in console but also to log it. However, you may still opt-in to enable error catching built in symfony/console.

orisai.console:

	# bool
	# Default: false
	catchExceptions: false

All commands registered as services are added to console, you don't have to add them.

Lazy loading

Console offers lazy loading of commands with which they are loaded just when used. Without lazy loading commands are always instantiated to get its name and description.

To enable lazy loading each command has to define name and description either as a property/method in its definition or in DI service configuration.

No global config option is required, our integration is able to load both lazy and not lazy commands.

To debug lazy loading, run bin/console commands-debug. It will show you which commands are missing name or description.

HTTP - link generating

It's possible to work with http request in console. Main reason to do so is link generating. To generate a link you have to enable http request override and specify base URL.

Enable override:

orisai.console:
	http:
		override: %consoleMode%

Specify URL either via configuration:

orisai.console:
	http:
		# string | dynamic parameter
		url: https://www.example.com

Or via command option:

bin/console newsletter:send --ori-url https://example.com

All the commands have this global option available, and it precedes URL from configuration in priority.

Be aware request is created just once per PHP run, argument ori-url cannot be changed without recreating DIC object. To call other commands from your command with different url you have to do so via separate PHP process ( run bin/console).

Also, only argv input is used because we need to know value of argument before a command is run. In practice, it means to set this option in tests you have to set it directly to $_SERVER['argv'], no other variant will work (e.g. via new ArrayInput()). bin/console uses argv as input, therefore standard usage is not affected.

HTTP headers

Console HTTP request can define custom headers:

orisai.console:
	http:
		headers:
			custom-name: custom-value

By default is set user-agent with value orisai/nette-console. It may be disabled as any other header:

orisai.console:
	http:
		headers:
			user-agent: null

Overwriting command configuration

It's possible to override command configuration via service tag console.command - change name and aliases, set description and hide the command from command list. To do so, add tag to command service.

Change name and description:

services:
	overriden.command:
		tags:
			console.command:
				name: new-name
				description: New description

Aliases are added to name, separated by |:

services:
	overriden.command:
		tags:
			console.command:
				name: AgathaChristie|MaryWestmacott|AgathaMaryClarissaMiller
				description: Woman of many names.

To hide command, add leading | to name:

services:
	overriden.command:
		tags:
			console.command:
				name: |unicorn
				description: "I am hiding so people can't hang my head on their wall."

Don't set command name, description, aliases or hidden via constructor or set*() methods (setName(), ...). It's not supported by lazy loading and configuration via tags and default properties/methods will always be prioritized.

Note: Syntax of aliases and hidden command matches the one from Symfony.

Command discovery

Extension automatically registers to console application every service with type Symfony\Component\Console\Command. That in practise means you don't have to pass commands to extension yourself.

discovery > tag option makes it possible to find only commands with given tag:

orisai.console:
	discovery:
		tag: my.console.command

services:
	overriden.command:
		tags:
			my.console.command: []

Set tag name is used instead of default console.command tag for command config overriding:

services:
	overriden.command:
		tags:
			# Not used by extension
			console.command:
				name: ignored
			# Used by extension
			my.console.command:
				name: used

Writing own commands

Following example shows how to define command that is findable by ConsoleExtension and is lazy loaded.

To learn about accepting input arguments and options, writing to output and else about commands, check Symfony documentation.

namespace App\Core\Newsletter;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class SendNewsletterCommand extends Command
{

	private NewsletterSender $newsletterSender;

	public function __construct(NewsletterSender $newsletterSender)
	{
		parent::__construct();
		$this->newsletterSender = $newsletterSender;
	}

	public static function getDefaultName(): string
	{
		return 'newsletter:send';
	}

	public static function getDefaultDescription(): ?string
	{
		return 'Send newsletter to users';
	}

	protected function execute(InputInterface $input, OutputInterface $output): int
	{
		$this->newsletterSender->send();

		return self::SUCCESS;
	}

}

Register command as a service:

services:
	- App\Core\Newsletter\SendNewsletterCommand

PHPStan compatibility

PHPStan does not understand commands arguments and options out of the box. To solve that problem, you can set up phpstan/phpstan-symfony.

Just install the package, register rules (if you don't use phpstan/extension-installer) and prepare a script which will provide Symfony\Component\Console\Application to PHPStan.

Create file phpstan-console.php:

<?php declare(strict_types = 1);

use App\Bootstrap;
use Symfony\Component\Console\Application;

require __DIR__ . '/../vendor/autoload.php';

$configurator = Bootstrap::boot();
$configurator->setDebugMode(true);
$container = $configurator->createContainer();

return $container->getByType(Application::class);

Link file phpstan-console.php in phpstan.neon:

parameters:
	symfony:
		console_application_loader: phpstan-console.php

Extra commands

This package provides some extra commands specific for nette/di.

di:parameters

bin/console di:parameters

Dump parameters into console, sorted and highlighted.

di:parameters screenshot

If you do not have parameters export to DIC enabled (Option export > parameters of Nette\DI\Extensions\DIExtension is set to false), command will be not able to dump parameters. To bypass that limitation, enable backup of parameters just for scope of di:parameters command:

orisai.
	di:
		parameters:
			# true|false
			# Default: false
			backup: %consoleMode%

Helpers and helper set

While you can still use default helpers and register new helpers via $application->getHelperSet()->set(new ExampleHelper()) we don't provide any support for registering helpers in configuration. It's just not needed. Dependencies should be requested via constructor and rendering helpers can always be created via new ExampleHelper().

Events

symfony/event-dispatcher (and also symfony/event-dispatcher-contracts) is integrated into console via Symfony\Component\Console\ConsoleEvents. Our extension autoconfigures that integration, so you can use these events.

Multiple consoles

If you want commands separated by entrypoint, register multiple console extensions:

extensions:
	orisai.console: OriNette\Console\DI\ConsoleExtension
	custom.console: OriNette\Console\DI\ConsoleExtension

Create custom Application class for auto-wiring:

namespace App\Console;

use Symfony\Component\Console\Application;

final class CustomApplication extends Application
{

}
custom.console:
	autowired: App\Console\CustomApplication

Create new entrypoint for the second console, with the autowired type:

$application = $container->getByType(\App\Console\CustomApplication::class);

Configure command discovery so the second console registers only allowed commands:

custom.console:
	discovery:
		tag: anotherConsole.command