Jak spustit Presenter:akce z konzole / CLI

ludek
Člen | 83
+
0
-

Zdravím,

mám napsanou akci na posílání emailů.

Když to spustím v prohlížeči http://localhost/test/www/email/daily, bez problémů funguje.

Není to nic zvláštního (vizte níže), ale dědí to z BasePresenteru, využívá šablon, atd.

DOTAZ: co mám udělat, abych to mohl spustit z konzole (CLI) každý den pomocí cronu?
Jaký je nejjednodušší způsob jak z konzole zavolat to, co jsem si v prohlížeči otestoval?

Našel jsem:

  • php -d display_errors=1 -f index.php Email:daily → neudělá nic, nevypíše ani žádné chyby
  • Kdyby\Console a Contributte\Console, ale nechápu, jestli je to nástroj k tomu, co potřebuju. Umožní mi to spustit bin/console Email:daily?

Díky.
_______________
Co se má spouštět vypadá takto:

namespace App\Presenters;

use Nette;
use Nette\Mail\Message;
use Nette\Mail\SendmailMailer;

final class EmailPresenter extends BasePresenter {

    /** @var Nette\Mail\IMailer Mailer */
    private $mailer;

    /**
     * @param Nette\Mail\IMailer $mailer
     */
    public function injectMailer(Nette\Mail\IMailer $mailer)  {
        $this->mailer = $mailer;
    }

    public function renderDaily(): void  {

	$users = $this->repository->table('users')->fetchAll();

	foreach ($users as $u) {
	    $stuff = $this->repository->table('stuff')->where('users_id', $u->id);
	    $this->sendMail($u, $stuff);
	    sleep(1);
	}
	$this->terminate();
    }

    public function sendMail(Nette\Database\Table\ActiveRow $user, Nette\Database\Table\Selection $stuff): void {

	$latte = new \Latte\Engine;
	$params['stuff'] = $stuff;
	$params['user'] = $user;

	$subject = 'Your stuff ' . $this->today->format('d-m-Y');

	$mail = new Message;

	$sendTo = $user->email;

	$mail->setFrom('robot@domain.com')
		->addTo($sendTo)
		->setSubject($subject)
		->setHtmlBody($latte->renderToString(__DIR__ . '/../templates/email.latte', $params));

	try { $this->mailer->send($mail); echo "<br> SENT " . $sendTo; }
	catch (Nette\Mail\SendException $e) { echo " ERROR " . $e->getMessage(); }
    }

}
Milo
Nette Core | 1283
+
+2
-

Jestli to máš takhle hotové jako akci, nejsnazší je z cronu zavolat wget http://localhost/test/www/email/daily

Polki
Člen | 553
+
+4
-

Contributte/Console
udělá práci za tebe, akorát pro ni musíš tu knihovnu na odesílání mailů upravit, aby byla přes tu knihovnu spustitelná viz dokumentace.

Takže by tvoje třída vypadala asi takto:

/**
 *
 * @cron 0 * * * *    // definice času spouštění jako u klasického cronu.
 */
final class EmailCronCommand extends Command {

	protected static $defaultName = 'email:send';

    /** @var Nette\Mail\IMailer Mailer */
    private $mailer;

    public function __construct(Nette\Mail\IMailer $mailer)  {
        $this->mailer = $mailer;
		// další inject věci
    }

	protected function configure(): void
	{
		$this->setName(self::$defaultName);
		$this->setDescription('This is command for execute emails.'); // Použij lepší hlášku
	}

    public function execute(InputInterface $input, OutputInterface $output): void  {

		$users = $this->repository->table('users')->fetchAll();

		foreach ($users as $u) {
	    	$stuff = $this->repository->table('stuff')->where('users_id', $u->id);
	    	$this->sendMail($u, $stuff);
	    	sleep(1);
		}
    }

    public function sendMail(Nette\Database\Table\ActiveRow $user, Nette\Database\Table\Selection $stuff): void {

		$latte = new \Latte\Engine;
		$params['stuff'] = $stuff;
		$params['user'] = $user;

		$subject = 'Your stuff ' . $this->today->format('d-m-Y');

		$mail = new Message;

		$sendTo = $user->email;

		$mail->setFrom('robot@domain.com')
			->addTo($sendTo)
			->setSubject($subject)
			->setHtmlBody($latte->renderToString(__DIR__ . '/../templates/email.latte', $params));

		try { $this->mailer->send($mail); echo "<br> SENT " . $sendTo; }
		catch (Nette\Mail\SendException $e) { echo " ERROR " . $e->getMessage(); }
    }
}

a spouštěla se takto:

$ php console.php email:send

Doufám, že jsem nic nepopletl případně mě tvůrci console opravte, nebo ukamenujte :D

Pokud nechceš použít contributte/console, tak můžeš alternativně si to spustit sám:
soubor s kódem:


final class EmailCronCommand{

    /** @var Nette\Mail\IMailer Mailer */
    private $mailer;

    public function __construct(Nette\Mail\IMailer $mailer)  {
        $this->mailer = $mailer;
		// další inject věci
    }

    public function execute(): void  {

		$users = $this->repository->table('users')->fetchAll();

		foreach ($users as $u) {
	    	$stuff = $this->repository->table('stuff')->where('users_id', $u->id);
	    	$this->sendMail($u, $stuff);
	    	sleep(1);
		}
    }

