Back when I wrote the code to localize this site I ran into some unexpected behavior that I couldn't figure out. I have two ways to set the language here - first you can do it by subdomain, fr defaults to French. Then regardless of the subdomain you can use the drop-down menu in the navbar to set the language, which sets a session variable. To handle the subdomain I have a middleware which runs on every request and sets the locale to the language specified by the subdomain, if a subdomain is used. I was confused by why the subdomain could be overwritten by the session var set with the drop-down menu, but I ended up leaving it that way because it worked better than the way I had originally envisioned.

This weekend I decided to try to get to the bottom of why the behavior was different than what I would have expected and I discovered something a bit bizarre about Laravel sessions. In the middleware the session is always empty, but I can set a variable and access it from within the middleware. By the time I get to the controller the values put in session in the middleware are gone, and replaced with the values previously set in the session. I haven't looked at Laravel's session code yet, but I assume that however it stores session variables is initialized somewhere between the middleware and the controller. Before I started with Laravel, I used to keep session variables in $_SESSION, so the way it works now is a bit confusing to me.

To explain, I have the following in my middleware, which is registered to run on every request:

public function handle($request, Closure $next)
{
  echo "1: " . session('foo') . "
";
  session(['foo' => 'bar']);
  echo "2: " . session('foo') . "
";
  return $next($request);
}

When I load any page, it outputs:

1: 

2: bar

If I then in a controller execute:

session(['foo' => 'baz']);

And load another page which just contains:

echo "3. " . session('foo');

The output, with the middleware is:

1. 

2. bar

3. baz

So, in the middleware you can set and access the session, but the session doesn't persist past the request, and by the time the controller is executed that session has been replaced with a session that does persist from the previous request. I can think of a few ways around this, but it doesn't seem worth the effort involved. For me, the result of this issue is that I have to include a call to a helper function in every single page I want to translate - if I want to keep the drop-down menu to translate. My other option would be to just do the localization based on subdomain and have the drop-down menu link to the same page on a different subdomain instead of just setting a variable and reloading the same page, which may in fact be a better solution, but again maybe not worth the effort.

I don't know if anyone else has run into this behavior in Laravel, I also don't know if this behavior is intentional or not, but if you are trying to access or set session variables in a middleware with no luck this is likely the reason.

Labels: coding, laravel
No comments

New Localization Package

Monday 23 January 2017

As a result of figuring out what was going on with the session and the middleware yesterday I was able to rewrite my localization code and greatly simplify the whole project. Previously I had been calling a function to set the language in every single controller action that returned a view, I was able to eliminate all of that and consolidate everything in the middleware.

I made another package - escuccim/translate - that has two parts:

First is the middleware which does two things:

   a. Checks the subdomain to see if the subdomain corresponds to a language. If so it sets the app locale to the appropriate language.

   b. Checks to see if there is a session variable with the language in it, if so it sets the app locale accordingly.

The key for me here is that if there is a locale specified by both the subdomain and the session, the session takes precedence, thus allowing the user to display the page in whatever language they desire, irregardless of the subdomain.

The second component of the package is a route which accepts a locale as a parameter and sets a session variable to that locale, so that the middleware can then access that information.

This package is available on my GitHub and my Packagist. When I am done testing it you can install it via composer.

I'm glad I took the time to investigate the session/middleware issues because figuring that out allowed me to replace code that was unneccessary and ugly to look at with a nice, simple, elegant solution.

Labels: coding, laravel, localization
No comments

More Packages

Wednesday 18 January 2017

After having written a few packages I believe I now have it down cold. I started another one just a few hours ago and am already finished with it and had no problems at all this time. The latest package is escuccim/recordcollection which is a package with the code I use for my searchable database of vinyl records. Yesterday I did a package with the code for my online CV in it, so at this point this site is basically just the static pages and four Laravel packages. While it's a bit more complicated to make changes now because I need to go to the package code, alter it, then push it up to git and then composer update, I think the extra couple steps is well worth it in terms of maintainability and portability. My packages are largely self-contained, with their own views, controllers, models, etc. so I can just add them to a project and everything will (almost) magically work.

