Route to madness: Too much magic in Laravel!

Some people say that Laravel’s facades are optional, and if you don’t want to use them, then you can use dependency injection or the helper methods instead.

Laravel does have a powerful DI container. I have tried to go as facade-less as I can, but again I’ve found routing to be a pain.

Using some of PHP’s magic methods, Laravel likes to proxy calls. A lot. The obvious examples are, of course, Facades. When you call something like Cache::get('something'), Laravel proxies that call into the container and whatever service is required. There’s even an explanation in the official documentation.

One of the problems with proxying calls like this is that it can become a little too magical. Out of the box, a call like this cannot be analysed statically (that is to say, without running the code), to determine what’s going on.

The solution to this is to provide hints for an IDE to pick up on. There’s a package for this which outputs an _ide_helper.php file containing hundreds of automatically generated hints.

Recently, I wanted to see if it was possible to ditch facades in the routes file. The method that loads routes adds a $router variable when it includes the routes file, so it seems like Laravel is indicating you can:

//Illuminate\Routing\Router
protected function loadRoutes($routes)
{
    if ($routes instanceof Closure) {
        $routes($this);
    } else {
        $router = $this;

        require $routes;
    }
}

And a cursory test proves this to work.

// Works
Route::get('/', function () {
    return 'Hello World';
});

// Also works
$router->get('/', function () {
    return 'Hello World';
});

Other method calls also work:

Route::view('/', 'welcome');

$router->view('/', 'Welcome');

We can also use closures for things like groups:

Route::middleware([TrimStrings::class])->group(function () {
    Route::get('/', function () {
        return '/';
    });
});

$router->middleware([TrimStrings::class])->group(function () use ($router) {
    $router->get('/', function () {
        return '/';
    });
});

The only difference to the code is that we have to pass the $router variable into the closure because it’s otherwise not in scope.

tl;dr – replace Route:: with $router-> and remember to pass the $router variable into closures. Simple. End of blog post right? No.

Consider Route Prefixes, the example in the documentation looks like this:

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        return '/admin/users';
    });
});

Following the previous facade removals, we should be able to change this to:

$router->prefix('admin')->group(function () use ($router) {
    $router->get('users', function () {
        return '/admin/users';
    });
});

But when we try this, the first example works, but the Facade-less version breaks:

Wtf why?

No, this isn’t a typo or other trivial bug in my code. It’s a deeper issue with how Laravel proxies calls all over the place.

Let’s first look at the Facade version and follow it.

The Route facade extends an abstract facade class which implements __callStatic, passing in the method name ('proxy') and the arguments passed in (the closure):

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    if (! $instance) {
        throw new RuntimeException('A facade root has not been set.');
    }

    return $instance->$method(...$args);
}

The call to getFacadeRoot essentially just returns $app['route'] – or whatever key the facade says to use.

So when we call Route::prefix, Laravel resolves the Router from its container, then invokes the prefix method on it.

If we look at the Router class, does it have a public prefix method? No. Public is important, because if the method exists but it’s protected or private, then it cannot be called and instead PHP try __call instead.

We can demonstrate this easily:

$test = new class
{
    protected function foo(): void
    {
        print 'foo';
    }

    public function __call($name, $arguments)
    {
        print 'calling magic method!';
    }
};

$test->foo();
$ php test.php
calling magic method!

Instead of calling the foo method which does exist, PHP calls __call because the foo method is not public.

The Router does have a protected prefix method. So why is it called when using $router but not when using a facade?

Answer: Because $router is actually an alias of $this.

Cast your memory back to when the routes file is included:

//Illuminate\Routing\Router
protected function loadRoutes($routes)
{
    if ($routes instanceof Closure) {
        $routes($this);
    } else {
        $router = $this;

        require $routes;
    }
}

Which means the in the facade-less code, you can replace $router with $this and it’s functionally the same:

$router->prefix('admin')->group(function () use ($router) {
    $router->get('users', function () {
        return '/admin/users';
    });
});

$this->prefix('admin')->group(function () {
    $this->get('users', function () {
        // Matches The "/admin/users" URL
        return '/admin/users';
    });
});

Of course $this can call its own protected method, so it does, and breaks, because while the facade version will correctly call Router::__call, and then filter its way into RouteRegistrar, the facade-less version will instead call Route::prefix and break.

What a mess.

The workaround to this, which I eventually ditched in favour of returning to facades, is to explicitly call __call and wrap the arguments with an array

