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

Introduction

Increasing customer loyalty is a requirement of any organization. If a recurring project is found, the organization develops. Do not require to generate new leads. The cycle of the company runs from the existing customer and scales the company.

1. Keep in touch

Keep in touch with each client every few months to make sure everything goes smoothly. During a class or meeting, ask about your client's challenges. Issues you can resolve. They are more likely to turn to you when they need you.

2. Create high-quality software

One of the best ways to keep customers coming back is to always develop high-quality software. You can do this by meeting the expectations of your customers and also by making sure that they are satisfied with your work. Help your customers save money and get rid of waste, ultimately deliver huge profits for them, and possibly do more business for you.

3. Listen carefully when your customers are speaking to you

Try to give your customers your undivided attention. Multi-tasking is often. When customers are in front of you, make eye contact.

4. Collaborate with clients

To ensure quality in the software you develop and keep your customers happy, you should encourage collaboration with your clients. Meet with them and ask questions so you can fully understand use cases for the software you are developing. Bring new ideas to make suggestions on how to make improvements.

5. Demonstrate time management skills

Tracking time is also key to the success of a project, you should be able to provide detailed reports to your clients for the time spent on their particular projects. They’ll be more likely to hire you again if they see you’re organized and working efficiently.

6. Keep your promises to your customers

When you tell your customer you are going to do something, follow-up as quickly as possible. Given the time frames and honor them.

7. Don’t waste your customers’ time

Everyone is busy. Complicated voice mail systems, making customers repeat their service requests more than once and un-knowledgeable employees are some major time-wasters. A customer’s time is as important as your time!

8. Ask your customers what they want

Constantly ask your customers what you can do for them and how you can do it better. They want extended hours, academic classes, or the ability to talk to the manager. After you ask, try to give them what they want and keep them informed of the process along the way.

We will see more points in our next tutorial.

July 07, 20202 minutesauthorAnkit Kalathiya
post
The OneSingnal is the market leader in push notification providers. It provides the mobile + web push, email & in-app messages and easy way to send notifications. OneSignal provides an officially core PHP APIs but not Laravel package. We are using OneSignal in many projects and write a bunch of line code in all projects where we needed OneSingnal.

One day I had an Idea in my mind why I should not write a Laravel wrapper for OneSignal?. Finally, I wrote the shailesh-ladumor/one-signal Laravel Wrapper for it. Using this package, we can write neat & clean code and just a few lines of code.

OneSignal add this package in his docs here. and watch the video tutorial here

This package also works with the previous Laravel version.

Today we are going to see how we can use Laravel OneSignal Wrapper in Laravel. Let's see step by step, how we can do that.

Install Packages

Install shailesh-ladumor/one-signal by the following command,

composer require ladumor/one-signal

Publish the config file

Run the following command to publish config file,

php artisan vendor:publish --provider="Ladumor\OneSignal\OneSignalServiceProvider"

Add Provider

Add the provider to your config/app.php into provider section if using lower version of Laravel,

Ladumor\OneSignal\OneSignalServiceProvider::class,

Add Facade

Add the Facade to your config/app.php into aliases section,

'OneSignal' => \Ladumor\OneSignal\OneSignal::class,

configure a .env file with following keys

ONE_SIGNAL_APP_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX ONE_SIGNAL_AUTHORIZE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X ONE_SIGNAL_AUTH_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ONE_SIGNAL_AUTH_KEY is optional if you do not want to create an app. I hope you are familiar with the OneSignal Platform and know how to get APP_ID and AUTHORIZE. If not, you should see the below image for how to get it.

So, we are done. Let's check how to send push notifications.

Checkout this code to send a push notification.

use Ladumor\OneSignal\OneSignal; 
$fields['include_player_ids'] = ['xxxxxxxx-xxxx-xxx-xxxx-yyyyy'] 
$message = 'hey!! This is a test push.!' OneSignal::sendPush($fields, $message);
Video tutorial also available here

July 02, 20202 minutesauthorShailesh Ladumor
post
We are going to change the background of the photo with another texture.

Step:1 Create a new artboard

Press Ctrl+N for creating a new artboard and set its dimension as per requirement. For example, here we took 4951 x 3301 px.

Step:2 Set Image In artboard

Drag image from its location to the artboard.

Step:3 Select cropping portion of the image

For the selection of an image, select the quick selection tool from the toolbar, and select a portion of the image which you want to crop.



Step:4 Select Refine edge

After perfect selection, select a refined edge tool.
After this select overlay from view mode then from edge detection, select a smart radius.