    public function sendMail(Nette\Database\Table\ActiveRow $user, Nette\Database\Table\Selection $stuff): void {

		$latte = new \Latte\Engine;
		$params['stuff'] = $stuff;
		$params['user'] = $user;

		$subject = 'Your stuff ' . $this->today->format('d-m-Y');

		$mail = new Message;

		$sendTo = $user->email;

		$mail->setFrom('robot@domain.com')
			->addTo($sendTo)
			->setSubject($subject)
			->setHtmlBody($latte->renderToString(__DIR__ . '/../templates/email.latte', $params));

		try { $this->mailer->send($mail); echo "<br> SENT " . $sendTo; }
		catch (Nette\Mail\SendException $e) { echo " ERROR " . $e->getMessage(); }
    }
}

config:

services:
	- Namespace\EmailCronCommand

napřklad emailCron.php:

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

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

exit(App\Bootstrap::boot()
	->createContainer()
	->getByType(Namespace\EmailCronCommand::class)
	->execute());

spuštění:

php emailCron.php

Rozdíl, mezi těmito 2 přístupy je, že contributte/console ti všechny crony zpřístupní přes 1 entrypoint a můžeš je spouštět podle parametru, což je strašně super. Dál má další fičury, co ti pomůžou, ale pokud chceš používat to, jak jsem zmínil v anotaci @cron, tak musíš si k tomu napsat vlastní obslužný skript (případně dodám).
Druhé řešení má zase tu výhodu, že není závislé na žádné knihovně a když se autor knihovny rozhodne nepokračovat a nebude mít následníky, nemusíš překopávat aplikace. (Což si myslím v případě Contributte nehrozí)

Oba příkazy pak můžeš spouštět cyklicky cronem, takže z pohledu spuštění je to jedno, co použiješ. Záleží, jak se cítíš. Nicméně celá logika by měla být v modelu a ne v Presenteru. Kterákoliv z těch 2 tříd, co jsem popsal nahoře je modelová service, která funguje ať ji spouštíš z presenteru, nebo z CLI. No a z presenteru se to pak dá spouštět takto:

final class EmailPresenter extends BasePresenter {

    /** @var EmailCronCommand Mailer */
    private $mailer;

    public function injectMailer(EmailCronCommand $mailer)  {
        $this->mailer = $mailer;
    }

	public function renderDaily(): void  {
		$this->mailer->execute();
 	}
}

a volat pak jak psal Milo wget http://localhost/test/www/email/daily

Otázka je, jestli chceš to mít přístupné přes URL. Pokud ne, tak když to je napsáno správně (logika v service ne v Presenteru), tak tu servicu je jedno odkud spustíš, takže ji můžeš klidně spouštět přes CLI

Editoval Polki (7. 10. 2021 0:40)

ludek
Člen | 83
+
+1
-

@Milo wget mě taky napadl a původně jsem to tak udělal. Ale i když by to asi nebyl problém, spíše z principu nechci, aby to bylo přístupné přes URL. Díky.

@Polki Skvělá odpověď!

Napsal jsem modelovou třídu. Víc cronů nepotřebuju a tak je asi lepší vyhnout se závislosti na Contributte/Console.

1. modelová třída (umístit do app/model/ModelovaTrida.php)

<?php
namespace App\Model;

use Nette;
use Nette\Mail\Message;
use Nette\Mail\SendmailMailer;

final class ModelovaTrida {

    use Nette\SmartObject;

    /** @var Nette\Database\Context Pristup k databazi */
    private $repository;

    /** @var Nette\Mail\IMailer Pro posilani emailu */
    private $mailer;

    // předat závislosti (spojení na databázi a mailer)
    public function __construct(Nette\Database\Context $database, Nette\Mail\IMailer $mailer)  {
        $this->repository = $database;
		$this->mailer = $mailer;
    }

    /**
     * Nějaké úkony s databází, šablonami a posíláním emailů
     * @return void
     */
    public function execute(): void {

	$params = $this->repository->table('table');
	$latte  = new \Latte\Engine;
	$mail   = new Message;
	$mail->setFrom('email@domain.com')
		->addTo('email@domain.com')
		->setSubject('Předmět zprávy')
		->setHtmlBody($latte->renderToString(__DIR__ . '/../templates/email.latte', $params));

	try { $this->mailer->send($mail); echo "\nSENT ". date('Y-m-d H:i:s');	}
	catch (Nette\Mail\SendException $e) { echo "\nERROR ".$e->getMessage(); }
    }
} // class

2. zaregistrovat v config/local.neon

services:
	- App\Model\ModelovaTrida

2.5 smazat cache (když to neudělám, v TRACY/DIC panelu to vidím, ale při spuštění z konzole napíše, že neexistuje)

3. spouštěcí script /cron/spustit-modelovou-tridu.php

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

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

exit(App\Bootstrap::boot()
	->createContainer()
	->getByType(App\Model\ModelovaTrida::class)
	->execute());

4. spustit z konzole v /cron

php spustit-modelovou-tridu.php

Funguje perfektně. Ještě jednou moc díky za vyčerpávající odpověď.

Polki
Člen | 553
+
0
-

@ludek Super. Jsem rád, že to pomohlo.

Marek Znojil
Člen | 76
+
+1
-

@ludek Tady se můžeš ještě inspirovat jak si popřípadě zařídit i rozdílnou konfiguraci přímo pro tasky.