I’ve been Learning Laravel as I use it on a small side project. I’ve learned the basics pretty well and am slowly advancing my app. Today I was trying to get Google Analytics events to send on certain successful user events. This post touches on Service Providers, View Composers, cookie/session data, and accessing the request. It’s pretty technical and assumes a base level of Laravel and PHP knowledge, though Laravel experts will probably pick holes in both my terminology and my method – that’s what the comments are for! 😀

Today I wanted to add some Google Analytics event logging to hasyourbabyarrivedyet.com, which is a sillyside-project that I’m using to learn Laravel (and other things).

I LIKE Laravel, but I’m finding some things in Laravel pretty easy, and some things pretty hard. In particular, the documentation is either:

  • tutorial- or FAQ-like; or
  • auto-generated interface details from the PHPDoc’ed code.

As an example of this, I was trying to work out a routing issue the other day to do with route groups. The Routing documentation in Laravel 5.1 is more like a tutorial: it addresses the basic things that you might want to do with route groups, but doesn’t tell you what ELSE you can do beyond the examples shown. The API documentation for Route::group() tells you what parameters it takes, but one of the parameters is an array of attributes, and it doesn’t tell you what those attributes can be!

Aside: Yes, this is an open source project, and you (and I!) can contribute to the documentation on GitHub. My point here is that while there are tutorials, and docblocks, there isn’t a comprehensive method reference and I’m often told that the answer is “on the Laracasts forums” (which are excellent, by the way) or by “asking on Slack“, but this doesn’t represent a sustainable documentation solution.

Anyway, I want to remember the things I went through and document them for others. So here’s some things I learned as I went along.

The starting point

To start with, I already had Google Analytics set up on the site. It’s just code in the footer of my master.blade.php file. I’m not using much front-end JavaScript, so the plan is that when my AuthController->create() method fires, I’ll remember the fact that I want to send the event in session data – because the user will be redirected on a successful account creation – and then when I print the view on the redirected page, I’ll add the JS to do the ga.send call to log my event.

Accessing the request object

My first challenge is that I need to access the request object from wherever I need it in order to set or get the session data, which is accessed through the request object.

I’ve still not found a general purpose way to get the current request from anywhere in my code. But it seems that a lot of the top-level things that you would construct in Laravel (like Controllers and Service Providers) can take a type-hinted Request in method declarations, as long as you use the Request class. So I can do this:

use Illuminate\Http\Request;
...
class AuthController extends Controller
{
    ...
    protected function create(Request $request, array $data)
    {
        // Do something with $request
    }
}

It will come to pass that I add some stuff to a Service Provider, and you can do the same thing with Service Providers:

use Illuminate\Http\Request;

class AppServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        // Do something with $request
    }

    ...
}

The trick then seems to be to pass your request down to anything you do within your controller or service provider that needs it. I’ll give an example of that later.

Saving session data

Laravel comes with session handling, so all I had to do here was check my config and add the relevant code. It was useful to note the “flash data” function which stores data only for use in the next request. This is exactly what I wanted to do.

protected function create(Request $request, array $data)
{

    $request->session()->flash('send_ga_event', [
        'category' => 'userEvents',
        'action' => 'signUp',
        'label' => '',
        'value' => 1
    ]);

    return User::create([
        ...
    ]);
}

Getting the session data back

I know, almost immediately what my end goal is. I want to do this in master.blade.php:

<!-- Google Analytics -->
<script>
    //
    // GA async code snippet here.
    //

    ga('create', 'UA-36775788-1', 'auto');
    ga('send', 'pageview');

    @if (isset($send_ga_event))
        ga('send', {
            hitType: 'event',
            eventCategory: '{{ $send_ga_event['category'] }}',
            eventAction: '{{ $send_ga_event['action'] }}',
            eventLabel: '{{ $send_ga_event['label'] }}',
            eventValue: '{{ $send_ga_event['value'] }}'
        });
    @endif

</script>

But the question is, how do I get the data from the session into my view? Well, because this is the master template, I want to get the session data into ALL views.

On the face of it, this isn’t too hard. You can send data to all views by using “the view factory’s share method…within a service provider’s boot method”. Which is fine, if you’re familiar with the view factory (I could figure out what that is), a specific method of the view factory (which I’ve no idea what it is or does), what a service provider is (I’m rapidly learning at this point) and what the boot method is and when it fires (which, of course, everyone knows, right?!).

On the face of it, I need to do this:

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        view()->share('key', 'value');
    }

    ....
}

BUT…I need my request data. Fortunately I can pass this in using the type hinting, as described before:

use Illuminate\Http\Request;

class AppServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        // If a send_ga_event session data is set, send it to the view
        if ($request->session()->has('send_ga_event')) {
            view()->share('send_ga_event', $request->session()->get('send_ga_event'));
        }
    }

    ....
}

But this gives an error:

Session store not set on request.

The problem with this, it turns out, I think, is that the boot() method runs before the SessionStart Middleware. So at this point I have no session data available in the Request.

A trip back to the docs reveals that there are “View Composers“:

View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location.

This seemed to be what I wanted. Not actually doing the data-passing-into-the-view at boot time, but deferring it until the view is about to be rendered. Perfect!

Now, the examples didn’t quite make sense, and I didn’t want to have to construct a new class JUST to pass this one piece of data to all views. But the closure option seemed interesting.

I tried this, noting that “The composer method accepts the * character as a wildcard, allowing you to attach a composer to all views”:

class AppServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        // If a send_ga_event session data is set, send it to the view
        view()->composer('*', function ($view) {
            if ($request->session()->has('send_ga_event')) {
                view()->share('send_ga_event', $request->session()->get('send_ga_event'));
            }
        });
    }
}

The only issue with this was that the request wasn’t available in the closure. But it’s a closure, so that’s OK, we can just pull it from the scope in which is was defined. Here’s the final service provider code that is in App/Providers/AppServiceProvider.php:

<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Request;

class AppServiceProvider extends ServiceProvider {

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(Request $request)
    {
        // If a send_ga_event session data is set, send it to the view
        view()->composer('*', function ($view) use ($request) {
            if ($request->session()->has('send_ga_event')) {
                view()->share('send_ga_event', $request->session()->get('send_ga_event'));
            }
        });
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

That, combined with the session flashing in the AuthController’s create method, and the script code in the master.blade.php all worked together to do what I needed:

Evidence of success!

Evidence of success!