Update to LaraBlog Package

Thursday 02 February 2017

I made an update the other day to the LaraBlog package. I added the ability to reply to comments and for users to delete their own comments. At first I had a hard time figuring out how to display the nested comments, as theoretically you can have an infinite number of replies to a comment, so how do you determine the inset when Bootstrap only has 12 columns?

This was easily resolved by two realizations:

  1. With Blade includes I can recursively include a template into itself.
  2. Bootstrap doesn't use 12 absolute size columns, but divides the available space into 12 columns.

So what I did was update my Blog model so that when getting the comments for a blog I only get the original comments, not replies. Then I added to my Comments model a function to get the replies to a specific comment. 

I already had my comments display in a separate view which displayed the form to leave a comment and then all of the comments for a specific post. I separated this out into multiple views:

  1. The old comment view - comments.blade.php - now has the formatting and then includes the _form.blade.php and the comments index view.
  2. The _form has just the form to submit a comment.
  3. The comments index loops through the comments and for each:
    1. Includes the comments show view and...
    2. Loops through the replies and includes the index for each
  4. The show.blade.php displays each comment and includes the _form which is hidden until the user clicks on reply.

The index, when including replies, leaves a blank column to the left of the replies, which, when recursively included in the levels of nested replies, will size itself to the available space, so that the indent gets smaller on every nested level of replies - but there could theoretically be an infinite level of nested replies without breaking anything.

I had never thought of recursive including of views until I came across the idea on stack overflow while looking for a solution as to how to address what looked to be a nightmare of infinite nested loops; but that idea provided a simple and elegant solution to what was looking to be a complete mess.

Labels: coding, laravel
1 comments

Laravel 5.4

Sunday 29 January 2017

Yesterday, after playing around with Laravel 5.4 for a few days on my dev environment, I upgraded this site. The only issue I had was with the testing, which I addressed in the previous post. The PHPUnit issues were easily resolved by installing laravel/browser-kit-testing and updating my existing tests to reference the new BrowserKitTest.php class instead of the old TestCase.php. The issue of the naming of the test files was in fact due to autoloading, and I resolved it by adding the following to my composer.json under the "autoload-dev" classmap section:

"tests/BrowserKitTest.php"

After upgrading from 5.3 to 5.4 you need to clear your view cache, which you can do with:

php artisan view:clear

And the upgrade guide also suggests clearing the route cache with:

php artisan route:clear

I have never had an issue with the route cache, but I have often had issues with the view cache, so I personally clear my views after most of my updates.

The other thing to be aware of when upgrading to 5.4 is that tinker is no longer part of Laravel, so needs to be installed separately as laravel/tinker. I believe it is installed by default with a new installation of 5.4, but if you are upgrading you need to do this, especially if you use tinker as frequently as I do.

I haven't used any of the new features of 5.4 yet, from reading the upgrade guide there aren't really many features that jump out at me as something I would make a lot of use of, but I'm sure I will in the future.

Labels: coding, laravel
1 comments

PHPUnit and Laravel 5.4

Thursday 26 January 2017

I just upgraded one of my projects to Laravel 5.4 and I immediately had some issues with PHPUnit tests. They changed the testing framework in the new release, and you will need to alter any existing tests that use browser testing accordingly. This is all documented in the upgrade notes under the testing section. 

After making the changes listed in the documentation most of my tests ran fine, but I still had one that kept giving me an error I couldn't figure out: 

Fatal error: Class 'BrowserKitTest' not found

I copied over the code from tests that were working into the file with the error and the exact same code that was working in one file was giving me this error in a file with a different name, which was very frustrating, to say the least. After a little bit of trial and error I came up with a way to fix this issue, which was simply to rename the file. The original name of the file was BlogTest.php, but when I rename it to CBlogTest.php it works fine. My guess as to why this is is that it loads the php files in the /tests directory in alphabetical order and it couldn't find the class BrowserKitTest until it had loaded that file. I assume this is because I have upgraded from 5.3, and the new BrowserKitTest.php file needs to be added to some autoload file somewhere.

I'll post more thoughts about Laravel 5.4 after I've had some time to mess around with it. So far, other than this one issue, all my code has worked fine after upgrading.

Labels: coding, laravel
No comments

I figured out how to resolve the session and middleware issues I mentioned in the previous post. I previously had the middleware in the $middleware array in /app/Http/Kernel.php. The Laravel session is started with the middleware StartSession, which is in the $middlewareGroups array under web. I moved my middleware to the web $middlewareGroups and put it after StartSession, and now the Middleware can access the session. The only difference is that the Middleware will only be run on requests that are part of the 'web' group instead of on every request, but this actually makes more sense in this case.

After having figured this out the unexpected behavior I was witnessing before makes sense now.

Labels: coding, laravel
No comments

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

Archives