John Main Logo

John Main

Code. Design. Hosting. Maintenance.

25
Jul '18

I know I know. Rely too much on traits and you’re asking for trouble. But when used correctly, for self-contained bolt-on functionality they really can be fantastic and save loads of time.

There only condition for this logic is that any record with which you are using this must have a ‘logo’ field. Yeah I know I could have done a more advanced version with a separate logo table and a polymorphic relationship, but it’s not like a standard system will have 2 dozen records that all require logos against them so I’ve kept it simple.

I’ve used the word ‘logo’ here, but it’s a generic image storing trait and could be used for anything. You’ll also notice an optional config option of ‘app.directory.logo’, which, if provided, will be used as a relative path below the application’s public folder, otherwise ‘/logos’ will be used.

This assumes that you have an image referenced as ‘logo_file’ in your request and have triggered a save on the parent record.

namespace App\Trt;
trait Logo
{
/**
* Custom boot method
*/
public static function bootLogo()
{
/*
* Trigger on save
*/
static::saved(function ($record) {
try {
if(request()->hasFile('logo_file') && request()->file('logo_file')->isValid()) {
$logo_file = request()->file('logo_file');
$logo_folder = public_path(config('app.directory.logo', '/logos'));
if(is_dir($logo_folder) && is_writable($logo_folder)) {
//Prepending the record ID to ensure uniqueness
//Could also prepend parent record type / sanitised model name if it's to be used for multiple records
$file_name = $record->id . '_' . $logo_file->getClientOriginalName();
$record->clearLogo(false);
if($logo_file->move($logo_folder, $file_name)) {
$record->logo = $file_name;
//Direct update here so we don't trigger any callbacks
\DB::table($record->getTable())->where('id', $record->id)->update(['logo' => $file_name]);
}
}
else {
\Log::error('Logo folder is not writable ' . $logo_folder);
}
}
elseif(request()->clear_logo) {
$record->clearLogo();
}
}
catch(\Exception $e) {
}
});
/*
* Trigger on delete
*/
static::deleting(function ($record) {
$record->clearLogo(false);
});
}
/**
* Check for logo
* @return boolean
*/
public function hasLogo() {
return ($this->logo && is_file($this->logoPath()));
}
/**
* Logo URL
* @return string|false
*/
public function logoURL() {
return $this->logo ? url(config('app.directory.logo', '/logos') . '/' . $this->logo) : false;
}
/**
* Logo Path
* @return string|false
*/
public function logoPath() {
return $this->logo ? public_path(config('app.directory.logo', '/logos') . '/' . $this->logo) : false;
}
/**
* Clear Logo
* @param boolean [$save=true] Save
* @return boolean
*/
public function clearLogo($save = true) {
if($this->logo) {
if($this->hasLogo())
unlink($this->logoPath());
$this->logo = null;
if($save)
$this->save();
return true;
}
return false;
}
}

And that’s it!

01
Jun '18

I’m sure we’ve all been there. You create a nice data structure for a utility and the client says “Ok, but I also need to be able to add a note to it”. You add a note field and then they say “Ok, but I also need to add a note over here”. You add another note field over there and then they say “But I need to be able to add new notes and keep the old ones”. You scream internally, wish they’d told you that in the first place, and start building a more flexible solution.

Well here is my take on it. It’s a separate notes table with a polymorphic association to any and all other records. Their models simply need to use this ‘Notable’ trait. The notes will be created automatically with any save action on the parent model, and have a simple functions to retrieve all notes against a parent, or clear them. Simples.

This assumes that you have either a single ‘note’ parameter in your request, or a ‘notes’ array, and have triggered a save on the parent record.

Table structure:

CREATE TABLE `notes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL,
`parent_type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`content` text COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);

Trait:

namespace App\Trt;
/**
* Notable trait
*/
trait Notable
{
/**
* Custom boot method
*/
public static function bootNotable()
{
/*
* Trigger on create
*/
static::saved(function ($record) {
//We check the class name here as otherwise it will get in an infinite loop of trying to add notes below notes
if(class_basename(get_class($record)) != 'Note' && (request()->note || request()->notes)) {
if(request()->note) {
$record->notes()->create(['content' => request()->note]);
}
if(request()->notes && is_array(request()->notes)) {
foreach(request()->notes as $note) {
$record->notes()->create(['content' => $note]);
}
}
}
});
/*
* Trigger on delete
*/
static::deleting(function ($record) {
$record->clearNotes();
});
}
/**
* Note relationship
*/
public function notes()
{
return $this->morphMany('\App\Model\Note', 'parent');
}
/**
* Clear all notes
*/
public function clearNotes() {
$this->notes()->delete();
}
}

I tend to include the ID of the current user in the note too, so that I can display the user name against the note text. Simple case of adding the column to the table and the fillable array, adding the relationship function, and replacing:

$record->notes()->create(['content' => request()->note]);

with:

$record->notes()->create(['content' => request()->note, 'user_id' => \Auth::user()->id]);
15
Apr '18

One of my projects features a survey / audit system for which I was tasked to provide an offline facility by allowing the download of fillable PDF forms, and then the subsequent re-upload and processing of them.

For the form generation and download I built a wrapper around TCPDF, which I may blog about one day, but it was an extremely involved process and today is not that day!

The import and processing routine I implemented using TCPDF (specifically this PDFTK library aliased as ‘PDFTK’ in my app configuration), and ended up being nicely self-contained:

/*
* Read fields from PDF file
* @param string $path Path to PDF
* @return array
*/
public static function read($path) {
$pdf = new \PDFRead($path, ['_command' => '/usr/bin/pdftk']);
$ignore_fields = ['Validate', 'Submit', 'Reset', 'Print'];
$ignore_values = ['Select...', 'Off'];
if($data = $pdf->getDataFields()) {
$values = collect($data->__toArray())->mapWithKeys(function($data_field) use ($ignore_fields, $ignore_values) {
if(in_array($data_field['FieldName'], $ignore_fields))
return [];
if(isset($data_field['FieldValue'])) {
$value = $data_field['FieldValue'];
if($data_field['FieldType'] != 'Text' && in_array($data_field['FieldValue'], $ignore_values))
$value = '';
}
else
$value = '';
return [$data_field['FieldName'] => $value];
});
$parsed_values = [];
foreach($values as $key => $value) {
if(preg_match('/(.*)\[([0-9\_]+)\]/', $key, $matches)) {
if(!isset($parsed_values[$matches[1]]))
$parsed_values[$matches[1]] = [];
$parsed_values[$matches[1]][$matches[2]] = $value;
}
else
$parsed_values[$key] = $value;
}
return $parsed_values;
}
}
Pass any filled PDF form through that function and it will return an array in the same format as you would get from any normal form submission.
12
Mar '18

Bit of a follow-on here from my post on record versioning. Versioning a record is all well and good, but often you would like to version the reccord’s relations too in order to make the record meaningful, provide context, provide metadata etc.

In the code below, the archive function on the parent model is triggered to create the archive (versioned) record. Note that I have added the with(‘notes’) call, which will add all related records from the ‘notes’ relation into the resulting array. As such this relation has been serialiasied with the parent record.

When the archive (versioned) record is deserialised we will then find that the ‘notes’ are attached to the copy of the parent model, but as simple arrays rather than objects. The custom ‘hydrateArchive’ function simply takes these arrays and turns them back into real models.

Parent Model

/**
* Archive
* @return \App\Model\Archive
*/
public function archive() {
return $this->archives()->create([
'data' => serialize($this->with('notes')->find($this->id)->toArray())
]);
}
/**
* Custom archive hydrate
*
* @return \App\Model\Base
*/
public function hydrateArchive() {
if(!$this->notes)
return $this;
$this->notes = collect($this->notes)->map(function ($data) {
return new \App\Model\Note($data);
});
return $this;
}

Archive Record

/**
* Deserialise
* @return \App\Model\Base
*/
public function deserialise() {
$deserialised = with(new $this->parent_type)->newFromBuilder(unserialize($this->data));
return $deserialised->hydrateArchive();
}
05
Feb '18

So we all know about Laravel’s built-in ‘creating’, ‘created’, ‘updating’, ‘updated’ etc model events. Seriously useful events for all manner of purposes.

Recently I found myself needing to add a couple more; one to trigger when a model was loaded (for logging purposes), and one before a model was updated (so that I could archive a copy of it before the data was changed).

I came up with the following code and inserted it into an abstract base model class, which all the other models extend.

protected $observables = ['loaded', 'beforeUpdate'];
/**
* Register a loaded model event with the dispatcher.
*
* @param  \Closure|string  $callback
* @return void
*/
public static function loaded($callback)
{
static::registerModelEvent('loaded', $callback);
}
/**
* Register a before update model event with the dispatcher.
*
* @param  \Closure|string  $callback
* @return void
*/
public static function beforeUpdate($callback)
{
static::registerModelEvent('beforeUpdate', $callback);
}
/**
* Create a new model instance.
*
* @param  array  $attributes
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function newFromBuilder($attributes = array(), $connection = null)
{
$instance = parent::newFromBuilder($attributes);
$instance->fireModelEvent('loaded', false);
return $instance;
}
/**
* Trigger before update
*
* @param  array  $attributes
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function update(array $attributes = [], array $options = [])
{
$this->fireModelEvent('beforeUpdate', false);
return parent::update($attributes);
}
02
Jan '18

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

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.

14
Aug '17

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

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);
}
}
02
Mar '17

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