Then set these properties:
  1. Set radius 1 to 2 px
  2. Increase smooth 6 to 7 px from global refinements
  3. Set contrast 20% in global refinements
  4. Check to decontaminate colors in output settings and set amount 70%
  5. Set new layer with a layer mask in output dropdown
Press OK.

Now our image will look like below:

Step 5: set new background

Select a new background that you want to set in the image from its location and set the artboard.

Now place our cropped image above this background and adjust as per requirement.

June 30, 20201 minuteauthorKishan Savaliya
post

Recently, the Laravel team announced a Laravel Fortify. A framework agnostic authentication backend for Laravel applications. It provides registration, authentication along with two-factor authentication.

As said above, it is framework agnostic, so it doesn't provide any blade views with it. You can implement views of your choice of the frontend. Blade, Vue, React with Bootstrap or TailwindCSS, or any other CSS framework.

Today we are going to see how we can use Laravel Fortify with one of the most popular Bootstrap 4 theme AdminLTE v3.

We can actually do that in minutes with the package that we already developed called Laravel UI AdminLTE

This package also works with the previous laravel version to have an authentication system with Laravel UI for Laravel Frontend Scaffolding.

Let's see step by step, how we can do that.

Install Packages

Install Laravel Fortify and Laravel UI AdminLTE by the following command,

composer require laravel/fortify infyomlabs/laravel-ui-adminlte

Publish Fortify Resources

This command will publish all required actions in the app/Actions directory along with the Fortify configuration file and migration for two-factor authentication.

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

Run Migrations

Then run migrations,

php artisan migrate

Add Fortify Service Provider

Next step, add published FortifyServiceProvider to config/app.php

Run AdminLTE Fortify Command

Run the following command,

php artisan ui adminlte-fortify --auth

Install Node Modules and Run a Build

As a next step, install required npm modules and run a build,

npm install && npm run dev

And we are done. Now visit the home page and you should be able to see the full authentication system working including,

Login Registration Forgot Password Reset Password Home page

Laravel AdminLTE UI also provides a starting layout with a sidebar menu and header once you login. so you are all set to go.

June 27, 20202 minutesauthorMitul Golakiya
post

Sometimes we need to load a large amount of data into memory. Like all the models we have in the database.

For e.g. PDF Printing, Perform some global updates, etc.

So the general practices we use in Laravel is to write the following code,

$users = User::all();

Just imagine I have 10,000 users in the database and when I load all the users in one shot.

But it takes a really high amount of memory to load all the records and prepare Laravel Model class objects. And sometimes we also load them in chunks to save the memory, but in some use cases, chunking can not be the option.

Here is the screenshot of mine when I load 10,000 users into memory with the above code.


10k Models


It's using 37MB memory. Also, imagine the required memory if we are loading some relationships as well with these 10,000 records.

The Eloquent model is a great way to handle operations with lots of features like Mutators, Relationships, and much more.

But we really do not use these features all the time. We simply output or use the direct values which are stored in the table. So ideally, we do not need an eloquent model at all, if we are not going to use these features.

In those cases, Laravel also has a handy function toBase(). By calling this function it will retrieve the data from the database but it will not prepare the Eloquent models, but will just give us raw data and help us to save a ton of memory.

So my revised code will look something like this,

$users = User::toBase()->get();

Check the revised memory screenshot after adding the toBase function.


10k Models toBase


So it almost saves 50% of the memory. It's reduced from 35MB to 20MB and the application also works much much faster, because it doesn't need to spend time in preparing 10,000 Eloquent models.

So if you are not really going to use features of Eloquent and loading a large amount of data, then the toBase function can be really useful.

Here you can find a full video tutorial for the same.

June 21, 20202 minutesauthorMitul Golakiya
post

Recently, I've started working on one project where we follow modules patterns and for the same, we have different assets folders for the different modules and the folder named common for assets which are common across all the modules.

So our public folder looks like the following,

Module Asset Functions

The problem that I started facing was everywhere I needed to give a full path to import/include any of the files from any of the folders. For e.g.

<img src="{{ asset('assets/tasks/images/delete.png') }}"alt="Delete Task">

Even if we have some folder in images to group similar images then it was even becoming longer. For e.g.

<img src="{{ asset('assets/tasks/images/social/facebook.png') }}" alt="Facebook">

The workaround that I used is, I created one file called helpers.php and created dedicated asset functions for each of the modules. For e.g., for tasks,

if (!function_exists('tasks_asset')) {     
/**      
* Generate an asset path for the tasks module folder.
*      
* @param  string  $path      
* @param  bool|null  $secure      
* @return string      
*/     
function tasks_asset($path, $secure = null){
    $path = "assets/tasks/".$path;         
    return app('url')->asset($path, $secure);     
  } 
}

