توی کتاب Head First Design Pattern بعد از اینکه در مورد خیلی از دیزاین پترن ها صحبت میکنه توی فصل آخر به طنز میگه اونهایی که تازه در مورد دیزاین پترن ها خوندن میان می پرسن برای Hello World از چه دیزاین پترنی استفاده کنم!
در ادامه هم میگه اگه راه حل ساده تری دارید و داره کار میکنه نیاز نیست پیچیدگی بیهوده اضافه کنید ولی اگه مطمئن هستید (نه بر اساس حدس و گمان) که در آینده تغییرات زیادی دارید و مشکل میخورید اون موقع میتونید دیزاین پترن مناسب خودتونو استفاده کنید.
در مورد اینجا هم این صدق میکنه قرار نیست برای همه کلاساتون یه اینترفیس بسازید و ... اینکار بهش میگن over-engineering و فقط پیچیدگی اضافی توی سیستمون اضافه میکنید. البته همه اینا به اندازه پروژه تون یا دقیق تر بگم به context بستگی داره. اینکه قراره چی کار بکنید. فرضا اگه unit test برای کدهاتون نمی نویسید خیلی از چیزهایی که گفته شد فایده ای براتون نداره فقط پیچیدگی الکی اضافه کردید.
----------------
اما بریم سراغ موضوع. برای design by contract و dependency injection این کدی هست که من چند روز پیش برای پروژه فعلیم نوشتم. برای push notification فرستادن از سمت سرور به موبایل/تبلت ها موقعی که یه آیتم ساخته میشه و مشترکین باید مطلع میشدن یا نظری به یه آیتم داده میشد و صاحب آیتم باید مطلع میشد نیاز به این داشتم از یه سرویسی برای اینکار استفاده کنم. من بعد از گشتم pushbots رو پیدا کردم. اما نکته ای که داشت اینکه کلاسی که داشت ناقص بود و اسم متدها هم به پروژه من نمیخورد در نتیجه براش یه wrapper نوشتم. اما برای اینکار مطمئن بودم احتمال داره در آینده از این سیستم استفاده نکنم و چون در جاهای مختلف از این کلاس استفاده کردم در نتیجه تصمیم گرفتم یه اینترفیس براش تعریف کنم و به جای استفاده مستقیم از کلاس و وابسته شدن به پیاده سازی اون با اینترفیس کار کنم.
اینترفیس ساده ای که نوشتم (که بعدا میتونه تغییر کنه در صورت نیاز!):
<?php
namespace App\Utility\Push\Contract;
interface PushService
{
public function to(array $users);
public function message($message);
public function push();
}
اینم کلاس wrapper من برای کتابخانه pushbots هست:
<?php
namespace App\Utility\Push\Pushbots;
use App\Utility\Push\Contract\PushService;
class PushBots implements PushService
{
private $pushBots;
private $platforms = [
PushBotsLib::PLATFORM_IOS,
PushBotsLib::PLATFORM_ANDROID,
PushBotsLib::PLATFORM_CHROME,
];
function __construct(PushBotsLib $pushBots)
{
$this->pushBots = $pushBots;
}
public function to(array $users)
{
$this->pushBots->Alias($users);
return $this;
}
public function message($message)
{
$this->pushBots->Alert($message);
return $this;
}
public function push()
{
$this->pushBots->Platform($this->platforms);
return $this->pushBots->Push();
}
}
# برای ساده و کوتاه شدن کدها doctype هارو پاک کردم:)
حالا سوال این هست که چه جوری از این کلاس استفاده کنم. اگه توی متدها یا constructor کلاسم بنویسم:
function __construct(PushBots $pushBots)
{
$this->pushBots = $pushBots;
}
container لاراول یه نمونه از PushBots میسازه همچنین چون توی constructor اون هم PushBotsLib رو هم میسازه و پاس میده (به صورت بازگشتی).
اما این یه مشکل داره اولا اینکه از اینتفریس استفاده نکردم دوما اینکه api سرویس pushbots باید app_secret و app_id رو تنظیم کنید تا کار کنه!.
برای اینکار container لاراول متد bind رو گذاشته. با این متد میتونید بگید هر موقع فلان چیز رو خواستم نمونه این کلاس رو پاس بده! یا حتی میتونید یه closure به عنوان آرگومان دوم پاس بدید و بعد از تنظیماتی که انجام دادید (مثلا در اینجا app_secret و app_id تنظیم باید بشن) اون شی رو پاس بدید.
app()->bind(PushService::class, function () {
$pushBotsLib = new PushBotsLib();
$pushBotsLib->App(
config('services.pushbots.app_id'),
config('services.pushbots.app_secret')
);
return new PushBots($pushBotsLib);
});
خوب به نظر همه چی خوب میاد + اینکه هر موقع نیاز به این داشتم که از یه سرویس دیگه استفاده کنم کافیه نمونه یه کلاس دیگه رو پاس بدم! فقط همین!
اما سوال دیگه اینه که اینو کجا باید بذاریم تا کار کنه. راحت ترین کار گذاشتن توی routes.php هست ولی خوب این باعث میشه خیلی شلوغ و بهم ریخته بشه!. برای همین لاراول Service Provider هارو معرفی کرده. همراه با لاراول به صورت پیشفرض AppServiceProvider توی app/Providers وجود داره میتونید از همون استفاده کنید یا اینکه با دستور:
php artisan make:provider PushServiceProvider
یک service provider جدید بسازید. اگه از روش دوم استفاده کردید باید اونو در آرایه providers در config/app.php اضافه کنید تا لاراول اونو لود کنه.
حالا کافیه کد bind مون رو توی متد register بذاریم:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Utility\Push\Contract\PushService;
use App\Utility\Push\Pushbots\PushBots;
use App\Utility\Push\Pushbots\PushBotsLib;
class PushServiceProvider extends ServiceProvider
{
public function boot()
{
//
}
public function register()
{
$this->app->bind(PushService::class, function () {
$pushBotsLib = new PushBotsLib();
$pushBotsLib->App(
config('services.pushbots.app_id'),
config('services.pushbots.app_secret')
);
return new PushBots($pushBotsLib);
});
}
}
برای استفاده هم کافیه توی متد یا constructor کنترل ها یا متدهای handle رویداد ها یا jobs/commands یا و ... استفاده کنم. (به صورت دستی هم میتونید از dependency injection لاراول توی کلاس های دیگه استفاده کنید)
public function handle(PushService $push){
$name = $this->user->fullname;
$text = $this->comment->text;
$username = $this->author->username;
$push->message(trans('push.new_comment', compact('name', 'text')));
$push->to([$username]);
$push->push();
}
--------------
مثال خیلی خوب دیگه این هست که پیشنهاد میکنم حتما ببینید:
https://laracasts.com/series/search-...ice/episodes/1
در مورد service provider:
http://laravel.com/docs/5.1/providers
در مورد service container:
http://laravel.com/docs/5.1/container
-------------
چون طولانی شد در مورد repository pattern ننوشتم توی پست بعد در صورت تمایل در مورد اون هم مینویسم:)
------------
متاسفانه این تگ PHP انجمن خیلی مشکل داره تمام کدهامو خراب کرد مجبور شدم با CODE بزارم!