Laravel improving readability with blade directives

Laravel's blade templating system comes with ability to define custom directives. These directives can be very handly to improve readability of the view files. Let's do a quick recap of directives :

Blade directives :

  • Generic directive :

These directive take an argument and simply return the actions done in callback.

Blade::directive('caps', function ($expression) {
    return "<?php echo ucwords($expression); ?>";
});

You can use above in blade templates directly :

<div>{{ @cap('john smith') }}</div>

It is little tricky to handle the $expresssion in the callback, as if you pass an array or multiple string arguments, those are passed as single string expressions. You can see that shortly in action in usage ideas section.

  • Conditional directive :

These directives take an argument and use it inside callback to return value. Now the callback's return value is then casted as boolean to consider if statements inside it will be showed or now. It is just like @if ... @endif directive, but more intuitive.

Blade::if('isadmin', function ($user) {
    return $user->role == 'admin';
});

You can use above in blade templates directly :

@isadmin($user)
    <div>
        <a href="{{ route('users.all') }}">See all users</a>
    </div>
@endisadmin

Above is similar to this if done in usual without the conditional directive :

@if($user->role == 'admin')
    <div>
        <a href="{{ route('users.all') }}">See all users</a>
    </div>
@endif

Some usage ideas :

These directive give a great advantage to simplify your blade views and make them more readable. See few examples below :

  1. Conditional role checks :
Blade::if('role', function ($role) {

    // Not logged in
    if(!auth()->check()){
        return false;
    }

    // Multiple roles passed in argument
    if(is_array($role)){
        return in_array(auth()->user()->role, $role);
    }

    // Single role passed in argument
    return auth()->user()->role == $role;
});

This makes the role conditional checks in blade much easier :

@role('admin')
   <!-- something only an admin can see-->
@elserole(['customer', 'seller'])
   <!-- something only a customer or a seller an see-->
@endrole

  1. Rendering form errors :
Blade::directive('validation', function ($expression) {

    // $expression is a string expression and 
    // hence will be a string maintaing start and end quotes
    // Trimming the quotes
    $expression = str_replace(['(', ')'], '', $expression);
    $fieldName  = trim(trim($expression, '"'), '\'');

    $errors = \Session::get('errors', new \Illuminate\Support\MessageBag);

    if($errors->has($fieldName)){
        return '<span role="alert"><strong>' . $errors->first($fieldName) . '</strong></span>';
    }

    return;
});

This makes the the validation errors from server end much easier in forms :

<form role="form" method="POST" action="{{ route('some.url') }}">
    @csrf
    <div class="form-group">
        <label>First Name *</label>
        <input class="form-control" placeholder="Enter your first name" name="first_name">
        @validation("first_name")
    </div>
    <div class="form-group">
        <label>Last Name *</label>
        <input class="form-control" placeholder="Enter your last name" name="last_name">
        @validation("last_name")
    </div>
    <button type="submit" class="btn btn-primary">Submit Ticket</button>
</form>

  1. Dumping values in views fo debugging :
Blade::directive('dd', function ($expression) {

    // $expression is a string expression and 
    // It can not be directly passed in dd($expression)
    // We can use with() helper which basically encapsulate the syntax
    // to render it properly in blade files
    return "<?php dd(with({$expression})); ?>";
});

Please note that above will not work for multiple arguments, can dd one argumenet only. Above can be then used :

@dd([1,2,3,4]);

  1. Assigning values to variables in a view :

Now this might be a very less possible requirement but if you want to assign a variable in a view, generally you do something like :

@php 
    $name = "John Doe";
@endphp

However, we can do this using blade directive :

Blade::directive('assign', function ($expression) {

    // The $expression passed in closure is a string in format '($name), value'
    // Hence we first remove the paranthesis and explode by comma
    list($name, $value) = explode(', ', str_replace(['(', ')'], '', $expression));
    return "<?php {$name} = {$value}; ?>";
});

And then simply use it as (Below example is very very simple but you can use as per your preference) :

@assign($name, 'John Doe')

<div> {{ $name }} </div>

  1. Frequent HTML elements :

If you are using 3rd party HTML libraries like glyphicon or font-awesome, you need to use those in lot of places. You can use blade directive like :