With this function, I can use,

<img src="{{ tasks_asset('images/delete.png') }}" alt="Delete Task">

Other advantages it gives are,

  1. if in future if the path of tasks folder changed, then I do not need to go and update every single import/include.
  2. I (or any new developer) do not need to remember the long paths and can always use direct function names for modules.

Even I like this pattern so much, so I went further and created dedicated image function as well,

if (!function_exists('tasks_image')) {     
/**      
* Generate an asset path for the tasks module images folder.
*      
* @param  string  $path      
* @param  bool|null  $secure      
* @return string      
*/     
function tasks_image($path, $secure = null){
    $path = "images/".$path;         
    return tasks_asset($path, $secure);     
  } 
}

So I can use it as,

<img src="{{ tasks_image('delete.png') }}" alt="Delete Task">

Simple and handy functions to use everywhere.

May 16, 20202 minutesauthorMitul Golakiya
post

In chapter 5 of Gita, Karma-Sanyas-Yog, Lord Shree Krishna said in verse 8,

યોગયુક્ત તત્વને જાણનારો મનુષ્ય જુએ, સાંભળે, સ્પર્શ કરે, સૂંઘે, ખાય, ચાલે, ઊંઘે, શ્વાસ લે, બોલે, મળત્યાગ કરે, ગ્રહણ કરે, નેત્ર ઉઘાડે તથા મીંચે તો પણ "ઇન્દ્રિયોના વિષયોમાં ઇન્દ્રિયો વર્તે છે" એમ સમજી "હું કાંઈ જ કરતો નથી" એમ મને છે.

The wise person knows, just like breathing, smelling, opening and closing eyelid, eating, sleep happens automatically, just like that He believes that everything is happening by itself. I’m just doing nothing.

Same in business, do not try to micro-manage things. Some things happen by themselves. And let them happen in their own way. If you will spend your time micro-managing those small things then you will never be able to focus on other important things.

The another important thing is,

જ્ઞાનીઓ વિદ્યાવિનયયુક્ત બ્રાહ્મણમાં, ગાયમાં, હાથીમાં, કૂતરામાં તથા ચાંડાલમાં પણ સમાન દ્રષ્ટિ રાખવાવાળા હોય છે.

Sometimes people only respect certain people. Like some people only give respect to rich and financially sound people but they don’t pay attention to middle or lower income group people. Such behavior is not proper. Same way in the office as well, we give attention to some people and we might not give attention to the office boy, pun, watchman, cleaner, etc., or Senior Developer vs Junior Developer. One must consider each and everyone with equality. Each and everyone should be respected. Just say Hi or Hello and see the Joy that you and they get.

You can also listen to the full podcast here.

February 07, 20201 minuteauthorMitul Golakiya
post

In Last Podcast and Blog Post, we discussed how we need to create 4 departments in our business and become A-Karta in our organization.

But just becoming A-Karta doesn't work. So in verse 7 & 8, Lord Shree Krishna said and this is one of the most popular verses of Bhagavad Gita,

यदा यदा हि धर्मस्य ग्लानिर्भवति भारत |

अभ्युत्थानमधर्मस्य तदात्मानं सृजाम्यहम् || 7 ||

परित्राणाय साधूनां विनाशाय च दुष्कृताम् |

धर्मसंस्थापनार्थाय सम्भवामि युगे युगे || 8 ||

હે ભારત! જયારે જયારે ધર્મ ને હાનિ થાય છે અને અધર્મનું જોર વધે છે, ત્યારે ત્યારે હું જન્મ ધારણ કરું છું. સત્પુરુષોનાં રક્ષણ માટે, દુષ્ટોના વિનાશ માટે અને ધર્મની સારી રીતે સ્થાપના કરવા માટે હું યુગે યુગે પ્રકટું છું.

Whenever there is an increase in unrighteousness, Lord appears on earth age by age, to protect the righteous, to annihilate the wicked, and to re-establish the principles of dharma.

Same way when our 4 created departments do not do their work or perform their duties or do not produce desired results, then we also need to come into the picture time by time.

Whenever there is an increase in unrighteousness - People do not perform their duties or you do not get results

Lord appears on earth age by age - You need to come in the picture time by time

protect the righteous, to annihilate the wicked - to protect your culture & company & its people

and to re-establish the principles of dharma - to re-establish the balance and processes of the company.

You can also listen to the full podcast here.

February 01, 20201 minuteauthorMitul Golakiya