Which means these three approaches are all equivalent:

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        return '/admin/users';
    });
});

$router->__call('prefix', ['admin'])->group(function () use ($router) {
    $router->get('users', function () {
        return '/admin/users';
    });
});

$this->__call('prefix', ['admin'])->group(function () {
    $this->get('users', function () {
        return '/admin/users';
    });
});

In the end, I ended up going back to using facades for routing.

Multiple Subdomain and TLD Routing in Laravel 5

A recent project I’ve been working on has a requirement to have two concurrent versions of an application running on different subdomains.

It looks something like:

  • sandbox.example.com – A sandbox version of the application
  • live.example.com – The live version

The local developer version has the same URLs, just with a .test or .local TLD.

Laravel 5 supports subdomain routing, but not really for this use case, and honestly, not all that well out of the box.

This is the example found in the documentation:

Route::domain('{account}.myapp.com')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});

Problem 1: This route is now hardcoded to the .com TLD.

You can’t develop locally any more because your local environment will be probably on a .test TLD or another one of the reserved testing TLDs.

You might think that Laravel could intelligently swap out the TLD when it’s being ran in development mode, but no such feature exists. The code example from the docs cannot be copy and pasted into a real project and work as expected.

That said, one possible work around for this is to put the TLD in an environment variable that can be swapped out at run time.

Route::domain('{account}.myapp.' . config('app.tld'))->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});

Problem 2: This route now requires the {account} parameter.

Let’s name this route and try to use generate a link with the route helper.

Route::domain('{account}.myapp.' . config('app.tld'))->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    })->name('account.profile');
});
// Illuminate \ Routing \ Exceptions \ UrlGenerationException
// Missing required parameters for [Route: account.profile] [URI: user/{id}].
return route('account.profile', ['id' => 1234]);

The subdomain parameter is required but not provided, so UrlGenerationException is thrown. Also note that Laravel doesn’t tell you which parameter is missing which is not very helpful!

Obviously you can fix this by providing the subdomain in every call to route, but this has obvious problems:

  • All controller methods will need to accept the subdomain as a parameter and pass it along to the view.
  • All calls to the route helper will need to specify the subdomain

This perhaps makes sense if you are basing application logic on different subdomains, but in my case I’m not.

The solution

In your EventsServiceProvider, listen for the RouteMatched Event. When it fires, Laravel will have matched the route to use and will populate the current route with the parameters from the request.

You can inject these into the UrlGenerator‘s default values.

From this point on, the parameters you specified in the domain of your route will be filled with whatever matched them.

//EventsServiceProvider.php
protected $listen = [
   RouteMatched::class => [
       SetUrlDefaults::class,
   ],
];
//SetUrlDefaults.php
use Illuminate\Routing\Router;
use Illuminate\Routing\UrlGenerator;

class SetUrlDefaults
{
    private $urlGenerator;
    private $router;

    public function __construct(UrlGenerator $urlGenerator, Router $router)
    {
        $this->urlGenerator = $urlGenerator;
        $this->router       = $router;
    }

    public function handle(): void
    {
        $this->urlGenerator->defaults($this->router->current()->parameters);
    }
}

You could also use the URL and Router facades here if you really want. I prefer dependency injection.

Conclusion

Maybe all this seems obvious in hindsight, but figuring this out took me several hours and many missteps in the wrong direction.

It’s disappointing to me that examples in the official documentation have implementation issues that require either a good amount of experience of the framework or more advanced code splunking abilities.  I would put my solution and debugging above what I would expect from a junior or even mid tier developer.

Stride and the ninety-nintey rule

This was written before the partnership between Atlassian and Slack was announced, discontinuing Stride after only less than a year of it being released.

In the development world, there is a saying:

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time

— Tom Cargill, Bell Labs

Case in point, Stride. The new-ish Slack competitor from Atlassian and replacement to Atlassian’s much neglected HipChat.

Stride is 90% there and it is a massive step up from HipChat, but that’s not saying much considering that HipChat was only marginally better than tin cans and string.

I want to go over some of the reasons why I don’t like Stride. It is an interesting case, because it’s all the little things that are hard to pinpoint that make Stride so much inferior to Slack but hard to tell why.

Too much whitespace

The biggest problem is the overuse of whitespace.

Compare Stride:And Slack:Stride has four columns, whereas Slack has just three (or two if you only belong to a single organisation).

Stride has so much whitespace between everything. The chat pane even has a copious amount of padding either side of the text.