Blade::directive('icon', function ($expression) {

    // The $expression passed in closure is a string in format '($name), value'
    // Hence we first remove the paranthesis and explode by comma
    $classes = str_replace(['(', ')'], '', $expression);
    $classes = str_replace(['"', '\''], '', $expression);
    $classes = str_replace(',', ' ', $classes);

    return '<i class="fa ' . $classes . '" aria-hidden="false"></i>';
});

Then it is as easy as it can get to use :

<label> @icon('fa-user', 'fa-2x') User Name </label>

You can also extend this idea for input, select etc. I think once you set such directives in a project, your blade views become readble. Plus less code in a bladel file is always better.

.....

Read full article

Tags: laravel, php, blade, directives

Laravel tap vs tap vs pipe

If you are wondering having tap 2 times in the article title is a mistake, apparently it is not. Laravel has large set of helper functions which are generic and helper functions specific to collections. Tap and pipe are very popular amongst laravel enthusiasts. Let's dive into each of of those to see what really makes them special and what differentiates them :

The tap() function :

The reason of having tap 2 times in the article header is that, there are 2 implementations of tap helper function in laravel. One is in generic context and one specific to handling collections.

  • The generic tap() function :

The name tap suggests that it basically does set of actions using the value and then returns the value as if nothing happened. In other words, the tap function accepts two arguments $value and a Closure. The $value will be passed to the closure function. You can perform your actions in the closure where the return value of the Closure is irrelevant. At the end tap returns the same $value at the end.

For example :

$user = tap($user, function ($user) {

    $user->notify(new SendWelcomeEmail());

});

In the above snippet, we are passing $user instance to tap as first argument which is then cascaded as a parameter to closure. The closure will execute and basically send a new welcome email. Then control will come back to tap which will return the $user instance back.

Even you can just eliminate the closure and call function on it directly like below.

$user = tap($user)->notify(new SendWelcomeEmail());

The above will still return the user instance. The beauty of it is, you can chain the tap calls. Let's say you need to update the user role, then send the welcome email and then assign user some permissions. You can chain it all like :

$user = tap($user)->update([
    'role' => 'Customer' 
])
->notify(new SendWelcomeEmail())
->assignPermissions('CustomerRole');

The use of this method can be handy when certain methods do not return the object as you want. A great example is the update method itself on the eloquent model. It returns integer when used otherwise. But if used as above with tap function, it returns the object it performed the update.

  • The collection tap() function :

The tap function for collection can be used in collection function chaining. I think of it similar to javascript debug breakpoints. You pin it to print something at a point of time in your javascript and then move ahead after removing the breakpoint.

For example consider following example :


$customers = collect([ ['name' => 'mihir', 'is_active' => true, 'category' => 'gold'], ['name' => 'george', 'is_active' => true, 'category' => 'silver'], ['name' => 'nathen', 'is_active' => false, 'category' => 'platinum'], ['name' => 'steve', 'is_active' => true, 'category' => 'gold'], ['name' => 'shawn', 'is_active' => false, 'category' => 'gold'] ]); $activeGoldCustomers = collect($customers) ->where('is_active', true) ->tap(function ($activeCustomers) { $this->sendActiveCustomerEmail($activeCustomers); }) ->where('category', 'gold') ->tap(function ($goldCustomers) { $this->sendOfferCode('100%Discount'); });

In above example, we are just returning the customers who are currently active and fall under category gold. However, using the tap function we can perform certain actions on the go as we filter those. When we filtered for is_active = true, we sent activation email. Then when we added another filter for category = gold, we sent offer code just for active customer from gold category.

You can also find this handy in dumping data for debugging during the developement cycle.

The fundamental concept of tap() is just use the value and perform actions but NEVER CHANGE the value. If you think then what can change the value? pipe() is the answer.

The pipe() function :

Pipe function is just applicable for collections. For example consider following example :


$customers = collect([ ['name' => 'mihir', 'is_active' => true, 'category' => 'gold'], ['name' => 'george', 'is_active' => true, 'category' => 'silver'], ['name' => 'nathen', 'is_active' => false, 'category' => 'platinum'], ['name' => 'steve', 'is_active' => true, 'category' => 'gold'], ['name' => 'shawn', 'is_active' => false, 'category' => 'gold'] ]); $counts = collect($customers) ->pipe(function($customers) { return [ 'active_count' => $customers->where('is_active', true)->count(), 'gold_count' => $customers->where('category', 'gold')->count(), 'silver_count' => $customers->where('category', 'silver')->count(), 'platinum_count' => $customers->where('category', 'platinum')->count() ]; });

