How to override specific methods in Laravel core classes? - php

To be even more specific and provide the most basic example of one of the methods that I would like to override is the ScheduleRunCommand::handle() method.
(Specifically, would like to change the messaging for when nothing is ready to run to include a timestamp in the string.)
ScheduleRunCommand.php
public function handle(Schedule $schedule, Dispatcher $dispatcher, ExceptionHandler $handler)
{
$this->schedule = $schedule;
$this->dispatcher = $dispatcher;
$this->handler = $handler;
foreach ($this->schedule->dueEvents($this->laravel) as $event) {
if (! $event->filtersPass($this->laravel)) {
$this->dispatcher->dispatch(new ScheduledTaskSkipped($event));
continue;
}
if ($event->onOneServer) {
$this->runSingleServerEvent($event);
} else {
$this->runEvent($event);
}
$this->eventsRan = true;
}
if (! $this->eventsRan) {
$this->info('No scheduled commands are ready to run.');
}
}
Just to be clear, I do not want to edit this core file directly, just extend, specifically overriding that method with an updated message for when no events ran.

You have to create a new command that extends ScheduleRunCommand.
There, you extend the base command and you do whatever you want.
This command with automatically override the original command.
<?php namespace App\Console\Commands;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Events\Dispatcher;
class ScheduleRunCommand extends \Illuminate\Console\Scheduling\ScheduleRunCommand
{
public function handle(Schedule $schedule, Dispatcher $dispatcher, ExceptionHandler $handler)
{
dd("test");
parent::handle($schedule, $dispatcher, $handler);
}
}
If you did all this correctly, running php artisan schedule:run will execute the handle function of your new class:
test#project:~/code$ php artisan schedule:run
"test"
To specifically answer your question, since you can't only override No scheduled commands are ready to run., you must copy / paste the entire handle function and modify the message directly:
<?php namespace App\Console\Commands;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Events\Dispatcher;
class ScheduleRunCommand extends \Illuminate\Console\Scheduling\ScheduleRunCommand
{
public function handle(Schedule $schedule, Dispatcher $dispatcher, ExceptionHandler $handler)
{
$this->schedule = $schedule;
$this->dispatcher = $dispatcher;
$this->handler = $handler;
foreach ($this->schedule->dueEvents($this->laravel) as $event) {
if (! $event->filtersPass($this->laravel)) {
$this->dispatcher->dispatch(new ScheduledTaskSkipped($event));
continue;
}
if ($event->onOneServer) {
$this->runSingleServerEvent($event);
} else {
$this->runEvent($event);
}
$this->eventsRan = true;
}
if (! $this->eventsRan) {
$this->info('[.'.date('Y-m-d H:i:s').'] No scheduled commands are ready to run.');
}
}
}

Related

How to test Laravel 5 jobs?

I try to catch an event, when job is completed
Test code:
class MyTest extends TestCase {
public function testJobsEvents ()
{
Queue::after(function (JobProcessed $event) {
// if ( $job is 'MyJob1' ) then do test
dump($event->job->payload());
$event->job->payload()
});
$response = $this->post('/api/user', [ 'test' => 'data' ], $this->headers);
$response->assertSuccessful($response->isOk());
}
}
method in UserController:
public function userAction (Request $request) {
MyJob1::dispatch($request->toArray());
MyJob2::dispatch($request->toArray());
return response(null, 200);
}
My job:
class Job1 implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $data = [];
public function __construct($data)
{
$this->data= $data;
}
public function handle()
{
// Process uploaded
}
}
I need to check some data after job is complete but I get serialized data from
$event->job->payload() in Queue::after And I don't understand how to check job ?
Well, to test the logic inside handle method you just need to instantiate the job class & invoke the handle method.
public function testJobsEvents()
{
$job = new \App\Jobs\YourJob;
$job->handle();
// Assert the side effect of your job...
}
Remember, a job is just a class after all.
Laravel version ^5 || ^7
Synchronous Dispatching
If you would like to dispatch a job immediately (synchronously), you may use the dispatchNow method. When using this method, the job will not be queued and will be run immediately within the current process:
Job::dispatchNow()
Laravel 8 update
<?php
namespace Tests\Feature;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped()
{
Bus::fake();
// Perform order shipping...
// Assert that a job was dispatched...
Bus::assertDispatched(ShipOrder::class);
// Assert a job was not dispatched...
Bus::assertNotDispatched(AnotherJob::class);
}
}
This my generic method, using a route
Route::get('job-tester/{job}', function ($job) {
if(env('APP_ENV') == 'local'){
$j = "\\App\Jobs\\".$job;
$j::dispatch();
}
});

