Add Custom Variable to Laravel Error Log - laravel

I'd like to log the user's name along with the error that is outputted to the log. How do I add a variable to the beginning of an error log entry that outputs an exception?

I think I've got a fairly easy way to do this.
Solution 1
Create a new folder under app called handlers and create a new class called CustomStreamHandler.php which will hold the custom monolog handler.
namespace App\Handlers;
use Monolog\Handler\StreamHandler;
use Auth;
class CustomStreamHandler extends StreamHandler
{
protected function write(array $record)
{
$record['context']['user'] = Auth::check() ? Auth::user()->name : 'guest';
parent::write($record);
}
}
Make sure you set the namespace if you changed it from App and also modify the line where it's setting the user in the context so it works with your users table.
Now we need to drop the current StreamHandler from monolog. Laravel sets this up by default and as far as I can see, there isn't a good way to stop Laravel from doing this.
in app/Providers/AppServiceProvider, we should modify the boot() function to do remove the handler and insert the new one. Add the following...
// Get the underlying instance of monolog
$monolog = \Log::getMonolog();
// Instantiate a new handler.
$customStreamHandler = new \App\Handlers\CustomStreamHandler(storage_path('logs/laravel.log'));
// Set the handlers on monolog. Note this would remove all existing handlers.
$monolog->setHandlers([$customStreamHandler]);
Solution 2
This is a much easier solution but also not exactly what you are looking for (but it might still work for you).
Add the following to AppServiceProvider.php boot().
Log::listen(function()
{
Log::debug('Additional info', ['user' => Auth::check() ? Auth::user()->name : 'guest']);
});
This will simply listen for any logging and also log a debug line containing user information.

Related

Saving an object into the session or cookie

I'm using Instagram API library to connect user to Instagram profile and then do smth with it. So, as Instagram API wiki says:
Once you have initialized the InstagramAPI class, you must login to an account.
$ig = new \InstagramAPI\Instagram();
$ig->login($username, $password); // Will resume if a previous session exists.
I have initialized InstagramAPI class and then I called $ig->login('username', 'password');. But I have to call it in every function where I need to work with Instagram.
So how could I save this object $ig for using it in the future in other controllers without calling login() any more? Can I save $ig object into the session or cookie file?
P.S. I think saving into the session is not safe way to solve the issue.
UPD: I was trying to save $ig object into the session, however the size is large and session become stop working as well.
Regarding the register method you asked in the comments section, all you need to create a new service provider class in your app\providers directory and declare the register method in there for example:
namespace App\Providers;
use InstagramAPI\Instagram;
use Illuminate\Support\ServiceProvider;
class InstagramServiceProvider extends ServiceProvider
{
public function register()
{
// Use singleton because, always you need the same instance
$this->app->singleton(Instagram::class, function ($app) {
return new Instagram();
});
}
}
Then, add your newly created InstagramServiceProvider class in providers array inside the config/app.php file for example:
'providers' => [
// Other ...
App\Providers\InstagramServiceProvider::class,
]
Now on, in any controller class, whenever you need the Instagram instance, all you need to call the App::make('InstagramAPI\Instagram') or simply call the global function app('InstagramAPI\Instagram') or even you can typehint the class in any method/constructor etc. Some examples:
$ig = App::make('InstagramAPI\Instagram');
$ig = App::make(Instagram::class); // if has use statement at the top fo the class
$ig = app('...');
In a class method as a dependency:
public function someMethod(Instagram $ig)
{
// You can use $ig here
}
Hope this helps but read the documentation properly, there will get everything documented.

How to use global variables in Laravel