Above will basicaly return [3, 3, 1, 1], but it is very clean and less code is always better.

Please note that the pipe() helper method is different than the laravel pipelines. However, if you read about pipelines, its is kind of similar conceptually to generic tap function where you can pass an object through multiple classes to perform set of task and then return the result value once all the tasks have been finished. A handy article for the same is here if you want a quick read.

Even if these methods are very handy, sometimes if you chain it to much it loses the element fo readability. I will suggest it is preferable to follow what the domain logic wants and group the similar processes together to tap or pipe.

.....

Read full article

Tags: laravel, php, helpers, collections

Laravel Module Pattern

Laravel has a folder structure which ties similar entities of MVC together e.g. controllers in one folder, views into another. There are few set-backs of this approach :

  • If your project scales exponentially having lot of module, each of these directories scale as well. It becomes difficult to maintain.
  • If you want to remove the module entirely, there are files scattered in different folders to consider. This sometimes lead to unused files still present in your project repository.
  • Re-using a module into different project is a hassle (unless you have it installed coumpled as a plugin via composer)
  • While using code editors, as the module files are in different folders. We might need to expand the folders in project sidebar to view them at a glance. The scattered folder structure makes it difficult to for a quick view. (Forgive me for being picky here ;))

Implementation :

Before you dive in to further sections, if you are familier with basic concepts of larave, most part of the code will be very familier to you. Tt is really easier that you might think.

  • Creating the module :

Let's create a new folder called Modules in the project root. (You might want to create it inside app folder, I prefer it this way.) To use the \Modules namespace, we need to autoload it from composer.json in the psr-4 section.

 "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Modules\\": "Modules/",
        }
    },

Let's dump the updated autoloads by doing following from terminal shell :

composer dump-autoload

Now we are good to start with the first module. Let's consider a ticket module where user can submit a ticket from frontend and we store it into the database. We will not focus much on the actual implementation of the ticketing ystem. We will emphasize on the structure of module.

  • Create a new directory Ticket.

Create a new directory Ticket inside Modules folder. I like to keep module names singular (Ticket instead of Tickets).

  • Creating a service provider class :

On a broader level, frameworks like laravel have special entry point wrappers which can find, register and instantiate the core functionalities. In case of Laravel it's the ServiceProvider class.

Create a new file TicketServiceProvider.php inside Modules/Ticket.

<?php

namespace Modules\Ticket;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {}

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}

To make sure laravel considers this while booting up, we need to register it. Add this inside config/app.php's providers array :

App\Modules\Ticket\TicketServiceProvider::class,
  • Creating migrations :

We need to create a table to store new ticket data. Create a folder Migrations inside Modules/Ticket. Now we can create a new migration from terminal shell :

php artisan make:migration create_tickets_table --path=Modules/Ticket/Migrations

It will create a new migration class inside Modules/Ticket/Migrations path. Let's add the table script :

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTicketsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tickets', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title')->nullable();
            $table->text('body')->nullable();
            $table->boolean('is_closed')->default(0);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tickets');
    }
}

However, you must have noticed that this is not coming from Laravel's default database/migrations folder. We need to inform laravel to load this file as well for running migration. It can be done from service provider by adding following in boot method.

<?php

namespace Modules\Ticket;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {

        // Load ticket module migrations from 
        // `Modules/Ticket/Migrations` folder path
        $this->loadMigrationsFrom(__DIR__.'/Migrations');
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}

Now to run the migration from terminal shell :

php artisan migrate
  • Alternatively:

If you are thinking migrations are done very rarely, so why should we register it in service provider for a registration overhead? Laravel has got you covered.

You can skip the step of doing loadMigrationsFrom() in service provider class. Instead you can specify the path while running migrations :

php artisan migrate --path=Modules/Ticket/Migrations

If your project has CICD deployments, mostly it will just have the migrate command without the path option. The first method is preferable to keep it simple for deployments.

  • Creating Model :

Create a file Ticket.php inside Modules/Ticket. (You may create a Models folder inside Modules/Ticket and create model class inside it as per your preference.)

<?php

namespace App\Modules\Ticket;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Ticket extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'tickets';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title',
        'body',
        'is_closed'
    ];

}
  • Creating Controller :