Adding custom methods to Laravel 5 scheduler

I'm wondering what the best way to add things like weekends to the available schedule constraints:
Illuminate\Console\Scheduling\Event.php
public function weekdays()
{
return $this->spliceIntoPosition(5, '1-5');
}
and its logical opposite:
public function weekends()
{
return $this->days(array( '0','6'));
}
Where would I include these things so that they're not overwritten with a framework update?
First of all, if all you are missing is the weekends() method, you can achieve that by calling days(6,7) on your event.
If you need to add some more logic to the scheduler, please go on reading.
I had a look at the code and while Laravel doesn't offer a way to extend the Scheduler, and specifically its scheduled Events, with additional methoods, it's still possible to apply additional constraints from your Kernel::schedule().
Depending on your needs, there are 2 ways to do it.
If you want to set some custom CRON expression for an event, you can simply use its cron() method:
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
//scheduled code
})->cron('0 1 2 * * *')->daily();
}
If you need to apply some CRON constraints using existing methods, but need to modify it later the way weekdays() does using spliceIntoPosition, you can access it by calling getExpression(), modify it, and then set using cron().
protected function schedule(Schedule $schedule)
{
$event = $schedule->call(function () {
//scheduled code
});
$scheduledAt = $event->getExpression(); //get cron expression
...; //modify the $scheduledAt expression
$event->cron($scheduledAt); // set the new schedule for that even
}
If you want to reuse the logic for multiple events, you can add helper functions in your Kernel.php, e.g.:
protected function specialSchedule(\Illuminate\Console\Scheduling\Event $event) {
$scheduledAt = $event->getExpression();
...; // modify $scheduledAt expression
$event->cron($scheduledAt);
return $event;
}
Then you can reuse that logic when defining the schedule:
protected function schedule(Schedule $schedule)
{
$this->specialSchedule($schedule->call(function () {
//scheduled code
}));
}
UPDATE:
There is one more way to do that - a bit more complex, as it requires you to provide your own Schedule and Event classes, but also more flexible.
First, implement your own Event class and add there the new methods:
class CustomEvent extends \Illuminate\Console\Scheduling\CallbackEvent {
public function weekends() {
return $this->days(6,7);
}
}
Then your own Schedule class, so that it creates CustomEvent objects:
class CustomSchedule extends \Illuminate\Console\Scheduling\Schedule
{
public function call($callback, array $parameters = [])
{
$this->events[] = $event = new CustomEvent($callback, $parameters);
return $event;
}
public function exec($command, array $parameters = [])
{
if (count($parameters)) {
$command .= ' '.$this->compileParameters($parameters);
}
$this->events[] = $event = new Event($command);
return $event;
}
}
Lastly, in your Kernel.php you need too make sure your new schedule class is used for scheduling:
protected function defineConsoleSchedule()
{
$this->app->instance(
'Illuminate\Console\Scheduling\Schedule', $schedule = new Schedule
);
$this->schedule($schedule);
}
Folowing #jedrzej.kurylo's answer, i did this on laravel 5.8:
php artisan make:model CustomCallbackEvent
php artisan make:model CustomEvent
php artisan make:model CustomSchedule
In CustomCallbackEvent:
use Illuminate\Console\Scheduling\CallbackEvent;
use Illuminate\Console\Scheduling\EventMutex;
class CustomCallbackEvent extends CallbackEvent
{
public function __construct(EventMutex $mutex, $callback, array $parameters = [])
{
parent::__construct($mutex, $callback, $parameters);
}
}
In CustomSchedule:
use Illuminate\Console\Scheduling\Schedule;
class CustomSchedule extends Schedule
{
public function call($callback, array $parameters = [])
{
$this->events[] = $event = new CustomCallbackEvent(
$this->eventMutex,
$callback,
$parameters
);
return $event;
}
public function exec($command, array $parameters = [])
{
if (count($parameters)) {
$command .= ' '.$this->compileParameters($parameters);
}
$this->events[] = $event = new CustomEvent($this->eventMutex, $command, $this->timezone);
return $event;
}
}
In CustomEvent:
use Illuminate\Console\Scheduling\Event;
class CustomEvent extends Event
{
public function myFunction()
{
//your logic here
}
}
In Kernel.php:
protected function defineConsoleSchedule()
{
$this->app->instance(
'Illuminate\Console\Scheduling\Schedule', $schedule = new CustomSchedule
);
$this->schedule($schedule);
}
The Illuminate\Console\Scheduling\Event class uses the Macroable trait. It means that you can dynamically add methods to the class without inheriting it.
First of all you have to register it in the boot method:
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Event::macro(
'weekends',
function () {
/** #var Event $this */
return $this->days([0, 6]);
}
);
}
}
Then you can use it as any other method:
$schedule->command('do-work')->weekends();
For more details about macros see https://asklagbox.com/blog/laravel-macros

