Tips's post

post

Recently Spatie released a brand new package for multi-tenancy called laravel-multitenancy.

It comes with great support to work out of the box with sub-domains like,

https://zluck.infychat.com

https://infyom.infychat.com

https://vasundhara.infychat.com

It identified the tenant based on the sub-domain and sets a database runtime for your tenant-specific models.

Recently, we used it in one of our client for Snow Removal CRM. Here we have two models,

  1. Freemium Model - with no sub-domain (application will work on main domain only)
  2. Premium Model - where the tenant will get its subdomain

And user can convert his account from Freemium to Premium at any point of time by just subscribing to the plan.

So what we want is, on the backend, we want to have a separate database for each tenant, but the application should run on main as well as a sub-domain.

So what we want to have is the ability to extend/customize the tenant detection mechanism. And Spatie does a very good job at there where you can customize the logic.

You can create your own TenantFinder class and configure it in the config file of the package. And there is very good documentation for that here: https://docs.spatie.be/laravel-multitenancy/v1/installation/determining-current-tenant/

To do that, what we did is, we have a field called tenant_id in our users table. All of our users are stored into the main database since we may have user access across the tenant.

And when any user does a login, we listen for the event Illuminate\Auth\Events\Login which is documented in Laravel Docs over here.

When a user does a login, our listener will be called and will create a cookie called tenant on the browser with the tenant id of the user. So our listener will look like,

<?php

namespace App\Listeners;

use App\Models\User;
use Cookie;
use Illuminate\Auth\Events\Login;

class LoginListener
{
    public function handle(Login $event)
    {
        /** @var User $authUser */
        $authUser = $event->user;

        Cookie::forget('tenant');
        Cookie::queue(Cookie::forever('tenant', encrypt($authUser->tenant_id)));
    }
}

Also, we encrypt the cookie on our end, so we do not want Laravel to encrypt it again, so we added tenant cookie into except array of EncryptCookies middleware as per documentation here. so our middleware looks like,

<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'tenant'
    ];
}

Now at the last point, we extended our logic to find a tenant and get it to work on the main domain as well as sub-domain.

We have created our own custom class called InfyChatTenantFinder, which looks like,

<?php

namespace App\TenantFinder;

use App\Models\Account;
use App\Models\SubDomain;
use Illuminate\Http\Request;
use Spatie\Multitenancy\Models\Concerns\UsesTenantModel;
use Spatie\Multitenancy\Models\Tenant;
use Spatie\Multitenancy\TenantFinder\TenantFinder;

class InfyChatTenantFinder extends TenantFinder
{
    use UsesTenantModel;

    public function findForRequest(Request $request): ?Tenant
    {
        $host = $request->getHost();

        list($subDomain) = explode('.', $host, 2);

        // Get Tenant by subdomain if it's on subdomain
        if (!in_array($subDomain, ["www", "admin", "infychat"])) {
            return $this->getTenantModel()::whereDomain($host)->first();
        }

        // Get Tenant from user's account id if it's main domain
        if (in_array($subDomain, ["www", "infychat"])) {

            if ($request->hasCookie('tenant')) {
                $accountId = $request->cookie('tenant');

                $accountId = decrypt($accountId);

                $account = $this->getTenantModel()::find($accountId);

                if (!empty($account)) {
                    return $account;
                }

                \Cookie::forget('tenant');
            }
        }

        return null;
    }
}

So basically, first we check if the sub-domain is there, then find tenant from the sub-domain.

If the domain is the main domain then get the tenant id from the cookie and return the account (tenant) model.

So this is how you can customize the logic the way you want to have a custom tenant identification system.