Look at the amount of dead space there is on the far right and left columns in Stride. The right panel is especially egregious because it contains the online users indicator, files, highlights, meetings, apps, and app icons. Really all of that could be moved into the top panel or into a menu (like Slack does). All of this stuff is used so little it does not deserve an entire column that can’t be collapsed or shrank.

Look at the avatars – despite the excessive padding, they are smaller in Stride

There is also a forced circular crop applied to them which manages to mangle every single avatar upload to it in our particular instance. Nobody has an avatar that looks better in a circle. Nobody.

Look at the space between individual messages. Slack is noticeably more compact, and it’s not even in its compact setting.

Stride has these panel things that notify you of changes from JIRA, etc, or when meetings start. Again, they are simply too big.

Just moving an issue about or changing its status easily fills up a the entire vertical space of the window. There’s no need for the extra line between the title and the info at the bottom. There’s no need for it to be wrapped in a grey box. Also of note, this screenshot shows a bug where the avatar for me next to Assignee is missing, despite me having an avatar in both JIRA and Stride.

Look at the sheer amount of space between rooms and people. In the space Stride takes to list 4 rooms, Slack can list about 7. Another thing to see here are the icons next to rooms. They are too small to tell apart, but take up too much space. The worst of both worlds. You also cannot customise them much. You can only pick out from premade icons, and there seems to be no way to get that pretty rainbow effect in reality.

Missing features

1. Stride does not let you reply to messages with an emoji like Slack or Discord.

This has become an invaluable feature for me in Slack. It lets you quickly and silently acknowledge something. And often gets used for great humour…

Meanwhile, Stride does have Actions and Decisions. Two features I have literally never used.

2. Stride does not provide typing indicators either for chat rooms or PMs.

This is really useful when you are chatting in a busy room, or when something big is happening. It’s a strange omission.

3. Dark mode

Slack doesn’t have this, but HipChat did. Stride is missing it too. Given that Stride is a forced upgrade from HipChat in organisations that upgrade, its omission is shocking.

I went to WordCamp London 2018

Between the upcoming VuTales rewrite, some freelance/ consultancy projects, and my love/hate feelings towards WordPress in general – I ventured (alone!) to London to attend WordCamp Lodon this year.

In numbers: this was the fifth PHP conference I’ve been to, the second I’ve personally paid for, and my first WordCamp. Now I think I finally have enough experience of these things to really compare and contrast them and give a proper review.

I’ve found conferences to be a mixed bag overall and feel the same about WordCamp. Overall though I enjoyed my time there like I have at all the conferences I’ve been to, and I’ll attend next year and maybe some other regionals. Maybe Brighton – I’m not sure.

The talks

When I say that I’ve found conferences to be a mixed bag, what I really mean are the talks.

I contend that the best talk I’ve ever seen (in person too!) was Michael Cullum‘s talk at PHPNW 2015: phpBB, Meet Symfony. The reason is because it was a talk that you couldn’t really condense down into prose and make into a blog entry. It was entertaining but also informative and told a story that worked best as a talk.

Conversely, I’ve attended talks where people have literally copied a top 10 list from their website/blog and converted it into a talk. These kinds of talks tend to be terrible.

Although a lot of talks could probably be blog articles, the better ones present something difficult and use the medium of a presentation to explain things that are hard to express or explain in prose. And to that, one or two of talks I attended I felt were too remedial. To be clear, this is a gripe with all conferences I’ve been to, not just WordCamp. That said, it’s a balancing act – and what I consider simple as a senior developer others may not.

David Lockie‘s talk on Blockchains was interesting. A shame it ran out of time towards the end. I am very dubious towards the abundance of blockchain related tech, but the talk was a good starting point for some of the use cases that are not cryptocurrency. I do think that there wasn’t enough linking back to WordPress in the talk – especially given that the title started with “WordPress and Blockchain”.

Tammie Listers‘ talk on Gutenberg was a good jump into the subject especially for a back end dev like myself.

Unfortunately there were two talks I attended that I felt were poor.

The first was a talk on an idea that I felt was very technically questionable. It was a solution looking for a problem that was over-engineered, brittle, and had significantly better and easier options other than the one being proposed.

The second was apparently showcasing a product that was meant to be immediately available but as of writing isn’t (also the website is still broken). It also advocated using WordPress as a development framework. WordPress is not a development framework, it’s a CMS . Do not try to use it as a framework. You will write terrible and inflexible code. There are much, much better alternatives for that (Lavavel, Slim + components, etc).

