02
Jan '18

by john

I’ve tried various different mechanisms for versioning records over the years, but recently I’ve been tasked with creating a versioning solution that is unobtrusive, will bolt-on for an existing Laravel site, and which doesn’t require any structural changes to existing tables.

The way I decided to approach this was via an ‘Archive’ table (with associated model) with a polymorphic relation to the parent record, whatever it be, and a trait that could be applied to any other models and would automatically create a versioned copy on of the parent whenever it was saved. Broadly speaking it looks like this:

Migration

Schema::create('archives', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('parent_id')->unsigned()->index();
    $table->string('parent_type');
    $table->longText('data');
    $table->timestamps();

    $table->index(['parent_id', 'parent_type']);
});

Model

class Archive extends \Eloquent {

    protected $fillable = ['parent_id', 'parent_type', 'data'];

    /**
     * Parent relationship
     */
    public function parent() {
        return $this->morphTo();
    }

    /**
     * Deserialise / hydrate
     * @return \Eloquent
     */
    public function deserialise() {
        $deserialised = new with($this->parent_type);
        $deserialised->fill(unserialize($this->data));
        return $deserialised;
    }
}

In practice I would usually also store additional meta-data in the archive record, relating to the user that did the save, and any contextual information. Of course we’d want timestamps here too.

Trait

trait Archivable
{
    /**
     * Custom boot method
     */
    public static function bootArchivable()
    {
        /*
         * Trigger on update (nothing to archive on initial create)
         */
        static::updating(function ($record) {
            $record->archive();
        });

        /*
         * Trigger on delete
         */
        static::deleting(function ($record) {
            $record->clearArchives();
        });
    }

    /**
     * Archive relationship
     */
    public function archives() {
        return $this->morphMany('\App\Model\Archive', 'parent')->orderBy('id', 'desc');
    }

    /**
     * Clear all archives
     */
    public function clearArchives() {
        $this->archives()->delete();
    }

    /**
     * Archive
     * @return \App\Model\Archive
     */
    public function archive() {
        return $this->archives()->create([
            'data' => serialize($this->getOriginal())
        ]);
    }

    /**
     * Get previous version
     * @return \Eloquent
     */
    public function previousVersion() {
        if($archive = $this->archives->first()) {
            return $archive->deserialise();
        }
    }

    /**
     * Get all archives hydrated
     * @return Collection
     */
    public function allArchives() {
        return $this->archives->map(function ($archive) {
            return $archive->deserialise();
        });
    }
}

So the entire parent record is serialised into the ‘data’ field of a new ‘Archive’ record each time the parent is updated. I’ve shown example accessors for previous version(s) with a deserialise/hydrate function to recreate the parent record from an archive.

14
Nov '17

by john

I always make security my top priority in any application I build. This is fine when I build something from scratch, but when I inherit a site I often encounter some interesting challenges. In this instance I was building a new application to sit on an existing database, which was in pretty decent state other than the fact that all user records had MD5 passwords!
I couldn’t force all users to go through a password reset process, and I wasn’t going to start trying to crack all the passwords (yes, yes, I know that could have been done but it’s too black-hat for my taste), so I had to think of a mechanism to convert the passwords transparently to the users.
I came up with a system that overrides Laravel’s standard login function, within the Illuminate\Foundation\Auth\AuthenticatesUsers class to do the following:

  • Find the user being logged in by their email address
  • Check if the user record has been flagged as ‘safe’ (this is just an additional boolean column on the record)
  • If not it compares the user record’s password with an MD5 hash of the submitted password
  • If the MD5 hash matches then it produces a new secure hash (bcrypt is currently the Laravel standard) of the submitted password, stores it on the user record and flips the ‘safe’ flag
  • Then the main Laravel login function is called, which takes it all from there
use AuthenticatesUsers {
    login as coreLogin;
}
/**
 * Handle a login request to the application.
 *
 * @param  \Illuminate\Http\Request $request
 * @return \Illuminate\Http\Response
 */
public function login(Request $request)
{
    $credentials = $this->getCredentials($request);

    if(!empty($credentials['email']) && !empty($credentials['password'])) {
        if($user = \App\Model\User::where('email', $credentials['email'])->where('active', true)->first()) {
            if(!$user->safe) {
                if($user->password == md5(trim($credentials['password']))) {
                    $user->password = \Hash::make(trim($credentials['password']));
                    $user->safe = true;
                    $user->save();
                }
            }
        }
    }

    return $this->coreLogin($request);
}

And there we have it. Anytime a user with an old password logs in they will have it transparently updated and secured.

21
Oct '17

by john

Cloning simple markup is usually a fairly trivial task in jQuery, using the .clone() function, however it becomes a major headache when there are form elements involved. jQuery’s clone function effectively works based on the raw HTML of the page and as such any changes that have since taken place within the DOM are ignored. This doesn’t sound like such a big deal until you realise that includes changes to all form input elements; the clone will have these all reset to their initial values.

I’ve written a little function to get round this issue, which simply ‘fixes’ the current values fo all input fields (except files) in the markup, allowing them to be cloned correctly:

function fixValues(element) {
    element.find('input[type=text]').each(function() {
        $(this).attr('value', $(this).val());
    });

    element.find('input[type=checkbox], input[type=radio]').each(function() {
        $(this).attr('checked', $(this).is(':checked') ? 'checked' : '');
    });

    element.find('select option:selected').each(function() {
        $(this).attr('selected', 'selected');
    });

    element.find('textarea').each(function() {
        $(this).html($(this).val());
    });
}

Just trigger this function on the element you want to clone, immediately before calling the jQuery clone function (or, if you’re feeling really enthusiastic, you could even extend the clone function itself) and your values should all be copied over as expected.

14
Aug '17

by john