Laravel 5 Implement multiple Auth drivers

Synopsis
I am building a system with at least two levels of Authentication and both have separate User models and tables in the database. A quick search on google and the only solution thus far is with a MultiAuth package that shoehorns multiple drivers on Auth.
My goal
I am attempting to remove Auth which is fairly straight-forward. But I would like CustomerAuth and AdminAuth using a separate config file as per config/customerauth.php and config\adminauth.php
Solution
I'm assuming you have a package available to work on. My vendor namespace in this example will simply be: Example - all code snippets can be found following the instructions.
I copied config/auth.php to config/customerauth.php and amended the settings accordingly.
I edited the config/app.php and replaced the Illuminate\Auth\AuthServiceProvider with Example\Auth\CustomerAuthServiceProvider.
I edited the config/app.php and replaced the Auth alias with:
'CustomerAuth' => 'Example\Support\Facades\CustomerAuth',
I then implemented the code within the package for example vendor/example/src/. I started with the ServiceProvider: Example/Auth/CustomerAuthServiceProvider.php
<?php namespace Example\Auth;
use Illuminate\Auth\AuthServiceProvider;
use Example\Auth\CustomerAuthManager;
use Example\Auth\SiteGuard;
class CustomerAuthServiceProvider extends AuthServiceProvider
{
public function register()
{
$this->app->alias('customerauth', 'Example\Auth\CustomerAuthManager');
$this->app->alias('customerauth.driver', 'Example\Auth\SiteGuard');
$this->app->alias('customerauth.driver', 'Example\Contracts\Auth\SiteGuard');
parent::register();
}
protected function registerAuthenticator()
{
$this->app->singleton('customerauth', function ($app) {
$app['customerauth.loaded'] = true;
return new CustomerAuthManager($app);
});
$this->app->singleton('customerauth.driver', function ($app) {
return $app['customerauth']->driver();
});
}
protected function registerUserResolver()
{
$this->app->bind('Illuminate\Contracts\Auth\Authenticatable', function ($app) {
return $app['customerauth']->user();
});
}
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function() use ($app) {
return $app['customerauth']->user();
});
});
}
}
Then I implemented: Example/Auth/CustomerAuthManager.php
<?php namespace Example\Auth;
use Illuminate\Auth\AuthManager;
use Illuminate\Auth\EloquentUserProvider;
use Example\Auth\SiteGuard as Guard;
class CustomerAuthManager extends AuthManager
{
protected function callCustomCreator($driver)
{
$custom = parent::callCustomCreator($driver);
if ($custom instanceof Guard) return $custom;
return new Guard($custom, $this->app['session.store']);
}
public function createDatabaseDriver()
{
$provider = $this->createDatabaseProvider();
return new Guard($provider, $this->app['session.store']);
}
protected function createDatabaseProvider()
{
$connection = $this->app['db']->connection();
$table = $this->app['config']['customerauth.table'];
return new DatabaseUserProvider($connection, $this->app['hash'], $table);
}
public function createEloquentDriver()
{
$provider = $this->createEloquentProvider();
return new Guard($provider, $this->app['session.store']);
}
protected function createEloquentProvider()
{
$model = $this->app['config']['customerauth.model'];
return new EloquentUserProvider($this->app['hash'], $model);
}
public function getDefaultDriver()
{
return $this->app['config']['customerauth.driver'];
}
public function setDefaultDriver($name)
{
$this->app['config']['customerauth.driver'] = $name;
}
}
I then implemented Example/Auth/SiteGuard.php (note the methods implemented have an additional site_ defined, this should be different for other Auth drivers):
<?php namespace Example\Auth;
use Illuminate\Auth\Guard;
class SiteGuard extends Guard
{
public function getName()
{
return 'login_site_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'remember_site_'.md5(get_class($this));
}
}
I then implemented Example/Contracts/Auth/SiteGuard.php
use Illuminate\Contracts\Auth\Guard;
interface SiteGuard extends Guard {}
Finally I implemented the Facade; Example/Support/Facades/Auth/CustomerAuth.php
<?php namespace Example\Support\Facades;
class CustomerAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'customerauth';
}
}
A quick update, when trying to use these custom auth drivers with phpunit you may get the following error:
Driver [CustomerAuth] not supported.
You also need to implement this, the easiest solution is override the be method and also creating a trait similar to it:
<?php namespace Example\Vendor\Testing;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
trait ApplicationTrait
{
public function be(UserContract $user, $driver = null)
{
$this->app['customerauth']->driver($driver)->setUser($user);
}
}

