How to log db queries in Lumen5.7? - php

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();

Related

How to override specific methods in Laravel core classes?

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.');
}
}
}

Laravel Extend Vendor Class

I tried extending an Illuminate Class Translator
I created a class and extended to translator
then I added this line to my RepositoryServiceProvider
$this->app->bind(\Illuminate\Translation\Translator::class, \App\Repositories\Translation\TranslationTranslator::class);
But its not working
what am I doing wrong?
the class as follows
<?php
namespace App\Repositories\Translation;
use Countable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\NamespacedItemResolver;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\TranslatorInterface;
class TranslationTranslator extends \Illuminate\Translation\Translator
{
/**
* Parse a key into namespace, group, and item.
*
* #param string $key
* #return array
*/
public function parseKey($key)
{
\Log::info('got in');
die;
$segments = parent::parseKey($key);
if (is_null($segments[0])) {
$segments[0] = #explode('.', $segments[2])[0];
}
if ($segments[1] == 'translatable') {
$segments[1] = #explode('.', $segments[2])[0] . '_' . #explode('.', $segments[2])[1];
}
return $segments;
}
}
UPDATE
Apparently the Translator class has a constructor
public function __construct(LoaderInterface $loader, $locale)
{
$this->loader = $loader;
$this->locale = $locale;
}
so my binding has to pass by the interface.. which cannot be instantiated
public function boot()
{
$app = $this->app;
$this->app->bind(\Illuminate\Translation\Translator::class, function() use ($app){
return $app->make(\App\Repositories\Translation\TranslationTranslator::class);
});
}
and getting this error
Illuminate\Contracts\Container\BindingResolutionException with message
'Target [Illuminate\Translation\LoaderInterface] is not instantiable
while building [App\Repositories\Translation\TranslationTranslator].'
You can use closure to resolve the classes
$this->app->bind(\Illuminate\Translation\Translator::class, function(){
return new \App\Repositories\Translation\TranslationTranslator;
});
Secondly translator is binded with laravel in using translator alias.
You can also override it.
$this->app->bind('translator', function(){
return new \App\Repositories\Translation\TranslationTranslator;
})
This worked for me
$app = $this->app;
$loader = $app['translation.loader'];
$locale = $app['config']['app.locale'];
$this->app->bind('translator', function() use ($loader, $locale){
return new \App\Repositories\Translation\TranslationTranslator($loader, $locale);
});
I hope this help you
Check below example
namespace App\Repositories\Translation;
use Illuminate\Translation\Translator;
class TranslationTranslator extends Translator
{
public function get()
{
....
}
}
No need to anything. you only need to add new functions or override base class functions. Then you can use this class as simple other classes.
Try changing it to this:
$this->app->instance(\Illuminate\Translation\Translator::class, \App\Repositories\Translation\TranslationTranslator::class);
That should then change the instance of the interface.
UPDATE
if you are just trying to add a new method, the Translator class is Macroable. So you can do the following
Translator::macro('parseKey', function ($key) {
\Log::info('got in');
die;
$segments = parent::parseKey($key);
if (is_null($segments[0])) {
$segments[0] = #explode('.', $segments[2])[0];
}
if ($segments[1] == 'translatable') {
$segments[1] = #explode('.', $segments[2])[0] . '_' . #explode('.', $segments[2])[1];
}
return $segments;
});
You would then be able to call your method as you normally would. For example:
app(Translator::class)->parseKey($key);

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();
}
});

Laravel 5 return JSON or View depends if ajax or not

I would like to know if there is a magic method to use this scenario :
If I call a page via an AJAX request the controller returns a JSON object, otherwise it returns a view, i'm trying to do this on all my controllers without changin each method.
for example i know that i can do this :
if (Request::ajax()) return compact($object1, $object2);
else return view('template', compact($object, $object2));
but I have a lot of controllers/methods, and I prefer to change the basic behavior instead of spending my time to change all of them. any Idea ?
The easiest way would be to make a method that is shared between all of your controllers.
Example:
This is your controller class that all other controllers extend:
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
protected function makeResponse($template, $objects = [])
{
if (\Request::ajax()) {
return json_encode($objects);
}
return view($template, $objects);
}
}
And this is one of the controllers extending it:
<?php namespace App\Http\Controllers;
class MyController extends Controller
{
public function index()
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($template, compact($object, $object2));
}
}
Update for Laravel 5+
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
protected function makeResponse($request, $template, $data = [])
{
if ($request->ajax()) {
return response()->json($data);
}
return view($template, $data);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MyController extends Controller
{
public function index(Request $request)
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($request, $template, compact($object, $object2));
}
}
There is no magic but you can easily override ViewService in 3 steps:
1.create your view factory (your_project_path/app/MyViewFactory.php)
<?php
/**
* Created by PhpStorm.
* User: panos
* Date: 5/2/15
* Time: 1:35 AM
*/
namespace App;
use Illuminate\View\Factory;
class MyViewFactory extends Factory {
public function make($view, $data = array(), $mergeData = array())
{
if (\Request::ajax()) {
return $data;
}
return parent::make($view, $data, $mergeData);
}
}
2.create your view service provider (your_project_path/app/providers/MyViewProvider.php)
<?php namespace App\Providers;
use App\MyViewFactory;
use Illuminate\View\ViewServiceProvider;
class MyViewProvider extends ViewServiceProvider {
/**
* Register the application services.
*
* #return void
*/
public function register()
{
parent::register();
}
/**
* Overwrite original so we can register MyViewFactory
*
* #return void
*/
public function registerFactory()
{
$this->app->singleton('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
// IMPORTANT in next line you should use your ViewFactory
$env = new MyViewFactory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
3.in your_project_path/config/app.php:
change 'Illuminate\View\ViewServiceProvider',
to 'App\Providers\MyViewProvider',
What this do:
it tells your application to use another view provider which will register your view factory
$env = new MyViewFactory($resolver, $finder, $app['events']);
in line 33 of MyViewProvider.php which will check if request is AJAX and return if true or continue with original behavior
return parent::make($view, $data, $mergeData);
in MyViewFactory.php line 19
Hope this help you,
In laravel 5.1, this is the best way:
if (\Illuminate\Support\Facades\Request::ajax())
return response()->json(compact($object1, $object2));
else
return view('template', compact($object, $object2));
The solution suggested by #ryanwinchester is really good. I, however, wanted to use it for the responses from update() and delete(), and there naturally return view() at the end doesn't make a lot of sense as you mostly want to use return redirect()->route('whatever.your.route.is'). I thus came up with that idea:
// App\Controller.php
/**
* Checks whether request is ajax or not and returns accordingly
*
* #param array $data
* #return mixed
*/
protected function forAjax($data = [])
{
if (request()->ajax()) {
return response()->json($data);
}
return false;
}
// any other controller, e.g. PostController.php
public function destroy(Post $post)
{
// all stuff that you need until delete, e.g. permission check
$comment->delete();
$r = ['success' => 'Wohoo! You deleted that post!']; // if necessary
// checks whether AJAX response is required and if not returns a redirect
return $this->forAjax($r) ?: redirect()->route('...')->with($r);
}

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);
}
}

Categories