InfyOm Blog

latest-post

Nowadays multi-tenant applications are more useful than single-tenant applications. We can use multi-tenant with multiple databases or single databases as per our need. But it's better to use a single DB with a multi-tenant when you have a small application.

In this tutorial, we are going to use multi-tenant with a single database.

We will implement multi-tenant with single DB by using the following package: https://github.com/archtechx/tenancy

Assuming you already have Laravel 8 repo setup. Now please follow the given steps to implement multi-tenancy with a single DB.

Package Installation

Run following commands :

  1. composer require stancl/tenancy

  2. php artisan tenancy:install

  3. php artisan migrate

Add following service provider to config/app.php

App\Providers\TenancyServiceProvider::class

Create Custom Model

Now create modal named MultiTenant into app\Models

MultiTenant.php

 SavingTenant::class,
        'saved'    => TenantSaved::class,
        'creating' => CreatingTenant::class,
        //        'created' => TenantCreated::class,
        'updating' => UpdatingTenant::class,
        'updated'  => TenantUpdated::class,
        'deleting' => DeletingTenant::class,
        'deleted'  => TenantDeleted::class,
    ];
}

Update Tenancy Configuration

As we have added custom model we also need to define that model into config/tenancy.php

Please change tenant_model value to our custom model.

'tenant_model' => \App\Models\MultiTenant::class,

Add Resolver

To use multi tenant with single DB we also need to add our customer resolver, that will be used into Middlewares that we will create ahead.

Create MultiTenantResolver into app\Resolvers

App\Resolvers\MultiTenantResolver.php

find(Auth::user()->tenant_id)) {
            return $tenant;
        }

        throw new TenantCouldNotBeIdentifiedByPathException($id);
    }

    public function getArgsForTenant(Tenant $tenant): array
    {
        return [
            [$tenant->id],
        ];
    }
}

Add Middleware

We will create our custom middleware that will set the current tenant into cache, and that will used by package to fire default query where('tenant_id', "tenant id we have set into middleware")

App\Http\Middleware\MultiTenantMiddleware.php

tenancy = $tenancy;
        $this->resolver = $resolver;
    }

    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $tenant = Auth::user()->tenant_id;

        return $this->initializeTenancy(
            $request, $next, $tenant
        );
    }
}

Also, don't forget to add middleware alias into App\Http\kernel.php

 protected $routeMiddleware = [
        ..............
        'multi_tenant' => MultiTenantMiddleware::class,
];

Now we will apply this multi_tenant middleware to our routes.

Add Trait to tenant-specific models

We have to add BelongsToTenant trait to all of our tenant-specific models.

Say if we want to add tenant_id into the users table then we must have to add BelongsToTenant to the app\Models\User model.

That trait will by default add following query everytime when we will try to fetch records or update records.

Where('tenant_id', 'tenant id will taken from cache')

Add tenant_id to tenant-specific migrations

As we have added the tenant trait, we must have to add tenant_id into tenant-specific migrations as specified below.

public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            ...........................

            $table->string('tenant_id');

            $table->foreign('tenant_id')
                ->references('id')
                ->on('tenants')
                ->onUpdate('cascade')
                ->onDelete('cascade');

            $table->timestamps();
        });

Update TenancyServiceProvider