Things I liked

The ticket price – At £40 it was a steal. Under half the next cheapest ticket (£92 for PHP South Coast’s Early Bird ticket). One of the key reasons I decided to go was because it would be so affordable. The biggest expenditure was the hotel costing something like 2/3s of the overall cost for me.

The venue – Having been to now PHP South Coast and PHP North West – the latter in a dedicated conference location – I was shocked at how good the venue was. By far the nicest I’ve been too. The aircon worked. The lecture rooms were all well sized and only once did I see one end up standing room only.

The catering was the well above what I was expecting and organised with military precision and efficiency. Between talks there was free tea, coffee, apple and orange juice as well as biscuits and fruit.

There were security guards haunting the entrances to the buildings, which felt a bit odd – but whatever.

Sponsors – sponsors at conferences are what pays for stuff, but WordCamp despite the low ticket cost didn’t repeatedly bash me over the head with announcements from sponsors and overbearing calls to go visit their stands. As before, very impressed given the low ticket price.

There was one sponsor whose stand I refused to visit because I find their advertising to be repugnant.

I managed to haul a nice water bottle, a 32GB USB stick, two notepads and a good quality (if small!) mug all from Timpani. As well as a fair amount of stickers, pens, and assorted whatnot from everyone else. I now have WordPress and Jetpack badges on my backpack and stuck a few of the stickers on my MBP.

Things I didn’t like

Premier Inn – Why must you be so expensive and the rooms always so hot!? Argh!

Fifty Shades of Grey

Fifty Shades of Grey, is the movie adapted from the book, edited from the fan fiction of the movie adapted from the book that’s slightly derivative of Buffy the Vampire Slayer.

FSoG is interesting in that in a world that seems [sc name=ttext title=”hur hur” text=”dominated” ] by movies targeted to males aged 29 or younger, FSoG is based on material almost exclusively for middle aged women. I say this as a 23 year old male who’s read the entire trilogy (yes, really).

The books are written by a 50-something woman for other women to flick their bean to fantasising that they’re a 21 year old tight vagina’d virgin; with a damaged but fixable billionaire obsessed with them who they can fall in love with, fix with motherly care, and live happily ever after in domestic bliss. Oh and there’s some kinky stuff too.

FSoG is a Self-Insert Fic – Ana is basically perfect. She’s 21, a virgin and has mind shattering orgasms and can deep throat a cock like a champ. The movie adapts out most of this, and a lot of the stuff that seems to make the book appeal to women. That scene where Grey casually pulls out Ana’s bloody tampon and earns his red wings is gone from the movie, as is all the inner goddess/ unconscious talk. The movie takes a disappointedly conventional approach to the depiction of genders. Gone too is the scene where Ana sucks on a “Chrstian Grey flavor popsicle” (direct quote from the book).

There are plenty of fairly tame shots of Dakota Johnson’s breasts that feel almost exploitative given that you see nothing of Jamie Dornan. This is a movie that should be for women, but there’s not a single cock shot let alone a shot of an erection. FSoG isn’t the movie I thought that would be pushing boundaries, but it’s disappointing that it doesn’t even try.

The narrative is a 1:1 copy of the novel, which means virtually all of the narrative criticism from the books carries over. The deception of BDSM is still shaky. EL James uses BDSM as a plot device to have Grey’s early abusive upbringing cast a dark shadow into his adulthood, for Ana to fix with her motherly love. I believe that James is intelligent enough to know her writing conflates abuse and BDSM, but not a good enough writer to fix it.

I feel like this mutes, if not invalidates, most of the criticisms I hear about the narrative. Yes, Christian Grey isn’t very believable as a character who could exist in reality,  but it doesn’t really matter. The story is a what if – what if your self inserted avatar could meet, win over and fix a damaged billionaire.

Sadly it carries over some of the poor research from the book. Grey surprising Ana at her job in the hardware store is passable as a romantic gesture, but cable ties are not good for bondage! And of course, it carries the clunky dialogue from the books.

FSoG, the movie, is tamed from the lewd female fantasy of the books to the middle of the road romantic drama. Albeit with a little more nudity and a little more BDSM than one would typically expect from a mainstream movie.

Rating

Passable for fans of the books. Missable for everyone else, unless you want to see what all the fuss is about.

4.5/10

The Interview

I’ll admit I only watched this film because of the Sony hack and surrounding drama.

