I created a command to do some data manipulation on a very large database table and as it takes fair enough time to complete, i took the benefits of progress bar plus echoing some information on the console.
to automate stuff and reduce human errors, i want to call my command inside a laravel migration using programmatically-executing-commands style and it works but the problem is it wont print any output from corresponding command inside the console
i think i should pass the current Output-buffer that artisan:migrate is using to the Artisan::call function to make it work but had no luck to access it inside the migration
any suggestions?
Expanding on #ettdro's answer, the Artisan::call method has the following signature:
Artisan::call(string $command, array $parameters = [], $outputBuffer = null);
As you can see, the method accepts an output buffer as its 3rd argument. You can pass that output buffer to the method and the command logs will show up on the console.
Here's an example:
<?php
use App\Console\Commands\YourConsoleCommand;
use Illuminate\Database\Migrations\Migration;
use Symfony\Component\Console\Output\ConsoleOutput;
class SomeDbMigration extends Migration
{
public function up()
{
$output = new ConsoleOutput();
Artisan::call(YourConsoleCommand::class, ['--some-option' => true], $output);
}
public function down()
{
$output = new ConsoleOutput();
Artisan::call(YourConsoleCommand::class, ['--some-option' => false], $output);
}
}
You could use ConsoleOutput provided by Symfony to print out in the console after calling Artisan command. Make sure to use it in your desired .php file like so use Symfony\Component\Console\Output\ConsoleOutput;.
You could have something like this:
$output = new ConsoleOutput();
$exitCode = Artisan::call('your call');
if ($exitCode == -1)
$output->writeln("<bg=red;options=bold>Error occured while migration rollback " . "Exit code: " . $exitCode ."</>");
else {
$output->writeln("<bg=blue;options=bold>Rollbacked successfully! Exit code: " . $exitCode ."</>");
}
See in my example you can also add colors to your text, that could be useful to have better visuals on errors and success, see more at this link: https://symfony.com/search?q=ConsoleOutput
Related
I have created custom artisan command and trying to get response of it.
Here is the handle method of custom artisan command.
/**
* Execute the console command.
*
* #return string
*/
public function handle()
{
return 'Hello world';
}
Calling command from the controller
$result = Artisan::output('app:custom-command');
dd($result); // 0
dd(Artisan::output()); // ''
Expecting 'Hello world' in the controller.
Please note I want response not the output
i.e Not the output of $this->info('test.');
Commands don't return spesific variable they return an exit code.
if you get "hello world" from command;
command:
$this->info('Hello World');
controller:
Artisan::call('app:custom-command');
return Artisan::output();
First, please check these answers here:
Get response from Artisan call
Just to give you an insight, here a small thing you can do:
You can pass a variable by reference to the constructor of the Command class
As an example:
// File => UserController
$commandInstance = new DemoCommand($output);
$commandInstance->hanlde(); // Calling the related method manually
// Then in the DemoCommand Class #constructur method
public function __construct(&$output){
$this->output = $output;
}
// File => DemoCommand #handle Method
$result = ['a', 'b' ]; // ...
$commandInstance->setOutput($result);
You can also use session, Redis to save the data in memory and read it from the related class you need or store the output into a file or database...
I want to trigger an email when new rows are added to a table in my Laravel application. However I want to add a buffer of sorts, so if 5 rows are added in quick succession then only 1 email is sent.
The method I've chosen is to schedule a check every 15 minutes and see if there are new rows added. If there are then I will queue an email.
Currently I'm getting an error on the schedule. I'll run through my code below:
In Kernel.php where we setup schedules I have:
$schedule->job(new ProcessActivity)
->everyFifteenMinutes()
->when(function () {
return \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->exists();
})
->onSuccess(function () {
Log::debug(
'Success'
);
})
->onFailure(function () {
Log::debug(
'Fail'
);
});
Which I use to trigger the Job found in: App\Jobs\ProcessActivity.php :
public function __construct()
{
$this->jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->get();
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Log::debug('Activity Job Run', ['jobs' => $this->jobs]);
$this->jobs->each(function ($item, $key) {
Log::debug('loop');
// get project
$project = $item->project;
// get project email
$user_id = $project->user_id;
$email = \App\User::find($user_id)->email;
// get project UUID
$projectUuid = $project->public_id;
// emails
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// create activity email
Notification::route('mail', $subscriberEmails)->notify(new Activity($project, $projectUuid));
});
return true;
}
I've posted my full code above which also shows a relationship between my JobItems and Project models. I won't elaborate on that as I've commented in the code.
The problem
When I add a new row to my JobItem table I can see the job is scheduled and processed (using Laravel Telescope to inspect this).
However, I can also see in my log that for each job I get two log messages:
First: 'Fail' and then 'Activity Job Run'
My email is not sent and I'm uncertain how to determine why this is failing.
So it seems that onFailure is being triggered and there is a problem with my ProcessActivity.
Any clues on where I am going wrong and how to determine the error would be much appreciated.
I have a fix, but first, here are some things I learnt that hampered my progress:
I was using this artisan command to process my scheduled jobs:
php artisan queue:work
The problem with developing while using that command is that if there are code changes then those changes are not recognised.
So you can either Command+C to return to the console and use this every time there is a code change:
php artisan queue:restart
php artisan queue:work
Or you can just use this and it will allow code changes:
php artisan queue:listen
As you can imagine without knowing this you will have a slow debugging process!
As a result of this and adding an exception to my Job I made some progress. I'll paste in the code below to compare against the original code:
public function __construct()
{
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
$jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(20), Carbon::now()))->get();
Log::debug('Activity Job', ['jobs' => $jobs]);
// collection start
$collection = collect();
// loop jobs to get emails
foreach ($jobs as $key => $value) {
// get project UUID
$project = $value->project;
$projectUuid = $project->public_id;
// get emails subscribed to projects
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// merge into a single collection via the loop
if ($key != 0) {
$merge = $collection->merge($subscriberEmails);
$collection = collect($merge);
} else {
$collection = $subscriberEmails;
}
// Log::debug('emails_project in loop', ['emails' => $subscriberEmails]);
};
// clean object with uniques only
$subscriberEmailsCleaned = $collection->unique();
// debug
Log::debug('Project Emails to Notify', ['emails' => $subscriberEmailsCleaned]);
// create activity email
Notification::route('mail', $subscriberEmailsCleaned)->notify(new Activity($project, $projectUuid));
} catch (\Exception $e) {
\Log::info($e->getMessage());
}
}
First thing to note, is that as __construct() is run initially and is serialised. Then the handle method is called when the job is processed. So I had to move my eloquent query into the handle method.
I also used a foreach rather than .each to loop through and create a new collection of emails. Perhaps there is a more elegant way, but I needed to create a collection of emails and this way allowed me to move the variables in the loop outside to be used in the method.
You can see me merge these at the bottom of the loop.
I have also added a few Log:: items which is useful for debugging.
Not fixed 100%
With this code I can now auto schedule an email every x minutes when new items are added. However, I am still getting the log Fail from the onFailure()from my Kernal.php file:
->onFailure(function () {
Log::debug(
'Fail'
);
I am still confused as to what that indicates and how I can determine more information about how this has failed and what that means. However, it does work so I will cautiously move forward (with one eye open on the comments, in case someone has an idea that can help!)
I use this for logging .
I tried to configure log rotation on but I'm stuck. I know how to do this with Laravel, I'm trying to create my own rotating log file in Laravel using Monolog, however, the file rotation is not working and I don't know why.
/* controller file */
use Illuminate\Http\Request;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
public function getSheduled(){
$log = new Logger('getSheduled');
$log->pushHandler(new RotatingFileHandler(storage_path().'/logs/cron_log/custom_log.log',2, Logger::INFO));
$log->info(json_encode($followup_shedule_data));
}
It seemed pretty straightforward to me, but it's simply not working. The log files are being generated correctly but when When I see their output it give me like this:
/*text file */
[2017-02-14 12:24:46] getSheduled.INFO: [] [] []
I don't want last 2 array from array.Please answer
Change the code as follows:
$lineFormatter = new \Monolog\Formatter\LineFormatter(null, null, true, true);
$log = new Logger('getSheduled');
$log->pushHandler((new RotatingFileHandler(storage_path().'/logs/cron_log/custom_log.log',2, Logger::INFO))->setFormatter($lineFormatter));
$log->info(json_encode($followup_shedule_data));
currently I have the following set up, a route that is calling a function in my controller that is in turn queuing a job.
//My Route
Route::get('/testJob', 'Controller#testJob');
//My Controller
public function testJob()
{
$job = (new testJob())->delay(5);
$this->dispatch($job);
}
//My job
public function handle()
{
require 'testAPICall.php';
// echo $response;
return $response;
}
//testAPICall.php
$response = 'this is the response';
//Queue After
Queue::after(function (JobProcessed $event) {
echo var_dump($event->data);
});
What I would like to be able to do, is access the response returned by the job in Queue::after, or alternatively, pass a callback into the queue to be execute after the job, again with access to the response from the job.
Is this something that is possible with Laravel Queues, and if so how would I go about this?
Cheers, Jack.
Queue::after() is a global callback, that will run after each job. So this might not what you want.
In your case, I would depend on Events/Listeners to be triggered after finishing the job.
public function handle(Mailer $mailer)
{
//Your code
event(new JobDone($data));
}
Please let me know if you need more details for implementation.
I have done something like yours that log a message "queue.txt" in laravel 5 "app" folder
This code I've got from a youtube video and not my code , but I have tested it successfully
First thing you have to code in "Routes.php" as below
Route::get('/',function()
{
//$queue = Queue::push('LogMessage',array('message'=>'Time: '.time()));
$queue = Queue::later(20,'LogMessage',array('message'=>'Time: '.time()));
return $queue;
});
class LogMessage{
public function fire($job,$data){
File::append(app_path().'/queue.txt',$data['message'].PHP_EOL);
$job->delete();
}
}
Then you can run your project folder using "php -S localhost:8888 -t public"
at the same time you must open a another terminal window in windows or linux environment and pointed to same folder and issue the command "php artisan queue:listen"
I think this will be helpful for you!
Is there something similar in Laravel that allows you to see the actual SQL being executed?
In Rails, for example, you can see the SQL in console. In Django you have a toolbar.
Is there something like that in Laravel 4?
To clarify: My question is how to do it without code. Is there something that is built-in in Laravel that does not require me to write code in app?
UPDATE: Preferably I'd like to see CLI queries as well (for example php artisan migrate)
If you are using Laravel 4, use this:
$queries = DB::getQueryLog();
$last_query = end($queries);
I do this in Laravel 4.
Just set it once in app/start/global.php or anywhere but make sure it is loaded and then it will start logging all your SQL queries.
Event::listen("illuminate.query", function($query, $bindings, $time, $name){
\Log::sql($query."\n");
\Log::sql(json_encode($bindings)."\n");
});
Here is a quick Javascript snippet you can throw onto your master page template.
As long as it's included, all queries will be output to your browser's Javascript Console.
It prints them in an easily readable list, making it simple to browse around your site and see what queries are executing on each page.
When you're done debugging, just remove it from your template.
<script type="text/javascript">
var queries = {{ json_encode(DB::getQueryLog()) }};
console.log('/****************************** Database Queries ******************************/');
console.log(' ');
$.each(queries, function(id, query) {
console.log(' ' + query.time + ' | ' + query.query + ' | ' + query.bindings[0]);
});
console.log(' ');
console.log('/****************************** End Queries ***********************************/');
</script>
There is a Composer package for that: https://packagist.org/packages/loic-sharma/profiler
It will give you a toolbar at the bottom with SQL queries, log messages, etc. Make sure you set debug to true in your configuration.
Here's another nice debugging option for Laravel 4:
https://github.com/barryvdh/laravel-debugbar
I came up with a really simple way (if you are using php artisan serve and PHP 5.4) - add this to app/start/local.php:
DB::listen(function($sql, $bindings, $time)
{
file_put_contents('php://stderr', "[SQL] {$sql} in {$time} s\n" .
" bindinds: ".json_encode($bindings)."\n");
});
but hoping to find a more official solution.
This will print SQL statements like this:
[SQL] select 1 in 0.06s
This code is directly taken form other source but i wanted to make it easy for you as follow it worked for me on PHPStorm using my terminal window i was able to see a complete log but ,after login there was some Sentry thing.
1.add
'log'=>true
inside your config/database.php and below the place ur database name ex.mysql
then add below code toroutes.php above all no under any route configuration , since u can make that under a give route configuration but , u only see when that route is called.
to see this output /goto / app/storage/log/somelogfile.log
if (Config::get('database.log', false))
{
Event::listen('illuminate.query', function($query, $bindings, $time, $name)
{
$data = compact('bindings', 'time', 'name');
// Format binding data for sql insertion
foreach ($bindings as $i => $binding)
{
if ($binding instanceof \DateTime)
{
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
}
else if (is_string($binding))
{
$bindings[$i] = "'$binding'";
}
}
// Insert bindings into query
$query = str_replace(array('%', '?'), array('%%', '%s'), $query);
$query = vsprintf($query, $bindings);
Log::info($query, $data);
});
}
Dont forget to make break point .... or ping me :)
In QueryBuilder instance there is a method toSql().
echo DB::table('employees')->toSql()
would return:
select * from `employees`
This is the easiest method to shows the queries.