September 2, 2015 22:58

I've uploaded most of my photos from Korea. Some of my favorites below

Photography, Travel

August 31, 2015 11:10

Router binding in Laravel used in conjunction with middleware makes model management really easy. No more passing in IDs, loading models, and checking permissions for every controller method.

This example makes use of my ślimak slugged models package

class Album extends SluggedModel
{
    protected $appends = ['url'];

    /**
     * @return string
     */
    public function getUrlAttribute()
    {
        return '/albums/' . $this->slug;
    }
}

In app/Http/routes.php

Route::get('albums/{album}', 'AlbumController@show');
Route::get('albums/{album}/edit', 'AlbumController@edit');

In app/Providers/RouteServiceProvider.php

$router->bind('album', function ($slug) {
    return \App\Album::findBySlug($slug);
});

Finally, in your controller:

use App\Album;

class AlbumController extends Controller
{
    public function show(Album $album)
    {
        return view('albums.show')
            ->with('album', $album);
    }
}

Now when linking to your album's permalink in Blade, you can simply link to {{ $album->url }}. To link to the edit URL, just link to {{ $album->url }}/edit

This gets even more useful when you start to add middleware. For example, I can check to make sure only the album's owner, and admins, have permission to edit the album.

class Owner {
    public function handle($request, Closure $next, $resource)
    {
        $user = \Auth::user();

        if ($request->{$resource} && $request->{$resource}->user_id != $user->id && ! $user->is_admin) {
            abort(403);
        }

        return $next($request);
    }
}

Then if I add this middleware to the controller it takes care of that protection easily:

class AlbumController extends Controller
{
    public function edit(Album $album)
    {
        $this->middleware('owner:album');

        return view('albums.edit')
            ->with('album', $album);
    }
}

Laravel, PHP

August 28, 2015 23:33

I finally finished converting my site entirely into Laravel 5.

In addition, I've started using a bunch of awesome packages:

  • Parsedown (all blog posts are written in Markdown now)
  • SimpleImage to resize photos
  • Highlight.js for syntax highlighting in blog posts (see the previous post)
  • Dropzone for drag and drop photo uploads (in my private admin section)
  • Font Awesome for scalable icons in a bunch of places
  • Bower to manage all of those front-end dependencies and keep my repository clean

I've also done some major redesign, the most noticeable ones being the homepage and photography pages. Photos now scale to fit your window. As a result I decided to increase the quality of the photos as well, from 800 pixels wide to 1600.

News, Website

August 25, 2015 15:45

Here is a simple way to manage storing and displaying dates and times in Laravel in the user's timezone.

First of all, we are going to store all dates and times in UTC. In Laravel, make sure that the timezone is set to UTC in config/app.php

Second, you'll need a way to let users specify their timezone. I'll leave that up to the reader for now. You can either do this through a settings page, or auto-detect it with JavaScript. I prefer to store timezones as an hourly offset from UTC, e.g. -5 for Central time, -4 for Eastern.

Finally, we'll have a base model that all of our Eloquent models extend. If you already have one you can just add this function to it. Save this into app/Model.php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model as Eloquent;

class Model extends Eloquent
{
    /**
     * Display timestamps in user's timezone
     */
    protected function asDateTime($value)
    {
        if ($value instanceof Carbon) {
            return $value;
        }

        $me = \Auth::user();
        $tz = $me->timezone ?: \Cookie::get('tz');

        $value = parent::asDateTime($value);

        return $value->addHours($tz);
    }
}

Make sure to update all of your existing models to extend the new model class instead of the Eloquent model.

Eloquent has an asDateTime function which converts MySQL timestamps to Carbon entities. It calls this function both when setting and getting timestamps. So first we check if the $value is already a Carbon instance - if so, we return it without any adjustments, because when setting a timestamp we do NOT want to apply the timezone adjustment. **

The next two lines are simply retrieving the user's timezone from one of two sources, an attribute or a cookie. If you are setting the cookie through JavaScript, make sure it is added as an exception to the EncryptCookies middleware. Again, this value in my case is an hour offset e.g. -5. Finally we get the Carbon instance that Eloquent returns and add the hour offset.

Now all dates and times will be stored in UTC but displayed in the user's timezone!

Two caveats that I've discovered so far. First, when setting timestamps, to make sure to set them as Carbon instances. (You don't need to worry about the created_at, updated_at, or deleted_at timestamps, just any others that you use). For example, do this:

$post->scheduled_at = Carbon::now();

Not these:

$post->scheduled_at = \DB::raw('now');
$post->scheduled_at = date('Y-m-d H:i:s');

Otherwise your timestamps will not be stored in UTC.

The more annoying caveat is that for certain Carbon methods, you need to undo the hour adjustment in order for them to work correctly. The two that I've discovered so far are age and diffForHumans, because both of those methods compare the given timestamp to the current timestamp (in UTC). So if your user's timezone is -5 hours, and you ask for $date->diffForHumans() on a brand new timestamp, it'll falsely report that the date is 5 hours ago. I personally added a helper method undo_tz to wrap around those methods. For example:

echo undo_tz($post->created_at)->diffForHumans();

And the method:

function undo_tz(Carbon $date)
{
    $me = \Auth::user();
    $tz = $me->timezone ?: \Cookie::get('tz');
    return $date->subHours($tz);
}

Laravel, PHP

August 22, 2015 21:28

I've recently discovered how great collections are in Laravel.

No more array_summing or looping. Collections are easy to extend. First add this method to your model:

public function newCollection(array $models = [])
{
    return new TransactionCollection($models);
}

Then create the TransactionCollection class and extend it as you wish. For example:

class TransactionCollection extends Illuminate\Database\Eloquent\Collection
{
    public function total()
    {
        return $this->sum('amount');
    }
}

Now you can easily display the total of all transactions with $transactions->total()

Laravel, PHP