Create a new directory inside Modules/Ticket called Http. Let's create TicketController.php inside it :

<?php

namespace Modules\Ticket\Http;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\Ticket\Ticket;

class TicketController extends Controller
{
    /**
     * Display a form for the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('tickets.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  Request  $request
     * @param  UserRepository $users
     * 
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'string|required|max:255',
            'body'  => 'string|required'
        ]);

        // Create new ticket
        $ticket = Ticket::create($request->only('title', 'body'));

        return redirect()->route('ticket.new')->withSuccess('Thank you for raising a ticket. We will get back to you at the earliest.');
    }
}
  • Creating Routes :

Create a new file called TicketRoutes.php inside Modules/Ticket.

<?php 

Route::group(['namespace' => 'Modules\Ticket', 'middleware' => ['web', 'auth']], function () {

    Route::get('ticket/new', [ 'as' => 'ticket.new', 'uses' => 'TicketController@create' ]);
    Route::post('ticket/store', [ 'as' => 'ticket.store', 'uses' => 'TicketController@store' ]);
});

We need to tell laravel to register these routes as those are coming from a custom folder. We can do that from service provider :

<?php

namespace Modules\Ticket;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {

        // Load ticket module migrations from 
        // `Modules/Ticket/Migrations` folder path
        $this->loadMigrationsFrom(__DIR__.'/Migrations');

        // Load routes
        $this->loadRoutesFrom(__DIR__.'/TickerRoutes.php');
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}
  • Creating Views :

Create a new directory inside Modules/Ticket called Views. This will contain al views for our module. This is again not coming from Laravel's default resources/views folder. We need to tell laravel to load these views. It can be done from service provider by adding following in boot method.

<?php

namespace Modules\Ticket;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {

        // Load ticket module migrations from 
        // `Modules/Ticket/Migrations`
        $this->loadMigrationsFrom(__DIR__.'/Migrations');

        // Load routes from `Modules/Ticket/TickerRoutes.php`
        $this->loadRoutesFrom(__DIR__.'/TickerRoutes.php');

        // Load views from `Modules/Ticket/Views`
        $this->loadViewsFrom(__DIR__.'/Views', 'ticket');
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}

Note : Sometimes you may see loadViewsFrom() called without the second argument which is the package name. The difference is if you want to render a view without a package name you do ticket.create (file path : Modules/Ticket/Views/ticket/create.blade.php. If you specify package name, you do ticket::create (file path : Modules/Ticket/Views/create.blade.php)

Let's create a simple view create.blade.php which basically has a form :

@extends('layouts.app')

@section('content')
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="col-md-4 col-md-offset-4">
                <form role="form" method="POST" action="{{ route('ticket.store') }}">
                    @csrf
                    <div class="form-group">
                        <label>Title *</label>
                        <input class="form-control" placeholder="Title for your ticket" id="title" name="title">
                        @if ($errors->has('title'))
                            <span class="invalid-feedback" role="alert">
                                <strong>{{ $errors->first('title') }}</strong>
                            </span>
                        @endif
                    </div>
                    <div class="form-group">
                        <label>Description *</label>
                        <textarea class="form-control" placeholder="How can we assist you?" id="body" name="body"></textarea>
                        @if ($errors->has('body'))
                            <span class="invalid-feedback" role="alert">
                                <strong>{{ $errors->first('body') }}</strong>
                            </span>
                        @endif
                    </div>
                    <button type="submit" class="btn btn-primary">Submit Ticket</button>
                </form>
            </div>
        </div>
    </div>
@endsection

  • Creating Policies :

If you would like to use policy to authorize the requests, create a file TicketPolicy.php inside Modules/Ticket.

<?php

namespace App\Modules;

use App\User;


class TicketPolicy
{

    /**
     * Determines user access
     *
     * @param User $user
     * @return bool
     */
    public function index($user)
    {
        return true;
    }

    /**
     * Determines user access
     *
     * @param User $user
     * @return bool
     */
    public function store($user)
    {
        return true;
    }
}

As you must have thought by now, we need to register the policy using the laravel Gate contract inside service provider :

<?php

namespace Modules\Ticket;