Replace the App\Providers\TenactServiceProvider by the following code.

 [],
            Events\TenantCreated::class => [
                JobPipeline::make([
                    Jobs\CreateDatabase::class,
                    Jobs\MigrateDatabase::class,
                    // Jobs\SeedDatabase::class,

                    // Your own jobs to prepare the tenant.
                    // Provision API keys, create S3 buckets, anything you want!

                ])->send(function (Events\TenantCreated $event) {
                    return $event->tenant;
                })->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
            ],
            Events\SavingTenant::class => [],
            Events\TenantSaved::class => [],
            Events\UpdatingTenant::class => [],
            Events\TenantUpdated::class => [],
            Events\DeletingTenant::class => [],
            Events\TenantDeleted::class => [
                JobPipeline::make([
                    Jobs\DeleteDatabase::class,
                ])->send(function (Events\TenantDeleted $event) {
                    return $event->tenant;
                })->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
            ],

            // Domain events
            Events\CreatingDomain::class => [],
            Events\DomainCreated::class => [],
            Events\SavingDomain::class => [],
            Events\DomainSaved::class => [],
            Events\UpdatingDomain::class => [],
            Events\DomainUpdated::class => [],
            Events\DeletingDomain::class => [],
            Events\DomainDeleted::class => [],

            // Database events
            Events\DatabaseCreated::class => [],
            Events\DatabaseMigrated::class => [],
            Events\DatabaseSeeded::class => [],
            Events\DatabaseRolledBack::class => [],
            Events\DatabaseDeleted::class => [],

            // Tenancy events
            Events\InitializingTenancy::class => [],
            Events\TenancyInitialized::class => [
//                Listeners\BootstrapTenancy::class,
            ],

            Events\EndingTenancy::class => [],
            Events\TenancyEnded::class => [
                Listeners\RevertToCentralContext::class,
            ],

            Events\BootstrappingTenancy::class => [],
            Events\TenancyBootstrapped::class => [],
            Events\RevertingToCentralContext::class => [],
            Events\RevertedToCentralContext::class => [],

            // Resource syncing
            Events\SyncedResourceSaved::class => [
                Listeners\UpdateSyncedResource::class,
            ],

            // Fired only when a synced resource is changed in a different DB than the origin DB (to avoid infinite loops)
            Events\SyncedResourceChangedInForeignDatabase::class => [],
        ];
    }

    public function register()
    {
        //
    }

    public function boot()
    {
        $this->bootEvents();
//        $this->mapRoutes();

        $this->makeTenancyMiddlewareHighestPriority();
    }

    protected function bootEvents()
    {
        foreach ($this->events() as $event => $listeners) {
            foreach (array_unique($listeners) as $listener) {
                if ($listener instanceof JobPipeline) {
                    $listener = $listener->toListener();
                }

                Event::listen($event, $listener);
            }
        }
    }

    protected function mapRoutes()
    {
        if (file_exists(base_path('routes/tenant.php'))) {
            Route::namespace(static::$controllerNamespace)
                ->group(base_path('routes/tenant.php'));
        }
    }

    protected function makeTenancyMiddlewareHighestPriority()
    {
        $tenancyMiddleware = [
            // Even higher priority than the initialization middleware
            Middleware\PreventAccessFromCentralDomains::class,

            Middleware\InitializeTenancyByDomain::class,
            Middleware\InitializeTenancyBySubdomain::class,
            Middleware\InitializeTenancyByDomainOrSubdomain::class,
            Middleware\InitializeTenancyByPath::class,
            Middleware\InitializeTenancyByRequestData::class,
        ];

        foreach (array_reverse($tenancyMiddleware) as $middleware) {
            $this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
        }
    }
}

Create / Fetch Tenant

Now we have to create a tenant and give that tenant_id to related users.

each user contains their specific tenant_id.

Use the following code to create a tenant :

 $tenant1 = \App\Models\MultiTenant::create([
     'name' => 'Tenant 1'
 ]);

 $tenant2 = \App\Models\MultiTenant::create([
     'name' => 'Tenant 2'
  ]);

That will create tenant into tenants table and values will be stored into data column as a son.

$tenant1 = App\Models\MultiTenant::where('data->name', 'Tenant 1')->first();

$tenant2 = App\Models\MultiTenant::where('data->name', 'Tenant 2')->first();

$tenant1User = User::where('id', 'user id here')->update(['tenant_id' => $tenant1->id]);

$tenant2User = User::where('id', 'user id here')->update(['tenant_id' => $tenant2->id]);

Now we have 2 tenants with 2 separate users who contain separate tenant ids.

Add Middleware to Routes

Now do login with User 1 and try to fetch all users from the database, it will return users of logged-in users' tenants only.

As we have the BelongToTenant trait into the User model.

Route::group(['middleware' => ['auth', 'multi_tenant']], function () { Route::get('users', function() {

 // only tenant-1 users will be returned because we are setting the logged-in user tenant into the cache from `multi_tenant`middleware.
 $allUsers = User::all();
});

});

You can use the same for other models too.

Hope this helps you.

August 14, 20212 minutesuserVishal Ribdiya

Posts

post

