I've built a command in Symfony 4 which works fine when run from CLI but doesn't execute when run by cron. The console command is being run and no errors are thrown.
I've even thrown one in execute and it does not fail/error out:
public function execute(InputInterface $input, OutputInterface $output): void
{
throw new \Exception('I am doing something');
}
My full command looks like this and is autowired:
<?php declare(strict_types = 1);
namespace CRMInterface\Command\Customer;
use CRMInterface\Service\Customer\CustomerSyncService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CustomerSyncCommand extends Command
{
const COMMAND_NAME = 'crm:sync:customer';
/**
* #var CustomerSyncService
*/
private $syncService;
/**
* #var LoggerInterface
*/
private $logger;
/**
* #param CustomerSyncService $syncService
* #param LoggerInterface $logger
*/
public function __construct(CustomerSyncService $syncService, LoggerInterface $logger)
{
parent::__construct(self::COMMAND_NAME);
$this->syncService = $syncService;
$this->logger = $logger;
}
protected function configure()
{
$this
->setName(self::COMMAND_NAME)
->setDescription('Processes outstanding portal sync tasks');
}
/**
* #param InputInterface $input
* #param OutputInterface $output
* #return void
*/
public function execute(InputInterface $input, OutputInterface $output): void
{
$this->logger->info('Syncing customers...');
try {
$this->syncService->sync();
$this->logger->info('Customer sync complete.');
} catch (\Exception $e) {
$this->logger->error('Customer sync failed: ' . $e->getMessage());
}
}
}
My cron job is as follows:
*/3 * * * * www-data cd /var/www/html && /usr/local/bin/php bin/console crm:sync:customer --env=prod
This set up works in a Symfony 3 app and a Symfony 2.8 app I have running but not with 4 and it's driving me batty.
My bin/console is as follows - I've taken out the stuff to do with APP_ENV because it was superfluous in my case and was failing due to the lack of env vars in cron.
#!/usr/bin/env php
<?php
use CRMInterface\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;
set_time_limit(0);
require __DIR__.'/../vendor/autoload.php';
if (!class_exists(Application::class)) {
throw new \RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}
$input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], $_ENV['APP_ENV'] ?? 'dev');
$debug = ($_ENV['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption(['--no-debug', '']);
if ($debug) {
umask(0000);
if (class_exists(Debug::class)) {
Debug::enable();
}
}
$kernel = new Kernel($env, $debug);
$application = new Application($kernel);
$application->run($input);
Can anyone point me in the right direction as to why the command is running but not getting down to execute?
It's almost as if it's just running bin/console without the command... could it be something to do with lazy loading?
For those having similar issues with commands not making execution with no errors, check that cron has access to all the environment variables your application uses.
In this case, there was an exception that was thrown, but caught by the Symfony Application class. Environment variable not found: "ELASTICSEARCH_INDEX".. Unfortunately, it just continued on as if it had run. (May create an issue over at the repo).
For Docker on an Ubuntu based apache container, the solution was fairly simple - add a line in the entrypoint script to write the environment variables into the /etc/environment file:
FROM php:7.2
EXPOSE 80 443
CMD ["sh", "./.docker/run.sh"]
run.sh:
#!/usr/bin/env bash
printenv | grep -v "no_proxy" >> /etc/environment \
&& /etc/init.d/cron start \
&& apache2-foreground
Similar to the previous answer, but make sure you add the cron job to the proper user.
Example: crontab -u www-data -e
Edit the cron jobs by updating or adding
*/3 * * * * /usr/local/bin/php /var/www/html/bin/console crm:sync:customer --env=prod
You can add some testing on the command such as the following
0 * * * * if [ -f /var/www/html/bin/console ];
then /var/www/html/bin/console crm:sync:customer --env=prod >>
/var/log/symfony/crm.log; else echo 'console not found'
/var/log/symfony/crm.log; fi >/dev/null 2>&1
Related
Objective
I am writing phpunits tests, and I want to get the code coverage.
Problem
To do End To End tests, I have a Php build in server to setup my API, then the tests call this api.
But everything that is being executed by the server is not in the report.
For exemple I tested a controller with api calls, and on my report there is 0%.
All the file that are tested without the Php build int server are ok, it's only the tests that works with that are not counted.
Is there a way to count it ?
Some code
I am using a php that create an http server to do end to end tests on my api.
Here is my class
<?php
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Process\Process;
class ApiTestCase extends \PHPUnit\Framework\TestCase
{
protected static string $public_directory;
protected static Process $process;
const ENVIRONMENT = 'test';
const HOST = '0.0.0.0';
const PORT = 9876; // Adjust this to a port you're sure is free
public static function setUpBeforeClass(): void
{
$command = [
'php',
'-d',
'variables_order=EGPCS',
'-S',
self::HOST . ':' . self::PORT,
'-t',
self::$public_directory,
self::$public_directory.'/index.php'
];
// Using Symfony/Process to get a handler for starting a new process
self::$process = new Process($command, null, [
'APP_ENV' => self::ENVIRONMENT
]);
// Disabling the output, otherwise the process might hang after too much output
self::$process->disableOutput();
// Actually execute the command and start the process
self::$process->start();
// Let's give the server some leeway to fully start
usleep(100000);
}
public static function tearDownAfterClass(): void
{
self::$process->stop();
}
/**
* #param array<string,mixed>|null $data
* #param string $path
* #param string $method
* #return ResponseInterface
* #throws \GuzzleHttp\Exception\GuzzleException
*/
protected function dispatch(string $path, string $method = 'POST', ?array $data = null): ResponseInterface
{
$params = [];
if ($data) {
$params['form_params'] = $data;
}
$client = new Client(['base_uri' => 'http://127.0.0.1:' . self::PORT]);
return $client->request($method, $path, $params);
}
}
So in my test i can use it like that and it works fine
class MyApiTest extends \App\Tests\ApiDbTestCase
{
public function testAuthClientSuccess()
{
// creation of $parameters
$res = $this->dispatch('/v1/user/my/url', 'POST', $parameters);
// My asserts are done after that
}
}
I am using github action to create
name: Phpunit coverage
on: [push]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- uses: php-actions/composer#v6
- uses: php-actions/phpunit#v3
with:
php_extensions: pdo_pgsql xdebug
args: --coverage-html
env:
XDEBUG_MODE: coverage
- name: Archive code coverage results
uses: actions/upload-artifact#v2
with:
name: code-coverage-report
path: output/code-coverage
Okay, so I basically need to generate a json file using Laravel's scheduler and a custom artisan command (said list is actually a list of popular cities in our application). So I went ahead and did just that. Here's the definition of my Artisan command:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\PlaceService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
class CitySearch extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'city:search {--locale=}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Generates the list of the most popular cities to be used across the application when we need it.';
private $placesService;
/**
* Create a new command instance.
*
* #return void
*/
public function __construct(PlaceService $placesService)
{
$this->placesService = $placesService;
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
App::setLocale( $this->option('locale') );
$request = Request::create(route('api.search-places'), 'GET', ['maxResults' => 3000, 'isArtisan' => true]);
$request->headers->set('Accept', 'application/json');
$request->headers->set('Api-Key', 'aaaaaaaaaaaa');
// $request->headers->set('Api-Key', '43JSOSH333KSOH555WHO99');
$request->headers->set('App-client', 'web');
$response = app()->handle($request);
$content = json_decode($response->getContent());
$results = array_map(function($element){
if($element->type == "City")
$context = ['an', 'se', 'me'];
else
$context = ['se'];
return ['displayName' => $element->displayName, 'context' => $context];
}, $content->data);
print(json_encode($results));
}
}
Then I went into the scheduler and added the following to have the command run once a week:
namespace App\Console;
use App\Console\Commands\Admin\RedFalcon\PendingTransactionNotificator;
use App\Console\Commands\Admin\RedFalcon\FraudTransactionNotificator;
use App\Console\Commands\CardGenerate;
use App\Console\Commands\Communauto\Stats;
use App\Console\Commands\CPS\Archiver;
use App\Console\Commands\CPS\AutoCasher;
use App\Console\Commands\CPS\Autofixer;
use App\Console\Commands\CPS\Beta\Testers;
use App\Console\Commands\CPS\BNC\EmailFailedBankTransaction;
use App\Console\Commands\CPS\BNC\FeedComposer;
use App\Console\Commands\CPS\BNC\Feeder;
use App\Console\Commands\CPS\BNC\FeedMediator;
use App\Console\Commands\CPS\BNC\FeedReporter;
use App\Console\Commands\CPS\BNC\Parametrizer;
use App\Console\Commands\CPS\Captor;
use App\Console\Commands\CPS\ConfirmationFix;
use App\Console\Commands\CPS\ConfirmationCodeRemoval;
use App\Console\Commands\CPS\DB\RideArchiver;
use App\Console\Commands\CPS\DB\RideFixer;
use App\Console\Commands\CPS\Rider;
use App\Console\Commands\CPS\Test\Resetter;
use App\Console\Commands\CPS\Transactor;
use App\Console\Commands\CPS\Troubleshooting\Experiment1;
use App\Console\Commands\Notifications\ApnFeedbackService;
use App\Console\Commands\RelavelTester;
use App\Console\Commands\UpdateCityPopularity;
use App\Console\Commands\Util\FixBankTransactionTable;
use App\Console\Commands\Util\FixPreauthorizationTable;
use App\Console\Commands\Util\ResetPassword;
use App\Console\Commands\CitySearch;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\Log;
class Kernel extends ConsoleKernel
{
protected $list;
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
CardGenerate::class,
Rider::class,
Autofixer::class,
Captor::class,
Transactor::class,
Archiver::class,
FeedComposer::class,
FeedMediator::class,
Feeder::class,
Parametrizer::class,
RideFixer::class,
RideArchiver::class,
RelavelTester::class,
FixPreauthorizationTable::class,
PendingTransactionNotificator::class,
FraudTransactionNotificator::class,
FixBankTransactionTable::class,
Resetter::class,
Testers::class,
Stats::class,
Experiment1::class,
FeedReporter::class,
ResetPassword::class,
AutoCasher::class,
ConfirmationFix::class,
ConfirmationCodeRemoval::class,
CitySearch::class,
UpdateCityPopularity::class,
EmailFailedBankTransaction::class,
ApnFeedbackService::class
];
/**
* Define the application's command schedule.
*
* #param \Illuminate\Console\Scheduling\Schedule $schedule
* #return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('city:search --locale=fr')
->mondays()
->at('14:40')
->sendOutputTo(storage_path() . "/app/city-fr.json");
$schedule->command('city:search --locale=en')
->mondays()
->at('14:40')
->sendOutputTo(storage_path() . "/app/city-en.json");
}
/**
* Register the Closure based commands for the application.
*
* #return void
*/
protected function commands()
{
require base_path('routes/console.php');
}
}
Now, this works relatively well... except sometimes it crashes. When that happens, the two json files become filled with error messages instead of the actual data. What I'd like to do is basically save the original list before the command executes and in case something fails, I'd like to output that list into the file and log the error. Right now, everything goes into the file and of course, I get a truckload of errors in my application because the city list is invalid.
Since I'm in Laravel 5.5 I tried using the "before" and "after" hooks (onFailure and onSuccess not available in my version of the framework) and came up with this:
$schedule->command('city:search --locale=fr')
->everyMinute()
->before( function(){
$this->list = json_decode(file_get_contents(storage_path() . "/app/city-fr.json"));
})
->after(function(){
$test = json_decode(file_get_contents(storage_path() . "/app/city-fr.json"));
Log::debug($test);
})
->sendOutputTo(storage_path() . "/app/city-fr.json");
So, while I can successfully get the original list from the file before the process begins, in the "after" hook, the file is still empty so there's no way for me to know whether the process failed or not.
Does anyone know how I should go about this? It feels like the solution is right in front of my face, but I'm just missing it.
Okay, this is a faceplant moment for me. Turns out in the 'after' hook, I did have access to the file, but the reason my output was empty was that the json_decode method returned false because the content of the file wasn't valid json (which was what I was trying to test from the start). Anyway, once I finished picking up the pieces of my scattered brain, the following turned out to work perfectly:
$schedule->command('city:search --locale=fr')
->everyMinute()
->before( function(){
$this->list = file_get_contents(storage_path() . "/app/city-fr.json");
})
->after(function(){
if(!json_decode(file_get_contents(storage_path() . "/app/city-fr.json")))
{
$fp = fopen(storage_path() . "/app/city-fr.json", 'w');
fwrite($fp, $this->list);
fclose($fp);
}
})
->sendOutputTo(storage_path() . "/app/city-fr.json");
after migration Symfony from 3.3 to 3.4, my function not working (it works before). I have to clear cache in controller, and when I execute command below, function returns error.
exec(sprintf(
"php %s/bin/console cache:clear --env=prod",
$this->getParameter('kernel.project_dir')
));
It returns something like that:
Fatal error: require(): Failed opening required '/[...]/var/cache/prod/ContainerAcrshql/getTwig_ExceptionListenerService.php' (include_path='.:/usr/local/share/pear') in /[...]/var/cache/prod/ContainerAcrshql/appProdProjectContainer.php on line 764 Fatal error: require(): Failed opening required '/[...]/var/cache/prod/ContainerAcrshql/getSwiftmailer_EmailSender_ListenerService.php' (include_path='.:/usr/local/share/pear') in /[...]/var/cache/prod/ContainerAcrshql/appProdProjectContainer.php on line 764
In addition I can tell You, that in dev environment it works properly. Also when project run localy and simulate prod env (in address bar I type app.php after localhost:8000). I haven't other server to check if problem still occured
I'm calling an already implemented Symfony's command that clear or warmup cache (tested on Symfony 4).
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
class CommandController extends AbstractController
{
/**
*
* #Route("/command/cache/clear", name="command_cache_clear")
*/
public function command_cache_clear(KernelInterface $kernel)
{
return $this->do_command($kernel, 'cache:clear');
}
/**
*
* #Route("/command/cache/warmup", name="command_cache_warmup")
*/
public function command_cache_warmup(KernelInterface $kernel)
{
return $this->do_command($kernel, 'cache:warmup');
}
private function do_command($kernel, $command)
{
$env = $kernel->getEnvironment();
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => $command,
'--env' => $env
));
$output = new BufferedOutput();
$application->run($input, $output);
$content = $output->fetch();
return new Response($content);
}
}
You should add a valid permissions to the var/ directory to access to cache files:
chmod ... var/ -R
The user used when accessing from web is www-data
laravel Console command...
public function handle()
{
$stores_id = $this->stores->where('status', '=', 'Active')->orderBy('code')->get(['code'])->toarray();
//-------------error i think
$stockHolddata = DB::connection('oracle')->table('mmpl.V_STOCK_ONHAND')
->whereIn('ADMSITE_CODE',$stores_id)
->where('QTY','!=','0')
->get(['BARCODE','ADMSITE_CODE','QTY'])->toJson();
//----------------------
$jsondata = json_decode($stockHolddata,true);
if(!empty($jsondata[0])){
DB::table('v_stock_onhand')->truncate();
}
foreach ($jsondata as $val){
VStockOnhand::create($val);
}
}
VStockOnhand is a laravel mysql model.....
it runs fine if I directly run php artisan InsertOracle:stock in terminal
it won't work on cron job schedule
kernel.php
protected $commands = [
'App\Console\Commands\InsertOraclestock',
];
protected function schedule(Schedule $schedule)
{
$schedule->command('InsertOracle:stock')->dailyAt('23:00')->withoutOverlapping();
}
cron job code on crontab -e
* * * * * /usr/bin/php7.0 /var/www/rep/qa/artisan schedule:run >> /var/www/rep/qa/example.txt
I assumed that by default the Log::info calls wouldn't log in production, but they are still coming in.
Im setting production using my .env file
APP_ENV=production
APP_DEBUG=false
Ive tried these commands as well, but no luck
composer dump-autoload
php artisan cache:clear
php artisan optimize
Am i missing something?
For anyone still finding this thread (8 years later):
Configure your log channels in config/logging.php file
Set "level" parameter for your log channel to a .env variable
Example:
'channels' => [
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Lumen Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'error'),
]
]
Now you can set the LOG_LEVEL variable in your .env file for each environment
Well, I think that it's too late to search for all the Log::info() and do the proposed answer by #jon__o
if (App::environment('local', 'staging')) {
Log::info($error);
}
But you can still do something. You can override the default Laravel logger instance with your own implementation.
Go to your ApplicationServiceProvider and override the log instance with a custom one:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerLogger();
}
/**
* Register the logger instance in the container.
*
* #return MyCustomWriter
*/
protected function registerLogger()
{
$this->app->instance('log', $log = new MyCustomWriter(
new Monolog($this->app->environment()), $app['events'])
);
$log->dontLogInfoOnEnvironmnets(['production', 'staging', 'other']);
return $log;
}
Now you can create your custom writer by just extending the Laravel's Writer and overriding the info() method.
class MyCustomWriter extends \Illuminate\Log\Writer
{
protected $dontInfoOn = [];
/**
* Log an informational message to the logs.
*
* #param string $message
* #param array $context
* #return void
*/
public function info($message, array $context = [])
{
// Since we are providing the app environment to the Monolog instance in out ApplicationServiceProvider
// we can get the environment from the Monolog getName() method
if(!in_array($this->monolog->getName(), $this->dontInfoOn)) {
return parent::info($message, $context);
}
}
/**
* Don't log info() on the supplied environments .
*
* #param array $environments
* #return void
*/
public function dontLogInfoOnEnvironmnets(array $environments)
{
$this->dontInfoOn = $environments;
}
}
This way, you can still keep you Log::info on testing environments without checking every time.
Only the displaying of errors will be suppressed when your application is not in debug mode. The Log::info() function will always log when called.
The simple solution is for you to wrap that Log::info() function in something like this:
if (App::environment('local', 'staging')) {
Log::info($error);
}
Be sure to include the App facade use App; at the top of your file. Alternatively you can use the app() helper to get the environment: $environment = app()->environment();.