use Modules\Ticket\Ticket.php;
use Modules\Ticket\TicketPolicy.php;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(GateContract $gate) {

        // Load ticket module migrations from 
        // `Modules/Ticket/Migrations`
        $this->loadMigrationsFrom(__DIR__.'/Migrations');

        // Load routes from `Modules/Ticket/TickerRoutes.php`
        $this->loadRoutesFrom(__DIR__.'/TickerRoutes.php');

        // Load views from `Modules/Ticket/Views`
        $this->loadViewsFrom(__DIR__.'/Views', 'ticket');

        // Load the policies from `Modules/Ticket/TicketPolicy.php`
        $gate->policy(Ticket::class, TicketPolicy::class);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}
  • Creating config files :

We can have config files to access environmental variables specific for this module. For example in this ticket module let's say we need to configure an email to cc to. Create a file TicketConfig.php inside inside Modules/Ticket.

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Email to cc for submitted tickets
    |--------------------------------------------------------------------------
    |
    | Type : string
    |
    */

    'ticket_cc_email' => env('TICKET_CC_EMAIL', null),

];

Now, let's register this inside service provider.

<?php

namespace Modules\Ticket;

use Modules\Ticket\Ticket.php;
use Modules\Ticket\TicketPolicy.php;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class TicketServiceProvider extends BaseServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(GateContract $gate) {

        // Load ticket module migrations from 
        // `Modules/Ticket/Migrations`
        $this->loadMigrationsFrom(__DIR__.'/Migrations');

        // Load routes from `Modules/Ticket/TickerRoutes.php`
        $this->loadRoutesFrom(__DIR__.'/TickerRoutes.php');

        // Load views from `Modules/Ticket/Views`
        $this->loadViewsFrom(__DIR__.'/Views', 'ticket');

        // Load the policies from `Modules/Ticket/TicketPolicy.php`
        $gate->policy(Ticket::class, TicketPolicy::class);

        // Load the configurations from `Modules/Ticket/TicketConfig.php`
        $this->mergeConfigFrom(
            __DIR__.'/TicketConfig.php', 'ticket';
        );
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {}
}

We can then access the configurations as :

$ccEmail = config('ticket.ticket_cc_email');

Now you can see entire module is bundled to a single folder. Easy to manage. There is a downside of registrations inside service provider class which in general you do not worry about. However, this is structurally more intuitive and re-usable.

If you would like your module to have more extensive and detailed structure, you can simply use this ready composer plugin : nwidart/laravel-modules

Note : The structure and coding style are my personal opinions. There can be multiple ways to accomplish the same result. I feel just knowing the possibility that it can be done, opens new doors of imaginations based on personal comfort.

.....

Read full article

Tags: laravel, php, module, bundle

Laravel restricting unfiltered queries

Laravel eloquent is a very powerful ORM. I came across a situation for a project of mine long time ago. There was a table api_logs which basically logged all API requests and responses. Evidently the table grew exponentially. One of the developers created an analytics module in which he forgot to add a where clause. It queried millions of rows at a time on production. It was a silly mistake but server was overwhelmed and you can imagine the rest of story (I can tell you it is not that good).

I imagined if there was a way to just throw an error if any function queries all rows from a table like api_logs. Two advantages :

  • Error will be thrown during the development cycle where it's like an eye opener for the developer to make sure he adds filters.
  • If the develolper pushed the code to live by mistake, someone requesting the page will get the error page. I will be still having advantage with x% of customers getting an error rather than 100% of customer not using website when memory is exhausted. (If that query was in the header or footer on every page then basically it's over haha!)

Implementation :

Short and simple, nothing fancy. We will just intercept an eloquent model for every query and check if it has a where clause. The best place for that is boot method of the model. I wish laravel has retreiving observer just like created, updated etc, which intercepts the query before it hits the database connection. (I am guessing as observers basically observes the state of a model instance, the state prior to retreiving is ideally an inception step of preparing collection of models/model and not yet a prime state to observe. And hence no such observer yet available.)

We can however use \DB::listen() listener as follows :

<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class ApiLog extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'api_logs';


    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot(){

        parent::boot();

        \DB::listen(function($query) {
            //
        }); 
    }
}

Note that for Laravel version 5.1 and lower, the parameters of listen callback are $sql, $bindings, $time instead of just $query;

Now we have an listener, we need to get the query and check if it is selecting all rows. We will do it by regex on the raw sql.

<?php

    \DB::listen(function($query) {

        $table = (new self)->table;
        $pattern = "/from(\s)*?`?$table`?(\s)*$/m";

        if(preg_match($pattern, $query->sql)){
            throw new \Exception("Error : Running unfiltered queries on `$table` table is not allowed!");
        }
    }); 
}