I don't really see much demand for a package like this for other people so I haven't thoroughly tested this one in projects other than my own, so it may not work properly out of the box. If you have a large record collection that you want to store online I would recommend Discogs.com. It has a lot more features and functions than my package does, but I don't have the time to go through each of my 2,000 or so records and add them to my Discogs collection and I've had most of mine in a database for about 15 years, so I'm sticking with my own for now. If I was starting over from scratch I'd probably put it in Discogs and then pull the data from their API to display it here.

My other packages are all tested and working on fresh installs of Laravel, so feel free to use them if you want. 

Update - I ended up testing the recordcollection package on an almost fresh install of Laravel and I fixed the issues I found, so it should be in mostly usable shape.

Labels: coding, laravel
No comments

Using Laravel Packages

Sunday 15 January 2017

Since I have multiple sites that use almost the same code I have been trying to consolidate shared code into Laravel packages for ease of maintenance. This weekend I did my second package which is escuccim/sitemap which contains my code for generating XML sitemaps for Google. Since I have this site available in more than one language and I use subdomains to set the default language it made for very messy and confusing hardcoded sitemaps. I was able to shrink the code for each sitemap down from hundreds of lines to about 50 by putting the subdomains and the corresponding language in a DB table and then looping through them to output the URLs and hreflang tags in the sitemap. This time the process of writing the package was quick and easy using the same method that I struggled with last time.

Once I had that working I went back to my LaraBlog package which I added translation functionality to. I had one big problem which took me a while to figure out which was that it wasn't loading the translations at all, it was just displaying the key: 'escuccim::blog.key'. I researched this and found no answers, but was able to solve it by changing the namespace or hint to larablog. I am not sure why this worked, but I suspect it may be because I was using the namespace escuccim for the views and maybe they conflicted? Anyway if anyone else is having this issue try to change the namespace/hint.

When I had the blog package translating properly I deleted the code I was using for this site for the blog and the sitemap and replaced it with the new packages. So far everything seems fine, but I will give it a day or two before to turn up any issues before I start using the packages in other places. 

I have a few other things I want to put into packages, and I just have to say that Composer makes my life so much easier! Instead of having to go through my code line by line to copy changes from one place to another while avoiding any functionality that differs from one project to the next I just update the package and then composer does the rest!

Labels: coding, laravel
No comments

Apparently Google doesn't like it if all of your pages have the same title and meta description tags. So yesterday I decided to write unique titles and description tags for all of my pages. At first I did this by setting two variables - $title and $description - in my controllers and then passing them to the views, where I displayed them in my layout/app.blade.php. Since I have multiple languages in this site I ended up setting them like this:

$title = trans('whatever.pagetitle');
$description = trans('whatever.pagedescription');

This seemed a bit inelegant and I thought I could come up with a better way, which I did this morning. What I did was set up a file in my lang directory I called metadata.php. A sample of this is here. This file contains for each page a key for title and description as follows:

'/home-title' => 'Title';
'/home-description' => 'Description';

By using the URI appended with the value I want I was able to consolidate all of the values into one file for ease of use, and I was also able to make a helper function to get those values from the translation files and display it, so that the same exact code could be run on every single page and return the data I need. 

The helper function I used is on my GitHub here, and if it doesn't find data for the page it is looking for it has a default title and description it uses. For pages like blog articles and individual records I use the same title and description, but I still specify $title in the Controller, and if the value exists it is appended to the title in the layout file.

I like this solution because it allowed me to delete the redundant and ugly code in the controllers where I specified a title and description for each page with a function that pulls the data from one location, and if the data doesn't exist it substitutes a default in, instead of either failing or not doing anything. The code I used is on my GitHub Gist.

Labels: coding, laravel
No comments

Archives