Last month, I started training in one company where I am giving Laravel Training to around 15-20 PHP Developers, where we discussed the core Laravel concepts to them including Service Providers, Facades, Eloquent, and many more.

There I explained to them the use and beauty of Laravel Facades and how the use of Facade can simplify code. We took a real practical example and use case they have, which I'm outlining here.

Use Case

They are building a CRM application and using some third party API to send an SMS. Though Laravel Notification Channels are the best way to send the SMS but here that's not the goal of this post, so we will try to stick to the Facade development to send the SMS.

Here is how we were sending SMS so far.

Traditional way

Have one common Class/Repository/Manager called SMSManager.

<?php
namespace App\Managers;

class SMSManager
{
    public function sendSMS($recipient, $message)
    {
        // code to send sms
    }
}

This code and pattern look completely fine. But the problem was every time when we needed to send an SMS they needed to either Create/Inject SMSManager class and call sendSMS method of it.

... $smsManager = new SMSManager(); $smsManager->sendSMS($phoneNumber, $message); ...

Also, this code is not scalable. If you want to change your SMS Provider then you need to replace the function or create a new function. Or you want to use multiple SMS Providers for different users based on their subscription then this way doesn't work.

Laravel Facade Way

Laravel Facades is a great way to call static interfaces of the classes. So you don't need to make/inject SMSManager class instance and you can directly call sendSMS function.

To do that, we need to create Facade class and bind its accessor in Service Provider. We can create our own service provider or even can do it in AppServiceProvider as well. To keep it simple, let's use AppServiceProvider as of now.

Create a Facade Class

Create a new class called SMSGateway into app\Facades directory with the following code,