Above preg match will catch all queries which do not have any clauses after the table syntax. Those can be select, update or delete queries.

Let's make this little more manageable by creating a trait and then using it in model. Create RestrictsUnfilteredQueries.php :

<?php 

namespace App\Model;

trait RestrictsUnfilteredQueries
{
    /**
     * Restrict unfiltered queries
     * 
     * @return void
     * @throws \Exception
     */
    public static function restrictUnfilteredQueries()
    {
        \DB::listen(function($query) {

            $table = (new self)->table;
            $pattern = "/from(\s)*?`?$table`?(\s)*$/m";

            if(preg_match($pattern, $query->sql)){
                throw new \Exception("Error : Running unfiltered queries on `$table` table is not allowed!");
            }
        }); 
    }
}

Using in the model's boot method :

<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;
use App\Model\RestrictsUnfilteredQueries;

class ApiLog extends Model
{
    use RestrictsUnfilteredQueries;

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'api_logs';


    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot(){

        parent::boot();

        self::restrictUnfilteredQueries(); 
    }
}

And done! Now your server RAM will thank you forever for this ;)

Note that this will not work when you query using DB facade like DB::select() or DB::table()->get().

You may alternatively cater this as well by having DB::listen() inside a service provider's boot method. It will listen to all sort of queries. However, it might be too much to listen all queries.

There might be better way of implementing the same, I did it this way considering the urgency of situation. It is still there in project since then and working perfectly.

.....

Read full article

Tags: laravel, php, security, slowloading

MySQL tips for shell client

How many of you use terminal when operating mysql instead of a GUI client like sequel pro or mysql workbench? There is nothing bad in using either of it as it depends on your choice of comfort.

I like to use terminal for it. Here are few tips which you might find helpful. These are not relelated to how you use mysql database in general, these are specific to the option files and mysql terminal client.

Before diving into it, just make sure you know where your mysql option file my.cnf is place. Server may have multiple my.cnf files. How mysql prioritizes them can be seen by typing :

mysql --help | grep my.cnf

Mysql cnf preference

This will give you a fair idea of how mysql is prioritizing these option files. I would suggest to have the option file .my.cnf for each user inside their home directory, which has specific options not revealing anything secretive for all mysql users. Again when I say each user, it is NOT mysql's user but the user you are logged into the shell terminal.

  1. Having a nice prompt is always great :

When you login to mysql client, you will see basic promt like below, nothing fancy. However, wouldn't it be great to know what user you have logged in as and which database you are currently in? You can add following in .my.cnf :

[mysql]

prompt=(\U):[\d]$\_

Mysql shell prompt

This will give you a handy promt to always keep an eye.

  1. Setting up default database & credentials :

It is not good to always write mysql -u user -p database.. when you can have that set to defaults you use everytime. You can add following in .my.cnf :

[client]

user=mysqluser
password=mysqlpassword

[mysql]

prompt=(\U):[\d]$\_
database=databasename

Now you can just type mysql and you are good to go

  1. Colorized output :

Usual output of mysql shell client is in white which becomes confusing when you have multiple columns, text of the row proceeds to next row. I feel this colour-mysql-console is very handy :

Url : https://github.com/nitso/colour-mysql-console

Once you follow the steps for installtion, you will see following :

Mysql colorized output

There are few options which you can use not in option file but directly while starting mysql shell client. These can be used as and when needed.

  1. Saving the query result in file :

Sometimes you might need to query something and then study those results later or maybe share it with a colleage etc. You can use the tee option :

mysql --tee="/filepath/output.txt"

MySQL client will acknowledge this by saying : Logging to file '/filepath/output.txt'. Now each query's result will be stored in this file which is very handy.

You can use notee command if you want to disable a particular query result to be pushed into the output file.

  1. Saving the result in HTML table format :

This can be one of the cases but if you in case need to show a query result somewhere in pure table format, you can just use the html option :

mysql --html

Mysql HTML table result

Now you will see each query gives result in table, which you can easily put into your HTML page. Now this scenario may never likely come, but when it comes this will be a very quick option for you.

I use these on every local setup and it helps me very much in daily development activities. Hope you find it helpful.

.....

Read full article

Tags: mysql, shell, client, sql