I don’t really like Seth Rogen comedies, or modern American comedies in general. They lack the critical element of a comedy, actually being humorous. Most seem to rely on crude humour that I simply don’t find funny.

The Interview is laced with crude innuendo, toilet humour and juvenile sexual references throughout. See for example, one of the introductory shots of Agent Lacey played by Lizzy Caplan.

movies.vuii.co.uk_vlcsnap-2014-12-25-00h24m10s13

That said, the humour doesn’t totally fall flat.  The opening scene with Eminem goes in a radically different direction than what I was expecting much to my surprise and delight. It then proceeds to makes jokes like… well this:

movies.vuii.co.uk_vlcsnap-2014-12-25-00h33m00s190

There are a few jokes that are built up and have a pay off. Only a few though, the majority of the film is very low brow. The film passed the six laugh test, albeit barely.

The reddit /r/movies discussion on the film neatly picks out the funniest gags from the movie. You can read that and get about 90% of the humour. There isn’t all that much more to be said about the humour really. Do you like Rogen/ Goldberg movies? Then you’ll like The Interview. If not, read a couple reviews and the Eminem scene and you’ll get as much as you’re going to get from the film.

Seth Rogen as Seth Rogen Aaron Rapoport plays the same character he does in every film he’s in acting as the plot driving force as the practically only character with any scenes which are more plot orientated than just silly gags. James Franco is passable as David “Dave” Skylark, primarily pulling funny faces, doing silly things and acting like a child. It’s Randall Park as Kim Jong-un who takes the crown though making the scenes with Franco passable, and getting it past the aforementioned six laugh test.

The film dabbles just a little in satirising the trashy celebrity “news” coverage, but barely scratches the surface other than with some throw away jokes and cameos. It’s so subtle, it’s almost accidental.

Rating

The Interview appeals to a certain group of people, of which I don’t belong to. If you’re reading this, I suspect you don’t either. Watch it once because of the Sony drama, then never again.

3/10

Edge of Tomorrow

It’s 15 minutes into the future and aliens on meteors invade Germany and quickly spread across much of Europe. The world’s militaries conglomerate into the United Defence Force and prepare to take on the aliens at the beaches of France from a staging area at Heathrow.

American Major and PR man William Cage finds himself in London expecting to do more PR work, but instead finds himself expected to take up combat duties. He attempts to cowardly weasel himself out of duties but fails and gets sent to the front lines. The battle is hopeless, the aliens knew about the offensive and decimate the UDF troops. Cage sees his squad massacred in front of him, but manages to kill the alien just in time, spilling its black, oily, acid-like blood all over him and dissolves…

And then he wakes up the previous morning and repeats the process – a la Groundhog Day.

Cage melting in Alpha blood
Cage melting in Alpha blood

It’s  a film I’ve been waiting to watch for a while, and was expecting to be the roughly mediocre Hollywood sci-fi. Going in, all I knew about the film was:

  1. It’s Action Sci-fi
  2. Stars Tom Cruise
  3. Tag line is “Live. Die. Repeat.”
  4. …Aren’t those the exo-skeletons from Elysium?

Like most films with Tom Cruise, it’s hard to see a character and not Tom Cruise. This film is no exception, especially during the newsreel montage interspersed with clips of Cage doing his PR work. The difference between repurposed stock footage and scripted footage is somewhat jarring. Cage soon has his character established, but still remains inconspicuously Tom Cruise throughout the film.

The film seems to deliberately establish Cage as a confident PR man during the montage, then turn face heels when he’s drafted into combat duty. Cage first attempts to bargain and then blackmail to get out. My inference was that there was some nefarious reason he was being sent into combat or that there was a reason why he was so desperate to get out. Throughout the film I was expecting this to pay off, but it never did and seems to just be there to make his character arc, unlikeable coward to war winning hero, just that little bit bigger. Whatever. It works.

Emily Blunt plays the stoic Sergeant Rita Vrataski, who we learn had previously been in a timeloop like Cruise. Rita is a badass compared to Cage – it’s nice to see a female in such a dominating role. Rita is also just a little bit to genre savvy – she knows how the time loop blood works and she consistently has no hesitation shooting Cage in the head to reset a loop.

The pacing feels just right. The film never lingers for too long at any stage of Cage’s development with his ever increasing ability and progress to defeating the aliens rising at a consistent and steady pace.

Rating

Overall a very good film. A very good Sci-Fi. It’s a shame it was such a box office bomb. Sci-Fi needs more Edge of Tomorrows.

8/10