By Robert | | in Technology

For the uninitiated among you, Laravel, the brain-child of Taylor Otwell, is an open source PHP framework, architected to follow the MVC design pattern.

Since joining Klyp twelve months ago, I've been working exclusively with this awesome framework to deliver a number of different client projects. But for all it can do, Laravel still doesn't do everything (I've got to write some code, right?).

So, to help save my developer brethren some valuable time I've chosen five of the code snippets that I re-use across all of my Laravel apps. I'll endeavour to explain what each snippet does and how you'd use it; and perhaps it'll save you from having to reinvent the wheel.


1. Versioned Assets

First up, we'll start with a very common, yet basic problem - generating asset URLs.

Laravel already includes an asset() helper function for generating a URL (either HTTP or HTTPS) to an application asset. Amongst other things, I typically use this helper to link to compiled CSS or JavaScript assets in my application's layout template. But then I kept having an issue where old assets were being cached by the user's browser after I'd pushed an update to one of my applications.

This little helper function solves this problem by simply appending the file modification timestamp to the end of the asset URL. As soon as your asset is updated, the asset URL will change too, creating a newly unique file path, prompting your browser to download the latest version of the file. Too easy, right?



/**
 * Generate a URL to an application asset with a versioned timestamp parameter.
 *
 * @param string $path
 * @param boolean $secure
 * @return string
 */
function versioned_asset($path, $secure = null)
{
 $timestamp = @filemtime(public_path($path)) ?: 0;
 return asset($path, $secure) . '?' . $timestamp;
}

This helper function should be used in exactly the same way as the existing asset helper.


<link rel="stylesheet" href="{{ versioned_asset('/build/css/app.css') }}">
<link rel="stylesheet" href="https://klyp.co/build/css/app.css?1374937200">


2. Attributable Trait

Next up, we're working with models.

I sometimes want to grab a subset of attributes from one of my models or conversely, all but a subset of the attributes. Sounds pretty easy, right? And you'd be right!

You may already be aware of the array_only() and array_except() helper functions, and have used them before. I wrote the Attributable trait to exploit these helper functions within my model classes to return mutated subsets of the model's attributes.

The methods as they're written now will return you the mutated attribute values. If you instead want the raw attribute values, simply replace calls to $this->toArray() with $this->getAttributes().


namespace App\Models\Traits;
trait Attributable
{
 /**
 * Get a subset of the specified attributes from the model.
 *
 * @param array|string $attributes
 * @return array
 */
 public function only($attributes)
 {
 $attributes = is_string($attributes) ? func_get_args() : $attributes;
 return array_only($this->toArray(), $attributes);
 }
 /**
 * Get a subset of attributes from the model except those specified.
 *
 * @param array|string $attributes
 * @return array
 */
 public function except($attributes)
 {
 $attributes = is_string($attributes) ? func_get_args() : $attributes;
 return array_except($this->toArray(), $attributes);
 }
}

To use this trait, you simply need to include it inside your model or better yet, your parent model class.


namespace App\Models;
use Illuminate\Database\Eloquent\Model as BaseModel;
abstract class Model extends BaseModel
{
 use \App\Models\Traits\Attributable;
}
// Find user and get personal information
$user = \App\Models\User::find(1);
$data = $user->only('first_name', 'last_name', 'email');
dd($data);
// ['first_name' => 'Rob', 'last_name' => 'Simpkins', 'email' => 'engage@klyp.co'];


3. Responsible Trait

Sticking with models, I often find that in addition to tracking when a model is created, updated or deleted, it's also useful to know who performed each of those operations.

I wrote the Responsible trait to record who performed each of these operations using Eloquent's inbuilt saving and deleting events. The current user ID is ascertained from the Auth facade and set on the model by the appropriate event listener.


namespace App\Models\Traits;
use Auth;
trait Responsible
{
 /**
 * Boot trait.
 *
 * @return void
 */
 public static function bootResponsible()
 {
 // Prepare authenticated user id
 $userId = Auth::id();
 // Register saving event listener
 static::saving(function ($model) use ($userId) {
 $model->created_by = $model->created_by ?: $userId;
 $model->updated_by = $userId;
 }, 10);
 // Register deleting event listener
 static::deleting(function ($model) use ($userId) {
 $model->deleted_by = $userId;
 $model->save();
 }, 10);
 }
}

Simply include the trait inside your model or a parent model class and add created_by, updated_by and deleted_by integer type columns to your model's database schema.


namespace App\Models;
use Illuminate\Database\Eloquent\Model as BaseModel;
abstract class Model extends BaseModel
{
 use \App\Models\Traits\Responsible;
}
// Create new user
$user = \App\Models\User::create([
 'first_name' => 'Rob',
 'last_name' => 'Simpkins',
 'email' => 'engage@klyp.co',
]);
dd($user->created_by, $user->updated_by);
// '2016-06-20 12:34:56', '2016-06-20 12:34:56'


4. Validatable Trait

Sticking with models still further, we're going to tackle every developer's favourite data manipulation task - validation!

I don't know about you, but I like to keep my model validation rules with their respective models. And that's exactly what this Validatable trait does.

The trait allows you to define validation rules and optional messages using the rules() and messages() method, which can be overridden in your model. Using Eloquent's inbuilt saving event, an event listener runs your model data through a Validator instance and against your specified validation rules when you call save(), create() or update().

If the data is valid, the model saves the data. If the data is invalid however, a ModelValidationException is thrown. You can catch this exception and access the validator error messages either from the exception or the model itself.


namespace App\Models\Traits;
use Log;
use Validator;
use App\Models\Exceptions\ModelValidationException;
trait Validatable
{
 /**
 * @var \Illuminate\Validation\Validator Validation instance
 */
 protected $validator;
 /**
 * Boot trait.
 *
 * @throws \App\Models\Exceptions\ModelValidationException
 * @return void
 */
 public static function bootValidatable()
 {
 // Check if model attributes are valid when saving
 static::saving(function ($model) {
 // Model failed validation
 if (! $model->validate()) {
 // Prepare error
 $error = sprintf('Unable to save %s model. One or more attribute values are invalid.', get_called_class());
 // Log error and validation errors when in debug mode
 if (env('APP_DEBUG')) {
 Log::error($error, $model->toArray());
 }
 throw new ModelValidationException($model->validator(), $error);
 }
 });
 }
 /**
 * Validate data or model attributes against internal validation rules.
 * The internal validation rules and error messages may be overridden.
 *
 * @param array $data
 * @param array $rules
 * @param array $messages
 * @return boolean
 */
 public function validate(array $data = [], array $rules = [], array $messages = [])
 {
 // Prepare data and validation rules
 $data = $data ?: $this->attributes;
 $rules = $rules ?: $this->rules();
 $messages = $messages ?: $this->messages();
 // Prepare validator and set errors
 $this->validator = Validator::make($data, $rules, $messages);
 return $this->validator->passes();
 }
 /**
 * Get model validation rules.
 *
 * @return array
 */
 public function rules()
 {
 return [];
 }
 /**
 * Get model validation messages.
 *
 * @return array
 */
 public function messages()
 {
 return [];
 }
 /**
 * Get model validator.
 *
 * @return \Illuminate\Validation\Validator
 */
 public function validator()
 {
 return $this->validator;
 }
 /**
 * Get model validation errors.
 *
 * @return array
 */
 public function errors()
 {
 return $this->validator ? $this->validator->errors()->toArray() : [];
 }
 /**
 * Get model validation errors.
 *
 * @return array
 */
 public function attributesToArray()
 {
 // Call parent
 $attributes = parent::attributesToArray();
 // Append validation errors
 if ($this->errors()) {
 $attributes['_errors'] = $this->errors();
 }
 return $attributes;
 }
}

namespace App\Models\Exceptions;
use Exception;
use Illuminate\Validation\Validator;
class ModelValidationException extends Exception
{
 /**
 * @var \Illuminate\Validation\Validator Validator instance
 */
 protected $validator;
 /**
 * Create a new exception instance.
 *
 * @param \Illuminate\Validation\Validator $validator
 * @param string $message
 * @return void
 */
 public function __construct(Validator $validator, $message)
 {
 parent::__construct($message);
 $this->validator = $validator;
 }
 /**
 * Get model validator.
 *
 * @return \Illuminate\Validation\Validator
 */
 public function validator()
 {
 return $this->validator;
 }
 /**
 * Get model validation errors.
 *
 * @return array
 */
 public function errors()
 {
 return $this->validator->errors();
 }
}

Simply include the trait inside your model or a parent model class and define your validation rules and optional messages. Then wrap your model save operations in a try/catch block.


namespace App\Models;
use Illuminate\Database\Eloquent\Model as BaseModel;
abstract class Model extends BaseModel
{
 use \App\Models\Traits\Validatable;
}

namespace App\Models;
class Comment extends Model
{
 /**
 * @var string The database table used by the model.
 */
 protected $table = 'comments';
 /**
 * @var array The attributes that are mass assignable.
 */
 protected $fillable = [
 'comment',
 ];
 /**
 * Get model validation rules.
 *
 * @return array
 */
 public function rules()
 {
 return [
 'user_id' => ['required', 'exists:users,id'],
 'comment' => ['required', 'max:2000'],
 'created_by' => ['sometimes', 'numeric', 'exists:users,id'],
 'updated_by' => ['sometimes', 'numeric', 'exists:users,id'],
 'deleted_by' => ['sometimes', 'numeric', 'exists:users,id'],
 ];
 }
}

namespace App\Http\Controllers;
use App\Models\Exceptions\ModelValidationException;
use App\Models\Comment;
use Illuminate\Http\Request;
class CommentController extends Controller
{
 /**
 * Handle create comment request.
 *
 * @param \Illuminate\Http\Request $request
 * @param integer $recordId
 * @return \Illuminate\Http\Response
 */
 public function postCreate(Request $request, $recordId)
 {
 try {
 // Create comment and associate with current user
 $comment = new Comment($request->all());
 $comment->user()->associate($this->_user);
 // Save comment
 $status = $comment->save();
 }
 catch (ModelValidationException $exception) {
 // Prepare status and validation errors
 $status = false;
 $errors = $comment->errors();
 return response()->json(compact('status', 'errors'), 400);
 }
 // Prepare model and message for response
 $model = $comment;
 $message = trans('lang.comment.create.' . intval($status));
 return response()->json(compact('status', 'message', 'model'));
 }


5. Authorizable Trait

Finally, we're going to look at authorization, and how we can streamline these checks in our controllers.

If you haven't already explored the Laravel authorization documentation, I would highly recommend it. If you're not already using it, there's a huge reservoir of potential going untapped. In a nutshell, through the use of policies, Laravel allows you to explicitly state who can do what. A simple example would be, who can create, update or delete a comment.

I wrote the Authorizable trait to try and streamline these checks for the currently authorized user when performing actions on a single model, or a collection of models.

Essentially these methods just grab the current user, and using the Laravel Authorizable trait, check whether the user is permitted to perform the specified action on the specified model(s). If all is well, the methods will return true. If not, an AuthorizationException is thrown.


namespace App\Http\Controllers\Traits;
use Auth;
use App\Collections\Collection;
use App\Models\Model;
use Illuminate\Auth\Access\AuthorizationException;
trait Authorizable
{
 /**
 * Check whether current user is authorised to perform given ability on the model.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 *
 * @param string $ability
 * @param \App\Models\Model $model
 * @return boolean
 */
 protected function authorizeModel($ability, Model $model)
 {
 // Prepare authenticated user
 $user = Auth::user();
 // Check whether current user is authorised to perform given ability on model
 if ($user && $user->can($ability, $model)) {
 return true;
 }
 throw new AuthorizationException('You are not authorised to access the requested resource.');
 }
 /**
 * Check whether current user is authorised to perform given ability on all models in collection.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 *
 * @param string $ability
 * @param \App\Collections\Collection $collection
 * @return boolean
 */
 protected function authorizeCollection($ability, Collection $collection)
 {
 // Check whether current user is authorised to perform given ability on each model
 $collection->each(function ($model) use ($ability) {
 $this->authorizeModel($ability, $model);
 });
 return true;
 }
}

Include the trait inside your controller or a parent controller class, and call the authorization methods as required.

I tend to use a global exception handler to catch an AuthorizationException when thrown, but you could catch this in your controller too, by adding another catch block.


namespace App\Policies;
use App\Models\Comment;
use App\Models\User;
class CommentPolicy
{
 use \Illuminate\Auth\Access\HandlesAuthorization;
 /**
 * Check whether the comment can be read by the current user.
 *
 * @param \App\Models\User $current
 * @param \App\Models\Comment $comment
 * @return boolean
 */
 public function read(User $current, Comment $comment)
 {
 return ($current->id === $comment->user_id || $current->is_admin);
 }
 /**
 * Check whether the comment can be updated by the current user.
 *
 * @param \App\Models\User $current
 * @param \App\Models\Comment $comment
 * @return boolean
 */
 public function update(User $current, Comment $comment)
 {
 return ($current->id === $comment->user_id || $current->is_admin);
 }
 /**
 * Check whether the comment can be deleted by the current user.
 *
 * @param \App\Models\User $current
 * @param \App\Models\Comment $comment
 * @return boolean
 */
 public function delete(User $current, Comment $comment)
 {
 return ($current->id === $comment->user_id || $current->is_admin);
 }
}

namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
 use \App\Http\Controllers\Traits\Authorizable;
}

namespace App\Http\Controllers;
use App\Models\Exceptions\ModelValidationException;
use App\Models\Comment;
use Illuminate\Http\Request;
class CommentController extends Controller
{
 /**
 * Handle update comment request.
 *
 * @param \Illuminate\Http\Request $request
 * @param integer $commentId
 * @return \Illuminate\Http\Response
 */
 public function putUpdate(Request $request, $commentId)
 {
 try {
 // Find comment and check user authorization
 $comment = Comment::findOrFail($commentId);
 $this->authorizeModel('update', $comment);
 // Update comment
 $status = $comment->update($request->all());
 } 
 catch (ModelValidationException $exception) {
 // Prepare status and validation errors
 $status = false;
 $errors = $comment->errors();
 return response()->json(compact('status', 'errors'), 400);
 }
 // Prepare model and message for response
 $model = $comment;
 $message = trans('lang.comment.update.' . intval($status));
 return response()->json(compact('status', 'message', 'model'));
 }
}

If you have any go-to code snippets that you use across your projects, or you have any questions or comments about the snippets covered in this post, please do give me a shout. I'm always keen to learn and to help others do the same :) hit me up at engage@klyp.co!