Symfony2: Execute some code after every action

I recently started a project in Symfony2 and I need to run some methods before and after every action to avoid code redundancy (like preDispatch/postDispatch from Zend Framework and PreExecute/PostExecute from Symfony1).
I created a base class from which all the controllers are inherited,
and registered an event listener to run controller's preExecute() method before running requested action, but after reading tons of documentation and questions from here I still can't find how to run postExecute().
Foo/BarBundle/Controller/BaseController.php:
class BaseController extends Controller {
protected $_user;
protected $_em;
public function preExecute() {
$user = $this->get('security.context')->getToken()->getUser();
$this->_user = $user instanceof User ? $user : null;
$this->_em = $this->getDoctrine()->getEntityManager();
}
public function postExecute() {
$this->_em->flush();
}
}
Foo/BarBundle/Controller/FooController.php:
class FooController extends BaseController {
public function indexAction() {
$this->_user->setName('Eric');
$this->_em->persist($this->_user);
}
}
Foo/BarBundle/EventListener/PreExecute.php:
class PreExecute {
public function onKernelController(FilterControllerEvent $event) {
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$controllers = $event->getController();
if (is_array($controllers)) {
$controller = $controllers[0];
if (is_object($controller) && method_exists($controller, 'preExecute')) {
$controller->preExecute();
}
}
}
}
}
There is a discussion of this here and this particular example by schmittjoh may lead you in the right direction.
<?php
class Listener
{
public function onKernelController($event)
{
$currentController = $event->getController();
$newController = function() use ($currentController) {
// pre-execute
$rs = call_user_func_array($currentController, func_get_args());
// post-execute
return $rs;
};
$event->setController($newController);
}
}

How to log db queries in Lumen5.7?

I upgrade Lumen from 5.4 to 5.7, and I want to be able to log DB queries for debugging.
Here's the conf/source code. I have to use 'LumenDB' alias because of naming conflict of a third-party library.
I expect the query could be logged, but I can not see them in lumen.log.
MyApplication.php
<?php
namespace App;
use Illuminate\Support\Facades\Facade;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
class MyApplication extends \Laravel\Lumen\Application {
public function withFacades($aliases = true, $userAliases = [])
{
Facade::setFacadeApplication($this);
if (! static::$aliasesRegistered) {
static::$aliasesRegistered = true;
class_alias('Illuminate\Support\Facades\Auth', 'Auth');
class_alias('Illuminate\Support\Facades\Cache', 'Cache');
class_alias('Illuminate\Support\Facades\DB', 'LumenDB');
class_alias('Illuminate\Support\Facades\Event', 'Event');
class_alias('Illuminate\Support\Facades\Gate', 'Gate');
class_alias('Illuminate\Support\Facades\Log', 'Log');
class_alias('Illuminate\Support\Facades\Queue', 'Queue');
class_alias('Illuminate\Support\Facades\Schema', 'Schema');
class_alias('Illuminate\Support\Facades\Validator', 'Validator');
}
}
protected function registerLogBindings()
{
$this->singleton('Psr\Log\LoggerInterface', function () {
return new Logger('lumen', $this->getMonologHandler());
});
}
protected function getMonologHandler()
{
$maxFiles = 7;
$rotatingLogHandler = new RotatingFileHandler(storage_path('logs/lumen.log'), $maxFiles);
$rotatingLogHandler->setFormatter(new LineFormatter(null, null, true, true));
$handlers = [];
$handlers[] = $rotatingLogHandler;
return $handlers;
}
}
bootstrap/app.php
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\EventServiceProvider::class);
\LumenDB::connection()->enableQueryLog();
app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\DB;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
DB::listen(function ($query) {
// $query->sql
// $query->bindings
// $query->time
Log::info("-------");
Log::info($query->sql);
});
}
The query is executed inside a Service method which is called by a Command scheduled by cron.
public function getAllStatsToday()
{
$today = new \DateTime();
$today->setTime(0, 0, 0);
$productUsageStats = ProductUsageStat::make()
->where('updated_at', '>', $today)
->get();
return $productUsageStats;
}
You have not registered the AppServiceProvider in your bootstrap/app.php. Because of this the boot method in your AppServiceProvider is never registered and thus the logging is never executed.
You should change app.php to the following:
$app->register(App\Providers\EventServiceProvider::class);
$app->register(App\Providers\AppServiceProvider::class);
\LumenDB::connection()->enableQueryLog();

Categories