Is it possible to share and change some variable between multiple views? For example, I want to have a variable $user that will be shared between all views. When a user logs the variable is set up, when the user logs out, the variable is unset. I was unable to achieve requested using
the following combination:
in AppServiceProvider:
view()->share('var', 1);
in the controller:
$var = view()->shared('var');.
$var ++;
view()->share('var', var);
return view(''', 'var'=>$var)
Every time when the page is reloaded $var is always the same (2).
I want to have a variable $user that will be shared between all views
You should use auth()->user() to get authenticated user instance in any view.
But if you don't want to use it for some reason, you could share the variable between multiple views with a view composer.
share() method will be useful only if you want to share a variable with all views. To make it work, put view()->share('key', 'value') to the boot() method of a service provider.
Also, the code in your controller looks like you want to share data not between views, but between requests. Use session for that.
To save the data:
session(['key' => 'value']);
To get the data in another request:
session('key');
It would be better to add another service provider. Take a look at my provider:
<?php
namespace App\Providers;
use Request;
use Illuminate\Support\ServiceProvider;
class ViewComposerServiceProvider extends ServiceProvider
{
public function boot()
{
$this->globalThings();
//call another globals' function here
}
public function register()
{
//
}
/**
* Get the golbals
*/
private function globalThings()
{
view()->composer(array('*.*'),function($view){
//get the data however you want it!
$view->with('global', Model::where('field','value')->get());
});
}
And don't forget to add the service provider to list of provider is config/app.php
App\Providers\ViewComposerServiceProvider::class,

Configure langs in Laravel Translatable BootForms

I'm new to Laravel Translatable BootForms, and I was wondering something.
When I use this code :
{!!
TranslatableBootForm::text('Nom', 'name')
->required()
!!}
The render is as follows :
I don't know where this language list comes from.
I only want to list some languages specified in my database, as I do with this workaround :
#foreach($availableLangs as $availableLang)
{!!
TranslatableBootForm::text('Nom', 'name')
->renderLocale($availableLang['locale'])
!!}
#endforeach
Which gives me this :
My two questions are :
Where does this language list come from ?
How can I replace it by my own language list ?
Answering the first question may lead to an automatic answer for the second, though)
In Laravel, you should always try to read the Service Providers, they provide important clues about the project structures. Let's try to follow the trail of the function calls:
TranslatableBootForm is a facade and it resolves to and instance of translatable-bootform from the Service Container according to this line:
protected static function getFacadeAccessor() { return 'translatable-bootform'; }
Now, in the file TranslatableBootFormsServiceProvider.php we can see that translatable-bootform is an instance of TranslatableBootForm. So when you call TranslatableBootForm::text, you will be using the Facade which resolves to an instance of TranslatableBootForm
Opening the TranslatableBootForm class, we cannot find the text method, so there should be a __call method. The __call method always returns whatever is returned from the method render. So that's where the action is happening.
Reading the code there, you will find that it gets the locales from a method called locales and it will intersect it with the func_get_args() function to get whatever languages you pass to it. So renderLocale or simply render will do the same thing.
The method locales just returns an array which is by default empty in the class. If we return back to the TranslatableBootFormsServiceProvider we will see that there's an important line:
$formBuilder->setLocales($this->getLocales());
Which gets the locales from Translatable\TranslatableWrapper which is just a wrapper around this file in another package: https://github.com/dimsav/laravel-translatable/blob/master/src/Translatable/Translatable.php
Looking at the configuration file in the laravel-translatable package, we can see the languages:
https://github.com/dimsav/laravel-translatable/blob/master/src/config/translatable.php
Solutions
Now, you can simply copy the file translatable.php in your config folder and set your locales.
Or, you create a new service provider MyTranslatableBootFormsServiceProvider
class MyTranslatableBootFormsServiceProvider extends TranslatableBootFormsServiceProvider
{
/**
* Get Translatable's locales.
*
* #return array
*/
protected function getLocales()
{
// You can return a config key
// return config('yourconfig.locales');
// Or directly the array containing the languages
return ['en', 'fr', 'nl'];
}
}
Then, you will use this provider in your config/app.php instead of the original TranslatableBootFormsServiceProvider
Disclaimer:
I didn't try the code, you might have a bug, but you get the idea now how to find your way around Laravel packages.

Laravel 5: Sessions not working the way they should

On top of every controller and routes.php I used:
use Illuminate\Support\Facades\Session;
In routes.php I set the session using:
Session::put('key', 'value');
In a controller I want to call the session value of key using:
echo Session::get('key');
But once I set a new value to key in routes.php and call it in a controller, I still get the first value and not the new one. If I echo the the session using Session::all() in routes.php after setting it, I see the new value, but in a controller it flips back to the first value. I even tried using below in routes.php before setting the new value, but without success.
Session::forget('key');
Am I forgetting something here?
Using regular PHP $_SESSION my routes.php looks like this:
$slug = $_SERVER['REQUEST_URI'];
$slug = explode('/', $slug[0]);
if(in_array($slug[1], Language::all()->lists('iso'))) {
$_SESSION['language'] = $slug[1];
if(!$slug[2]) {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
if($slug[2] != 'dashboard' && $slug[2] != 'migrate' && $slug[2] != 'form-send') {
if (in_array($slug[2], ElementValue::where('element_field_id', 2)->lists('value_char')) && !isset($slug[3])) {
$_SESSION['slug'] = $slug[2];
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
}
}
}
}
Where in routes.php are you setting the session value? It sounds like you're doing something like this:
Session::put('key', 'value');
Route::get('my-route', 'MyController#doSomething');
and then doing this:
class MyController {
public function doSomething()
{
Session::get('key');
}
}
Is that correct? If so, read on...
I'm no expert on the Laravel request lifecycle (for more, see the documentation), but it doesn't surprise me that this doesn't work. The way I think about it is this: the routes.php file is loaded and executed early in the life cycle - probably first - since it tells the application what code to execute next (ie. what do when a particular request is received). And when I say "early in the life cycle", I mean early - like before sessions are initialized. I believe that the Session::put call is simply being ignored, since at the time when you're setting the value, the session does not exist.
You may want expand your question with a little more detail about what you're trying to accomplish - there has got to be a better way to do it.
EDIT - in response to the comments below...
I am not saying you should touch the $_SESSION superglobal - that's a bad idea because I'm not even sure that Laravel uses the native PHP session facility and you have no guarantee that whatever you do will continue to work in the future.
It's not clear what you're trying to do, but to me this sounds like a value that does not belong in the session.
By placing the Session::put in the routes.php file, it sounds like you have some value that's important and should be set for every session and every request
If that's the case, and it's a static value, then it's not a session value, it's a configuration value.
If, instead, it's a dynamic value and/or it changes depending on which user is associated with a session, then you can set it in one of several places:
if you're using controller-based routing, you could set this in the controller constructor, although I wouldn't recommend it, because you will probably have to do it for several controllers, leading to code duplication
if you're using closures in your routes, set it there. E.g.
Route::get('some/route', function () {
Session::put('key', 'value');
// this works, because the closure isn't executed until after
// the application is initialized
});
you could also do it in middleware
or in a service provider (although I'm not certain that sessions would be available when the service providers are executed).
The best option is probably middleware - this would allow you to set (or calculate) the session value in one place in your code and also associate it with particular routes, if you don't need it for all routes.
Don't use $_SESSION in laravel. Uses the laravel Session class. See the following post How to access the globals $_SESSION and $_COOKIE from a laravel app?
Also, all your if logic should not be living in routes.php. You should add that to middleware to filter your routes.
Also, you are really making this hard for yourself. Laravel provides most of what you need in convenient helper classes e.g. Request::url(), Request::getHost(), Request::getLocale(). Have a read through the docs and get familiar with "The Laravel Way" it will be much easier and things will then work as you expect.
I moved the logic to the controller and now my routes are this simple:
Route::pattern('slug', '[a-zA-Z0-9\-_\/]+');
$slug = Request::path();
if(isset($slug)) {
Route::any('/{slug}', 'PagesController#index')->where('slug', '[a-zA-Z0-9\-_\/]+');
}
The session is stored in the PagesController and used further in the application. Thanks for your help guys.

Laravel 4 route-model binding exceptions doesn't work despite docs and examples

I read a lot about Laravel4 Route-model binding (L4 docs, tutorials, etc.) but still exceptions (i.e. the model is not found) don't work for me
These are my basic files
routes.php:
Route::model('game', 'Game', function(){
// override default 404 behavior if model not found, see Laravel docs
return Redirect::to('/games');
});
...
Route::get('/games/edit/{game}', 'GamesController#edit');
GamesController.php
class GamesController extends BaseController {
...
public function edit(Game $game){
return View::make('/games/edit', compact('game'));
}
}
Pretty straight, but I get this error: Argument 1 passed to GamesController::edit() must be an instance of Game, instance of Illuminate\Http\RedirectResponse given
If I type http://mysite.dev/games/edit/1 all is fine (model with ID = 1 exists)
If I type http://mysite.dev/games/edit/12345 (no model with that ID) the ugly error above is triggered instead of the redirect I specified
I also looked at this (the bottom part where a Redirect closure is suggested: that is just what I am doing!) but no way to make it work: laravel 4 handle not found in Route::model
What's wrong with it? Please any help?
Thanks in advance
In Route::model you declare which variable will be a model instance, you shouldn't use it to do a redirection that way. Instead of that, specify that $game is of type Game and then work with your routes:
Route::model('game', 'Game');
...
Route::get('/games/edit/{game}', 'GamesController#edit');
Then if you access to /games/edit/3 GamesController::edit will receive an instance of Game class whose id=3
I ended up by setting a general "Not Found" error catcher, like this:
// routes.php
App::error(function(Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e) {
return Response::make('Not Found', 404);
});
...
Route::model('game', 'Game');
...
Route::get('/games/edit/{game}', 'GamesController#edit');
What I understand is that if I want a custom redirect and not a general 404 page (i.e. take the user to games' list if model not found), I CAN'T use the route-model-binding
In other words, I have to use Route::get('/games/edit/{id}', 'GamesController#edit'); and then do my application logic inside the 'edit' method:
public function edit($id){
$game = Game::findOrFail($id);
// if fails then redirect to custom page, else go on saving
}
I'm very new to Laravel, but as far as I can see this has nothing to do with the closure, but with the use of "Redirect::to" inside that closure. Using "App::abort( 404 );" works.

Resources