Maybe a bit of a niche thing to to here, but I found myself needing to do this as part of the process of transparently transitioning a site from an older framework. In effect I had two different apps with mod_rewrite rules to determine which served which pages. Critical to this was that I needed to make sure that these pages shared authentication and session data.

To start I had to move the authentication logic into the new Laravel app, as this would become the gatekeeper and the primary source of the session data. Then for all pages being served by the old app, I came up with the following to be included in the front controller of the old app, before its own app was initialised (we’re assuming here that “LARAVEL_ROOT” has been defined as the path to the Laravel application root):

require_once LARAVEL_ROOT . '/bootstrap/autoload.php';
$app = require_once LARAVEL_ROOT . '/bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

if(!empty($_COOKIE[$app['config']['session.cookie']])) {
    $id = $app['encrypter']->decrypt($_COOKIE[$app['config']['session.cookie']]);
    $app['session']->driver()->setId($id);
    $app['session']->driver()->start();

    if($app['auth']->check()) {

        /*
        Now I have access to the Laravel session via $app['session']->driver()
        e.g. $app['session']->driver()->get('site');

        And access to the auth logic via $app['auth']

        I can also call any registered facades too
        e.g. \Auth::user() \Request::get() etc
        */
    }
}

It’s best to to go too far off-piste here in terms of what you’re pulling out of your Laravel app, but it should give you all you need to share authentication and other critical session data.

04
Jul '17

by john

This was a bit of a challenge to myself to make PDF rendering much more simple to implement.

I came up with this middleware which will trigger if the request contains a ‘pdf’ parameter. As an additional nice-to-have if the request has a ‘ls’ parameter it will render landscape rather than portrait.

This uses Snappy PDF and assumes it is aliased simply as ‘PDF’ in the app config.

namespace App\Http\Middleware;

use Closure;

class PDF
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if($request->pdf) {

            //Useful to have in case there are any view changes that help with the render
            view()->share('pdf', true);
            view()->share('print', true);

            //Grab the response into a variable
            $response = $next($request);

            //Try to get title from the response, if not just take app name
            $title = (is_object($response->original) && $response->original->title) ? $response->original->title : config('app.name') . '.pdf';
            $orientation = $request->ls ? 'landscape' : 'portrait';
            return \PDF::loadHTML($response->content())->setOrientation($orientation)->stream($title);
        }

        return $next($request);
    }
}
29
May '17

by john

Like me, you may find the jQuery $.getJSON function really useful for quick GET-based AJAX calls, and lament the lack of a corresponding POST function. Well here it is:

$.postJSON = function(url, data, func) { $.post(url, data, func, 'json'); }

You’re welcome!

02
Mar '17

by john

As you probably know, for CLI PHP the pcntl_fork command works great for forking off new processes and allowing you to do some fun asynchronous things. However that command is generally not available when running as a web process via an apache handler or similar. I found myself wanting to have some asynchronous actions to be triggered via the web frontend and, after a bit of thought, I came up with the following:

exec('php ' . __FILE__ . ' &> ' . __DIR__ . '/output.txt &');

What we’re doing here is using an exec function to execute our current file in a process, push all output to a file (you can push to /dev/null if you want instead) and run it in the background. Hence the exec command will return immediately, the process will be off and doing its own thing, and you have, in effect, forked your process.

Now I know that if you’re using a full framework then there are better things you can do with queues, events, crons etc, but for a light script this can be pretty helpful.

As an additional quick help, if you want logic to be conditional on the process being executed then you can use the following to tell if you’re in CLI mode or not:

stripos(php_sapi_name(), 'cli') !== false
10
Jan '17

by john

Ever found yourself constantly pulling things out of the session to use in controller actions and thinking there must be a better way? Ever tried implementing a constructor to set instance variables but finding them empty? Well I’ve got your back.

The reason that simply populating instance variables in a controller constructor is that the constructor will be run before any middleware is triggered, and hence the session won’t be available to read from, the \Auth::user() won’t be populated etc etc. The way round this is to define a middleware closure in your constructor, which will run after all the other middleware, and ensure that the session etc is available to use here.

This is just a little example to make the current user and site directly available to controller actions. I also tend to put these in shared abstract base controllers for all the other controllers to inherit from, but that’s just me.

protected $active_user;
protected $active_site;

/*
 * Constructor
 */
public function __construct() {
    $this->middleware(function ($request, $next) {
        $this->active_user = \Auth::user();
        $this->active_site = session('site');

        return $next($request);
    });
}
13
Nov '16

by john

Easy one this, just copy and paste code if you’re using Bootstrap.
Whenever a tab is changed, this code will take its ID from the trigger and put into the window location hash (AKA address anchor or, if you’re really posh, the fragment identifier).

//Remember tab state
$('ul.nav-tabs > li > a').on('shown.bs.tab', function(e) {
    if($(e.target).attr('href')) {
        var id = $(e.target).attr('href').substr(1);
        window.location.hash = id;
    }
});

The second section of code ensures that whenever the page is reloaded, or the page is linked to using a valid tab ID, the identified tab will be selected automatically.

//Switch tab state
if (window.location.hash) {
    $("a[href='" + window.location.hash + "']").tab('show');
}

Done

01
Sep '16

by john

Quick one here, but one that it took me a while to discover so I thought I’d share it. If you want to have your script change the current address without actually navigating anywhere, you can push to the browser history using the following command:

History.pushState({}, title, url);

The latest item in the history will always display as the current URL in the address bar.

I find this particularly useful when I have a lot of dynamic elements on a page, particularly tabs and accordions.
For example if I wish to retain an opened tab across a page refresh I can use this trick to change the window location hash then use a simple script like this to switch to that tab on page load:

if (window.location.hash) {
    $("a[href='" + window.location.hash + "']").tab('show');
}