Sometimes, we meet these kind of use cases or features when doing web application programming:

1 - multiple type of report to generate

2- multiple type of discount to handle

3- multiple type of payment to handle

I recently faced with this use case when doing a freelance project for my customer.

The requirement for this use case is that we have 2 types of reporting: human resources reporting and accounting reporting. And, for quick and dirty we have simple solution for this as below (we use laravel framework 5)

In routes.php, I added this line

//Route for report
Route::get('report/{type}', 'ReportController@report');

type can be: hr or accounting

and the ReportController controller file, in this file based on type of $type variable, we will call the correspond function.

<?php

namespace App\Http\Controllers;
use App\Repositories\ReportRepository;

class ReportController extends Controller
{
    public function report($type) {
        $reporter = new ReportRepository;

        if($type === 'hr') {
            return $reporter->hrReport($data = []);
        }

        if($type === 'accounting') {
            return $reporter->accountingReport($data = []);
        }

        throw new \Exception('there is report function for kind of ' . $type . ' report');
    }
}

and we simple have the ReportRepository

<?php

namespace App\Repositories;

class ReportRepository
{
    public function hrReport(array $data)
    {
        dd('hrReport function');
    }

    public function accountingReport(array $data)
    {
        dd('accountingReport function');
    }
}

now, try **php artsian serve, **and go to localhost:

http://localhost:8000/report/hr or http://localhost:8000/report/accounting

[caption id=“attachment_421” align=“aligncenter” width=“300”]accounting report accounting report[/caption]

[caption id=“attachment_422” align=“aligncenter” width=“300”]hr-report-eg hr-report-eg[/caption]

The idea for this simple solution is that:

  • In ReportController.php, I injected ReportRepository class into

  • For each type of report (report/accounting or report/hr), the instance $report (type of ReportRepository) will call correspond function with the type of report.

The problem with this way is that it violated **Open for extension and close for modification (O) **rule in SOLID principle ( I have an article about SOLID principle here ) . For e.g: In case, you have another type of report, you must go to the controller and add more if condition, and add more code for the new report type in ReportRepository as well.

By following the O in SOLID principle, you can reduce if condition, separating the of concern of type of report into another class (in this case, I used laravel fw - so the class file is ApplicationServiceProvider), decoupling code dependency and easy for testing (mocking the interface is alway easy).

**So, let’s implement new way that follow O in SOLID **principle.

The idea for this way is:

  • For each type of report, there will be an implementation of function show of ReportRepositoryInteface interface.

  • Injecting ReportRepositoryInteface into each controller for each type of report

  • Binding based context ( type of report) in AppServiceProvider class

I hope it much easier for you to understand the second implementation with this diagram ( not actually class diagram)

[caption id=“attachment_428” align=“aligncenter” width=“984”]class diagram for example use case class diagram for example use case[/caption]

First, we need ReportRepositoryInterface.php file

<?php

namespace App\Repositories;

interface ReportRepositoryInterface
{
    public function show(array $data);
}

then, AccountingReportRepository.php

<?php

namespace App\Repositories;

class AccountingReportRepository implements ReportRepositoryInterface
{
    public function show(array $data)
    {
        dd(get_class($this));
    }
}

and HrReportRepository.php

<?php

namespace App\Repositories;

class HrReportRepository implements ReportRepositoryInterface
{
    public function show(array $data)
    {
        dd(get_class($this));
    }
}

Then, how to make those code work in laravel framework, yah, laravel >5 support contextual binding here https://laravel.com/docs/5.1/container#contextual-binding

adding 2 lines into the** routes.php** file

// Route for accounting report
Route::get('accounting/report', 'AccountingController@report');
// Route for hr report
Route::get('hr/report', 'HrController@report');

It is more reasonable to consider report is an action in AccountingController or HrController ?? :)

AccountingController.php

<?php

namespace App\Http\Controllers;

use App\Repositories\ReportRepositoryInterface;

class AccountingController extends Controller
{
    /**
     * @var ReportRepositoryInterface
     */
    protected $report;

    public function __construct(ReportRepositoryInterface $report)
    {
        $this->report = $report;
    }

    public function report()
    {
        $this->report->show($data = []);
    }
}

HrController.php

<?php

namespace App\Http\Controllers;

use App\Repositories\ReportRepositoryInterface;

class HrController extends Controller
{
    /**
     * @var ReportRepositoryInterface
     */
    protected $report;

    public function __construct(ReportRepositoryInterface $report)
    {
        $this->report = $report;
    }

    public function report()
    {
        $this->report->show($data = []);
    }
}

Then, you just need to bind the concrete class to abstract class just in contextual. The logic is as it is:

  • when AccountingController need ReportRepositoryInterface give it AccountReportRepository

  • when HrController need ReportRepositoryInterface give it HrReportRepository

and, just need to add the code in register function of AppServiceProvider (github code)

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

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

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->when('App\Http\Controllers\AccountingController')
            ->needs('App\Repositories\ReportRepositoryInterface')
            ->give('App\Repositories\AccountingReportRepository');

        $this->app->when('App\Http\Controllers\HrController')
            ->needs('App\Repositories\ReportRepositoryInterface')
            ->give('App\Repositories\HrReportRepository');
    }
}

github code for this tutorial (it may contain code of another tutorial):

https://github.com/nguyentienlong/laravel_sandbox_validation/tree/context-binding/

reference:

https://laracasts.com/discuss/channels/general-discussion/what-do-you-think-about-contextual-binding-ioc-laravel-5

@todo: write a demo test code for the repository