2

I have an Observer set up to Listen to a Model's events in order to keep my Controller clean of Logging messages. My implementation is as follows:

First, a store method that does just what it's supposed to do. Create and save a new model from valid parameters.

# app/Http/Controllers/ExampleController.php
namespace App\Http\Controllers;

use App\Http\Requests\StoreExample;
use App\Example;

class ExampleController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Create and save an Example from validated form parameters.
     * @param  App\Http\Requests\StoreExample  $request
     */
    public function store(StoreExample $request)
    {
        Example::create($request->validated());

        return back();
    }
}

The StoreExample Form Request isn't important. It just validates and checks a gate to authorize the action.

The Observer I have set up logs this action.

# app/Observers/ExampleObserver.php
namespace App\Observers;

use App\Example;

class ExampleObserver
{
    public function created(Example $example): void
    {
        \Log::info(auth()->id()." (".auth()->user()->full_name.") has created Example with params:\n{$example}");
    }
}

The problem I have, is the way my logs depend on the auth() object to be set. Given the auth middleware and the gate it has to check in order to store an Example, there is no way a guest user will set off this code.

However, I do like to use tinker in my local and staging environments to check the behavior of the site but that can set off an error (Well, PHP notice to be more precise) because I can create Example models without being authenticated and the logger will try to fetch the property full_name from the non-object auth()->user().

So my question is as follows: Is there a way to catch when I'm specifically using the Laravel tinker session to handle my models in the Observer class?

IGP
  • 14,160
  • 4
  • 26
  • 43
  • Well, this isn't a proper response to your question but it seems that you want to log and check different aspects of your app to validate the correct behavior so.. have you tried the [Laravel Telescope package](https://laravel.com/docs/telescope)? This will record automatically all the request, payload, database queries, events, and more. This could help you to achieve the results that you want. – Kenny Horna May 10 '19 at 04:11
  • I haven't tried it but I might in the future™. I don't really have trouble with logging. I just want to know if it's possible to identify when a model operation was done from a tinker session. – IGP May 10 '19 at 04:16

1 Answers1

1

Okay, replying to my own question: There IS a way. It requires using a Request object. Since observers do not deal with requests on their own, I injected one in the constructor. request() can be used instead, so no DI is needed.

Why is a Request important?

Because a request object has an accessible $server attribute that has the information I want. This is the relevant information I get by returning a dd($request->server) (I'm not gonna paste the whole thing. My Request's ServerBag has over 100 attributes!)

Symfony\Component\HttpFoundation\ServerBag {#37
  #parameters: array:123 [
    "SERVER_NAME" => "localhost"
    "SERVER_PORT" => 8000
    "HTTP_HOST" => "localhost:8000"
    "HTTP_USER_AGENT" => "Symfony"   // Relevant
    "REMOTE_ADDR" => "127.0.0.1"
    "SCRIPT_NAME" => "artisan"       // Relevant
    "SCRIPT_FILENAME" => "artisan"   // Relevant
    "PHP_SELF" => "artisan"          // Relevant
    "PATH_TRANSLATED" => "artisan"   // Relevant
    "argv" => array:2 [              // Relevant
      0 => "artisan"
      1 => "tinker"
    ]
    "argc" => 2
  ]
}

So there's all these attributes I can filter by using $request->server('attribute') (returns $request->server->attribute or null, so no risk of accessing an undefined property). I can also do $request->server->has('attribute') (returns true or false)

# app/Observers/ExampleObserver.php
namespace App\Observers;

use App\Example;

class ExampleObserver
{
    /* Since we can use request(), there's no need to inject a Request into the constructor
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }
    */

    public function created(Example $example): void
    {
        \Log::info($this->getUserInfo()." has created Example with params:\n{$example}");
    }

    private function getUserInfo(): string
    {
        // My logic here. 
    }
}
IGP
  • 14,160
  • 4
  • 26
  • 43
  • 1
    Good find! I needed this as well. I did this in the app service provider `config()->set('app.tinker',request()->server->get('argv')[1] === 'tinker');` So now I can access it everywhere in my application – Ogier Schelvis Aug 14 '20 at 14:58