July 24, 20203 minutesauthorMitul Golakiya
post
[Livewire](https://laravel-livewire.com/docs/2.x/quickstart) is a very awesome thing that I have ever seen, the old school developers are still using the jquery and ajax concept to not refresh the page. But forget the jquery and ajax stuff. If you are good at PHP then you can do the same with Laravel Livewire. ### Wait what? ## Load dynamic data on the page without using ajax? Yes, it is possible with Laravel Livewire. So that is all about laravel livewire, and in this tutorial, we will see how to build laravel pagination with laravel livewire. Let's start and I hope you already have set up the livewire. Let's say you already have created a component named `UsersListing` Now in the users listing, we want to paginate all users and we will list 10 records per page. ## How to use pagination with Laravel Livewire Livewire provides a trait called `WithPagination` and you have to add it into your component `UsersListing`. Check out the following code: ``` use Livewire\WithPagination; use Livewire\Component; class UsersListing extends Component { use WithPagination; public function render() { return view('livewire.users.index', [ 'users' => User::paginate(10), ]); } } ``` And to load pagination you have to add following code: ``` 
 @foreach ($users as $user) ... @endforeach {{ $users->links() }} ``` That's it, and your laravel pagination now works like charm without page refresh. There is much more about pagination like how to use it with a custom view, how to use it with a custom theme. We will see it in our next tutorial, until then enjoy the code
July 11, 20201 minuteauthorVishal Ribdiya
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

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 pattern and for the same, we have different assets folder 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 need 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 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 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

Last week, we were working on one project where we were using LaravelCollective for generating our form. LaravelCollective is a really awesome package and reduces lots of efforts, specifically for automatically binding old inputs to our forms.

Problem

With LaravelColletive when we pass null as a second value, it tried to get old inputs if available and inject them. But for some reason, it was not working with datetimelocal.

datetimelocal need a date in Y-m-d\TH:i Format. When I went into the code of FormBuilder.php, it’s already managing that and tries to convert date into that format if you have passed DateTime object.

So it was completely working fine while creating a record when you do not have any value.

But I have the same form which was used at both the time of Create and Update. And I was passing null into value field at both of the time and LaravelCollective injects it automatically from model or old inputs if there is some error. Something like following,

<div class="form-group col-sm-6">
    {!! Form::label('due_date', 'Due Date:') !!}
    {!! Form::datetimeLocal('due_date', null, ['class' => 'form-control']) !!}
</div>

So, Due date will be automatically placed from the model. It’s working fine with all other fields except datetimelocal.

Solution

The reason behind that is, the value is retrieved from model due_date field, but it comes in Carbon instance and when it converts to a date string, it’s converted into default format which is Y-m-d h:i:s. So it will not work for datetimelocal input since it requires Y-m-d\TH:i format.

So as a solution, what change we did is, instead of passing null value, we first check, if the model is there then pass the value directly to the input. Something like,

<div class="form-group col-sm-6">
    {!! Form::label('due_date', 'Due Date:') !!}
    {!! Form::datetimeLocal('due_date', (isset($task)) ? $task->due_date : null, ['class' => 'form-control']) !!}
</div>

So, I will check if I have passed the model $task to the view and then I will pass a due_date value to input. So FormBuilder will convert it to the proper format and it will get displayed into an input.

Now, when we save the form, it will also return date into Y-m-d\TH:i format, so again we need to convert it to the proper format. For that, we created a mutate attribute for due_date in my Task Model.

public function setDueDateAttribute($value)
{
   $this->attributes['due_date'] = Carbon::parse($value);
}

And that’s it. Our datetimelocal input gets working. I have seen lots of issues on stackoverflow for it. So hope it may help someone.

November 14, 20192 minutesauthorMitul Golakiya
post

Last week we submitted our first app to Envato - CodeCyan called InfyLMS. InfyLMS is a full-fledged library management system to manage libraries built on Laravel+ReactJS.

But it was not just an easy upload. When you upload an item to CodeCyan, they have a dedicated team that tests your application and reviews the code quality of your app.

We have faced a few problems that I want to share, so if someone is uploading his first item to CodeCyan then he/she can review those points before submission and the chances of rejection can be reduced.

1. Do not use remote CDN

We have used Bootstrap in our system as a theme and we were using remote CDN URLs of it for JS and CSS. for e.g.

// CSS
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

// JS
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

All your assets need to be offline available. so download all these files via either some package manager or just go and download it and put it under the respective asset folder.

2. Use Strict Mode in JS

All Javascript files need to be written into Strict mode.

For example, you can do this with jQuery as follows:

(function($) {
    "use strict";
    // Author code here
})(jQuery);

Or you can put

use "strict";

at the starting of your file if they are generated via some webpack. Even there is a babel plugin available which you can use with a webpack.

3. Attach documentation into the main file

Attach documentation into the main file which you are uploading. Create a docs folder into the root folder and attach full documentation to set up the application. Also, remember that this doc should work offline as well. so attach all your assets here as well.

4. Have Clear & Easy documentation for Non-Technical Users

All the customers or buyers do not have technical knowledge that needs to be there to set up an application. so try to make your application setup as easy as possible. for e.g, create a setup wizard which executes step by step and ask for details from customers.

Or create a dedicated section in your documentation for Non Technical users with clear, precise and minimal steps that needs to be performed.

If possible, then try to give two different zip files under your main zip file, like

  • dist - Distribution which can be set up with minimal efforts for non-technical users
  • source - Original source file for technical users

I hope this will help new Envato Authors who are uploading their very first item.

November 04, 20193 minutesauthorMitul Golakiya