<?php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class SMSGateway extends Facade
{
    /**
      Get the registered name of the component.
     
      @return string
     /
    protected static function getFacadeAccessor()
    {
        return 'sms_gateway';
    }
}

Bind Facade with SMSManager Implementation

The next step is to bind our facade class with our real SMSManager implementation. To do that, open AppServiceProvider and add the following line to the register method,

$this->app->bind('sms_gateway', function ($app) {     return new SMSManager(); });

Now, we are all set to use our Facade in our code.

Usage of Facade in code

To send an SMS, we now no longer need to create instance of SMSManager, we can directly use,

\App\Facades\SMSGateway::sendSMS($phoneNumber, $message);

Make Alias (Optional)

Or, you can add this facade to config/app.php into aliases section,

'SMSGateway' => App\Facades\SMSGateway::class,

And you can directly call,

\SMSGateway::sendSMS($phoneNumber, $message);

So this way, we can clean out our code and utilize the Laravel Facade to make our code more beautiful.

Hope this helps.

November 03, 20193 minutesauthorMitul Golakiya
post

Last month we started working on one of our internal project called InfyTracker, which we were planning to use as a simple time tracker and very little of project management or task reporting.

We have also published it as an open source project to our Github account so other people from the community can use it. It's still in alpha stage and we are still working on it and making tons of improvements. But, While building this project, we found a few things that we think might be useful to optimize the app or can be used in other projects. Also, I think lots of developers even do not realize these things.

So I planned to share it with the community where I will be discussing them in a detail with solutions we used. So here are the things/mistakes that we found during our project development.

  1. Adding all stylesheets and scripts in a layout file
  2. Not separating out page level CSS or JS
  3. Not using laravel-mix

1. Adding all stylesheets and scripts in layout

What I've seen is, when we want to add a new library/package to the project, people generally go to the layout file and just insert a style and script tag there.

For e.g., I want to use moment-js in the app, so what I do, I go to my layout file and include moment-js script tag there via CDN. Even if I only need moment-js in few of my pages. same can be there for other libraries like datatables, even if we only need datatables in a few pages of the app.

The problem it creates is, even if we don't need them in most of our pages, those files are still included in our page and of course, the page takes more time to load.

2. Not separating out page level CSS or JS

Generally, we use lots of JS in our code. And for all page level JS/CSS, most of the developers put it in a blade file of the page at the bottom with some section of JS/CSS.

For e.g, I have used a datatable in my page, so I need to initialize the datatable. so generally, I will have declared one section called scripts or bottom_js which will be yielded in my layout file. That's what I have seen in lots of code.

But, there are two problems with that,

  1. When that page is loaded, your JS code is completely visible to the world and sometimes that's not good and secure
  2. It's not minified (and since it's sitting into your blade files, there is no way to minify it either)

3. Not using laravel-mix

In really few projects or very few expert developers use laravel-mix or use laravel-mix in the right way. When you do not use laravel-mix in your site, your JS/CSS files are not minified. It's completely visible to the world which can be sometimes dangerous and file sizes are big as well in large projects.

That's the three major things that we found while developing this project.

Here is the solution that we used to overcome these problems.

Solution 1. Adding all stylesheets and scripts in specific pages

To resolve this problem, we declared two sections in our layout file.

  1. stylesheets
  2. scripts

so all of our web pages which has a dependency for the third-party CSS/JS libraries will look like following,

@extends('layouts.app') 
@section('stylesheets')     

@endsection 

@section('content')     
....content here.... 
@endsection 

@section('scripts')     

@endsection 

As per the above example, the iCheck library will be only added on pages where we are actually using iCheck instead of all of my web pages.

Solution 2. Separating out page level CSS or JS

To resolve this problem, we also declared two new sections in the layout file.

  1. page_css
  2. page_js

page_css section will contain page level CSS and page_js section will contain page level javascript like initializing datatable or checkboxes etc. Also, all page level CSS and JS are not placed directly inside the blade file. But we go one step further.

Under the resources/assets folder, we have two folders,

  1. js - contains all page level JS for different blade files
  2. styles - contains all page level CSS for different blade files

We tried to follow the same folder structure for this that we used for our blade views.

Folder Structure

If you have multiple files then you can try to use names like index.js, edit.js, etc, just the same as your blade view names.

And in your blade view files, you can include them as a script or stylesheet. For e.g.

@section('page_js')     

@endsection

Note: Use of mix is explained in the next section. so you can just ignore that as of now. But the basic idea is, include them as a script or stylesheet with sections, instead of directly putting them in blade files.

Solution 3. Using laravel-mix

Laravel mix is a great tool for asset compilation. All of the page level JS/CSS files and other common JS/CSS should be compiled by laravel mix and should be copied to the public folder with versioning for cache busting. And then it will be included via mix helper in our blade views. (As explained in the above solution at the bottom of the section).

For e.g. our webpack.mix.js looks like the following,

/* CSS */ 
mix.sass(
'resources/assets/style/sass/laravel/app.scss',
'public/assets/style/css/app.css')   .sass('resources/assets/style/sass/style.scss',
'public/assets/style/css/style.css') .sass('resources/assets/style/sass/dashboard.scss',
'public/assets/style/css/dashboard.css')
.version(); 

/* JS */ 
mix.js(
'resources/assets/js/custom.js',
'public/assets/js/custom.js') .js('resources/assets/js/users/user.js',
'public/assets/js/users/user.js') .js('resources/assets/js/task/task.js',
'public/assets/js/task/task.js')    
.version();

This will minify our code and make it secure in the production environment.

Hope this helps.

June 19, 20195 minutesauthorMitul Golakiya
post

Lots of people asked a question on our Github Repo about how they can display relationship data into index while using Datatables. so I decided to write a post with a detailed tutorial on that. Let's see how it's possible.

Problem:

Let's imagine the following simple scenario. We have a users table and we have a posts table. Post table is pretty simple with the following fields:

  • id
  • author_id
  • title
  • body
  • created_at
  • updated_at

Where author_id is a foreign key to the users table. We want to display the author name into the index table.

But since we need that data from the relationship, there is no direct way to display that into Datatable.

Solution:

Let's perform the following steps (before performing these steps, generate your CRUD with datatables for Post model):

Step 1: Add author relationship into Post model

Since we want to display the author name of the post, let's add author relationship into Post.php

public function author() {     
return $this->belongsTo(User::class, 'author_id'); 
}

Step 2: Modify PostDataTable

Since we have to display relationship data we need to load it using eager loading of laravel. Modify the query method of PostDataTable.php. something like,

public function query(Post $model) {     
return $model->newQuery()->with(['author']); 
}

Also, we need to add one more column author name into the datatable. so modify getColumns method and add that to the array. something like,

protected function getColumns() {     
return [        
       'title',         
       'author_name' => new \Yajra\DataTables\Html\Column([
       'title' => 'Author Name', 
       'data' => 'author.name',
       'name' => 'author.name'
      ])     
  ]; 
}

This will add one more column with header "Author Name" in the datatable before "Action" column. Here, we are adding custom datatable column where key will the name of column which datatable use internally, title will be used for Datatable Column header title and data will be used for retrieving data.

And that's it. Run your code and you should be able to see Author name of the post into your datatable.

March 19, 20192 minutesauthorMitul Golakiya
post

InfyOm Laravel Generator comes with two possible choices for a table in the index view.

  1. Native table with blade
  2. Datatables

Datatables comes with a huge set of features if you really need it and InfyOm Generator is capable of generating your CRUD table with Datatables. It uses yajra/laravel-datatables-oracle for that. But I heard from lots of people that they got confused about the installation of that package and how to use it with Generator. so I decided to write a post on that.

Yajra datatables come with a few different packages, one for core datatables and other plugins like buttons, HTML, export CSV etc. When you are using it with InfyOm Generator, you will need all of them since we generate a table with a full set of features.

Perform the following steps.

Step 1: Add Packages

In the first step, we need to add these packages into our project based on the laravel version we are using. Check the following table:

Laravel Version yajra/laravel-datatables-oracle yajra/laravel-datatables-buttons

yajra/laravel-datatables-html

5.5 8.x 4.x 4.x
5.6 8.x 4.x 4.x
5.7 8.x 4.x 4.x
5.8 9.x 4.x 4.x

Based on your laravel version, install these packages and add the following service providers and facade in config/app.php.

/** Service Providers **/  Yajra\Datatables\DatatablesServiceProvider::class, Yajra\Datatables\ButtonsServiceProvider::class, Yajra\Datatables\HtmlServiceProvider::class, 
/** Facade **/ 
'Datatables' => Yajra\Datatables\Facades\Datatables::class,  

Step 2: Run Vendor Publish

Once you add this, publish the assets for all these service providers. This is also one critical step. Make sure you run a vendor:publish for all these service providers. Or you can just run php artisan vendor:publish and select the service provider from the list and do this for all of these three service providers. It will publish some assets into your public directory.

Step 3: Enable Datatable option for Generator

After publishing assets, go to config/infyom/laravel_generator.php and make add_on => datatables => true.

Step 4: Scripts and CSS section into blade layout (Optional)

If you have used publish layout option then you can skip this step. Otherwise, make sure you have scripts and CSS sections into your main blade layout file. Since that's where all scripts and CSS will be added for datatables.

And that's it. Try to generate CRUD for some model and it should generate your crud with Datatables.

March 15, 20192 minutesauthorMitul Golakiya
post

Introducing Laravel 5.8 support to InfyOm Laravel Generator with more cleaner Repository Pattern

Laravel 5.8 just released this week with a bunch of new improvements. You can read the full post here about new updates. so in a very small fraction of time, we also worked on adding support for Laravel 5.8 into our generator. You can read the installation steps here.

Also, one another feature or improvement we have done is, we tried to create a cleaner and extendable repository pattern while generating CRUD. so far we were using prettus/l5-repository package, which is really awesome if you do not want to write your general functions of create/all/update/delete/find in all of your repositories. I really loved that package and that's the reason we extended that package when we created our repository.

This is all great when you are talking about simple CRUD functions. But things get confusing when people want to customize their code. I got a lot of emails and also lots of people created issues on Github regarding how to customize that function based on their certain needs.

So with this version, I decided to write our own simple BaseRepository which will be published into app/Repositories/BaseRepository.php. so developers are free to customize all the basic functions.

Actually, this is also possible with prettus/l5-repository as well and with our generator as well by publishing templates. But that needs some more work and some deep knowledge of customizing templates. But with this update, it will be easier.

Right now, I do not expect any breaking changes who are migrating their code from 5.7 to 5.8 which is using a generator. I tried to keep all old BaseRepository classes and repository packages into dependencies. All their existing generated repositories should work fine.

Still, if someone is getting any errors then they can contact me by creating issues on Github. I will try to respond there.

Hope this release will help and people can start to get started to upgrade their code to Laravel 5.8.

March 02, 20192 minutesauthorMitul Golakiya
post

We used Amazon SNS into one of our project where we need to receive instant updates to the various different microservices from other microservices. I was not able to find any good article which highlights everything about SNS integration into PHP or Laravel. This can be most confusing who is working with SNS for the first time. so I decided to write one which can help others. Following things are really important before we get started.

  • What exactly SNS is?
  • Where/When you can use it?
  • How does it work?
  • How to integrate it with PHP?

I will try to explain in a little bit brief here about SNS and will focus more on integration and problem that we tried to solve.

1. What exactly AWS SNS is?

AWS SNS stands for Amazon Simple Notification Service. It's a distributed pub/sub messaging system for microservices. Where publishers can publish a message to different subscribers with various kinds of subscribers including SMS, HTTP Endpoints or Webhooks, Amazon SQS, Lambda, Mobile Push etc. We will cover HTTP only subscribers in this post.

Kind of pub/sub where the publisher can publish a message and multiple interested subscribers can subscribe to that. You can read more on the AWS Website Here.

2. When you need AWS SNS?

When your project is distributed into various microservices and if you need to communicate from one or multiple microservices to other microservices then SNS comes to play a really good role.

Our Problem:

In our case, our project contains 3 different systems, where authentication + user management is handled by one central system. But each system has its local users table, where minimal users data is cached for fast data retrieval and table joins. so it doesn't need to query the central user system for each and every query.

If any new user is created by Admin on a central user management system, we need to notify other systems that a new user is created and they update their local users table.

3. How does it work?

Amazon SNS provides topics. You need to create a Topic and then define subscribers either programmatically or manually from Amazon Console. It depends on your use case. If you want to dynamically add subscribers then go that way or if you have some fixed microservices then you can define it from the console directly. This process works as follows:

  1. Create a Topic in AWS Console
  2. Create an HTTP subscriber
  3. SNS will send a SubscriptionConfirmation message to your HTTP Webhook
  4. Your microservice needs to call SubscribeURL
  5. Your subscription will be confirmed

This is the initial setup process. Once this is done. Everytime when any new message is published to Topic, it will call your webhook with that Message.

4. How to integrate it with PHP (Or any other PHP Frameworks like Laravel)?

Amazon has a very good PHP Library for their AWS products. Recently for SNS they have created another light-weight library to handle SNS Messages.

So we will use the following libraries in our integration.

Solution:

I will try to highlight the solution that we used to fix the above-mentioned problem. Check the following diagram:

As you can see, we set up an SNS Topic and created two HTTP/HTTPS subscribers for two different microservices. When an admin user creates a new user into the system, we publish a message to SNS Topic which sends an update to both different microservices.

Now, let's jump into the code.

Publishing a message:

You need to add aws/aws-sdk-php into your project. You can find installation steps on Github Repo. Also, you need to be familiar with AWS authentication process. These things are explained pretty well here. Collect all the things you need in terms of credentials.

  • Key
  • Secret
  • Region
  • Topic ARN

Following code hooked up into our central auth system. You can do this from wherever you want to publish your message. Create a client and then publish a message. You can pass two things into the SNS Message. Subject & Message body. Message body has several options. We will use a pure JSON string way for simplification.

use Aws\Credentials\Credentials; 
use Aws\Sns\SnsClient; 
$client = new SnsClient([     
      'version' => '2010-03-31',     
      'region' => $amazonRegion,    
      'credentials' => new Credentials(         
            $awsKey,         
            $awsSecret     
          ) 
      ]); 
      $subject = 'You got a new SNS Message'; 
      $message = json_encode([
      'message' => 'this is my first message via SNS Topic'
]); 
      $client->publish([    
      'TopicArn' => 'your-sns-topic-arn-here',     
      'Message' => $message,    
      'Subject' => $subject 
]); 

Generally, in our integration, what we did was, we used Subject to specify the type of event. Like Users.create, Users.update, Users.delete and Message used to contain user information. You can customize it based on your use-case.

That's it. Your message is published to a topic.

Handle Incoming SNS Message:

To handle SNS messages we need to integrate aws/aws-php-sns-message-validator into our project.

SNS will call our webhook for multiple kinds of events. It comes with Type param into the message body.

  • SubscriptionConfirmation
  • Message
  • UnsubscriptionConfirmation

Based on the Type parameter, we need to take relevant action. We have used the following code into our microservice webhook handlers.

use Aws\Sns\Message; 
use Aws\Sns\MessageValidator; 

try {     
// Retrieve the message     
$message = Message::fromRawPostData();   

// make validator instance     
$validator = new MessageValidator();   

// Validate the message     
if ($validator->isValid($message)) {         
      if ($message['Type'] == 'SubscriptionConfirmation') {  

// if it's subscription or unsubscribe event then call SubscribeURL             
      file_get_contents($message['SubscribeURL']);         
} elseif ($message['Type'] === 'Notification') {             
      $subject = $message['Subject'];             
      $messageData = json_decode($message['Message']);   

// use $subject and $messageData and take relevant action         
         }
     }
 } catch (Exception $e) {     // Handle exception .
} 

As you can see, based on Type, we are performing different operations.

This way, all our microservice can communicate in a very effective and highly available way to each other. Hope this helps :).

February 23, 20195 minutesauthorMitul Golakiya
post

Lots of people are wondering about implementing a multi-column search with Datatables and specifically how we can implement it with infyom laravel generator. There are several examples for creating a multi-column search with a traditional javascript way. Here are the links for that:

These all examples work great, but still, the missing piece is, there are no examples by which we can implement a laravel service which yajra provides. So after some research and reviewing several examples, finally I found out a way to create a multi-column search with yajra datatables with our generator. Here is the detailed guide to implementing it:

Before moving ahead, generate all scaffolds with datatables option true and make sure, your generated scaffold is fully working without any error.

Now, find out following files from your generated files (In my case, My model name is Post, find files, based on your generated files):

  • resources\views\posts\table.blade.php
  • app\DataTables\PostDataTable.php

Now we have to do the following changes in both files.

table.blade.php

find out this line,

 {!! $dataTable->table(['width' => '100%']) !!}

and add an extra parameter true to table function, like this:

 {!! $dataTable->table(['width' => '100%'], true) !!}

What it will do is, it will tell the datatable HTML builder that we want to draw a footer. so we can add our search boxes there. For more reference, you can check here.

Let's move to next file.

PostDataTable.php

In your `PostDataTable.php`, find out html() function, and we need to add one extra parameter to parameters array for a builder,

'initComplete' => function () {
this.api().columns().every(function () {
    var column = this;
    var input = document.createElement("input");
    $(input).appendTo($(column.footer()).empty()).on(\'change\', function () {
    column.search($(this).val(), false, false, true).draw();
    });
  });
} 

so it should look like as follows,

 public function html()
{
    return $this->builder()->columns($this->getColumns())
        ->addAction(['width' => '10%'])
        ->ajax('')
        ->parameters([
            'dom'          => 'Bfrtip',
            'scrollX'      => false,
            'buttons'      => [
                [
                    'extend' => 'print',
                    'text'   => 'Print all',
                ],
                [
                    'extend'        => 'print',
                    'text'          => 'Print selected',
                    'exportOptions' => ['modifier' => ['selected' => true]],
                ],
            ],
            'select'       => true,
            'initComplete' => function () {
this.api().columns().every(function () {
    var column = this;
    var input = document.createElement("input");
    $(input).appendTo($(column.footer()).empty()).on(\'change\', function () {
    column.search($(this).val(), false, false, true).draw();
    });
  });
 }
]);
}

Here, we are listening for the `initComplete` event of datatable and adding search input to every column footer. Also, we are listening for change event of each text input and searching that column. It will only work when you hit enter as we are listening for a change event, if you like to do instance search, then you should listen for keyup event.

That's it. Your search for each column should be working now.

March 04, 20172 minutesauthorMitul Golakiya
post

Another minor update release for InfyOm Laravel Generator with some enhancement for datatables and few bug fixes.

This release contains datatable duplication script & CSS fixes with partial files support for datatables js and CSS files.

Also, it contains a few bug fixes about save JSON model schema, text area field generation and few more.

You can find full release notes here.

September 30, 20161 minuteauthorMitul Golakiya