Skip to content

Instantly share code, notes, and snippets.

@fomvasss
Last active August 13, 2019 18:59
Show Gist options
  • Save fomvasss/b450242d45dfd5e26e11dc7a320224f5 to your computer and use it in GitHub Desktop.
Save fomvasss/b450242d45dfd5e26e11dc7a320224f5 to your computer and use it in GitHub Desktop.
Service provider, container, package development in Laravel
// https://code.tutsplus.com/ru/tutorials/how-to-register-use-laravel-service-providers--cms-28966
<?php
/**
*
* TL;DR
* PACKAGES Laravel structure dir
*
**/
/vendor_name
/package_name
/config
/database
/migrations
/public
/css
/js
/src
/Console
/Controllers
/Facades
Courier.php
/Models
/Resources
/Lang
/Middleware
helpers.php
routes.php
ServiceProvider.php
/tests
/views
composer.json
phpinit.xml
license
readme.md
//Версии пакетов: http://www.ibm.com/developerworks/ru/library/wa-php-renewed_3/
//Семвер: http://semver.org/lang/ru/
/**
*
*COMPOSER && Arisan command
*
**/
composer init
composer create-project vendor/package -s dev
php artisan make:migration create_users_roles_table --path=packages/Bitw/Acl/database/migrations
php artisan make:provider AclServiceProvider
php artisan vendor:publish && php artisan migrate
composer require "barryvdh/laravel-debugbar" --dev
composer require "smarty/smarty" -vvv
composer remove "vendor/package"
/*(-o), чтобы конвертировать правила автозагрузки psr-0/4 в «карту классов», чтобы ускорить автозагрузку.*/
composer -optimize-autoloader
### Command in seeder
$this->call('UsersTableSeeder');
$this->command->info('Users table seeded!');
/**
*
* composer.json
*
* For using local package, you mast add next strind in Laravel root dir root_project/composer.json:
*
**/
"autoload": {
"classmap": [
"database",
"app/Models"
],
"psr-4": {
"App\\": "app/",
"Fomvasss\\Epochta\\": "packages/fomvasss/epochta-sms/src"
},
"files": [
"app/helpers.php"
]
#use local rapository.add section in composer.json:
"repositories": [
{
"type": "path",
"url": "../packages",
//"packagist.org": false
}
]
#or
"repositories":[
{
"type":"git",
"url":"http://github.com/pqr/superlogger"
}
]
/**
*
* ServiceProvider
*
**/
namespace Alireza\Authentication;
use Illuminate\Support\ServiceProvider;
class MyAuthServiceProvider extends ServiceProvider {
/**
* индикатор задержки загрузки провайдера.
*
* @var bool
*/
protected $defer = false;
/**
* Выполнение после-регистрационной загрузки сервисов (Загрузка сервисов после регистрации).
*
* @return void
*/
public function boot()
{
//routes
$this->loadRoutesFrom(__DIR__.'/path/to/routes.php');
//require __DIR__ . '/Http/routes.php';
//views
$this->loadViewsFrom(__DIR__.'/path/to/views', 'view-namespace');
//publish views
$this->publishes([__DIR__.'/path/to/views' => resource_path('views/vendor/courier'),], 'views');
//translations
$this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
//publish translations
$this->publishes([__DIR__.'/path/to/translations' => base_path('resources/lang/vendor/courier'),]);
//publish config
$configPath = __DIR__ . '/../config/image_manager.php';
$publishPath = config_path('image_manager.php');
$this->publishes([$configPath => $publishPath], 'config');
//Общие ресурсы
$this->publishes([__DIR__.'/path/to/assets' => public_path('vendor/courier'),], 'public');
//migrations
$this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
//publish migrations
$this->publishes([__DIR__.'/../database/migrations/' => database_path('migrations')], 'migrations');
//console commands
if ($this->app->runningInConsole()) {
$this->commands([
FooCommand::class,
BarCommand::class,
]);
}
//for Facade (чтобы не регистрировать алиас в app/ServiceProvider)
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Courier', 'Fomvasss\CourierManager\Facades\Courier');
//registration helper-functions
$this->loadHelpers();
//registration blade-directive
Blade::directive('youtube', function($expression){
return '<?php echo youtube_iframe(' . $expression . '); ?>';
});
}
#Если ваш пакет использует контроллеры, вам надо убедиться в том, что они правильно настроены в разделе автозагрузки файла composer.json (или в методе register?).
#Использование шаблонов блейд
return view('view-namespace::admin');
@include('view-namespace::link')
#Использование файлов переводов:
echo trans('courier::messages.welcome');
#Публикация "Общие ресурсы"
php artisan vendor:publish --tag=public --force
php artisan vendor:publish --provider="Fomvasss\Youtube\YoutubeServiceProvider" --tag="config" --force
#Если вы хотите быть уверены, что ваши ресурсы всегда в актуальном состоянии, добавте в composer.json:
"scripts": {
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall",
"php artisan optimize"
],
"post-update-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan vendor:publish --tag=public --force",
"php artisan optimize"
]
},
##Публикация папок с файламы
$this->publishes(
[
__DIR__ . '/../views/admin/' => resource_path('views/admin'),
__DIR__ . '/../views/auth/' => resource_path('views/_auth'),
__DIR__ . '/../public/admin-assets' => public_path('vendor/admin-assets'),
],
'admin-views'
);
##регистрация функций-хелперов
protected function loadHelpers()
{
foreach (glob(__DIR__.'/Helpers/*.php') as $filename) {
require_once $filename;
}
// if (file_exists($file = __DIR__.'/helpers.php'))
// {
// require_once $file;
// }
}
##Пример src/helpers.php . Функция youtube_iframe() с контейнера 'youtube' (с класса Youtube())
<?php
if (!function_exists('youtube_iframe')) {
function youtube_iframe(string $link, array $options = [])
{
return app('youtube')->iFrame($link, $options);
}
}
/**
* Регистрация привязок (ключей?) в контейнере.
*
* @return void
*/
public function register()
{
//Мы помещаем реализацию класса CourierReader в контейнер (Service Container) с ключом Courier
//чтобы зарегистрировать его в нашем приложении [1],[2]
$this->app->bind('courier', function($app){
return new CourierReader($app);
});
$this->app->bind('youtube', function($app){
return new Youtube();
});
// register our controller
$this->app->make('Fomvasss\Calculator\Controllers\CalculatorController');
//конфиги пакета по умолчанию (соединяются с опубликованнымы); courier - имя в контейнере!
$this->mergeConfigFrom(__DIR__.'/path/to/config/courier.php', 'courier');
}
/**
*
* или реализация в отдельных методах
*
**/
public function register()
{
$this->registerShop();
$this->registerCommands();
$this->mergeConfig();
}
private function registerShop()
{
$this->app->singleton('shop', function ($app) {
return new LaravelShop($app);
});
}
private function mergeConfig()
{
$this->mergeConfigFrom(
__DIR__ . '/Config/config.php', 'shop'
);
}
private function registerCommands()
{
$this->app->singleton('command.laravel-shop.migration', function ($app) {
return new MigrationCommand();
});
}
#1.Запрашивать объект из контейнера мы будем следующим образом
class PagesController extends BaseController {
protected $courier;
public function __construct(Courier $object)
{
$this->courier = $object;
}
#2. Обратите внимание, что замыкание единственным аргументом принимает объект класса Illuminate\Foundation\Application, который и является этим самым сервис-контейнером, и он (сервис-контенер) также доступен через алиас фасада App, хелпер app(), а также как $this->app во многих классах Laravel.
$this->app->bind - создается новый обьект
$this->app->singleton - объект класса будет создан однажды - при первом вызове, а все последующие обращения к тому же контейнеру будут возвращать созданный ранее объект
$this->app->instance - Связывание инстансов (экземпляров) ведет себя точно так же как и связывание одиночек, с той лишь разницей, что объект в контейнере создается не в момент первого вызова, а еще до помещения в контейнер.
#3. Зарегистрировать (bind, связать) класс можно двумя путями - 1)при помощи коллбэк-функции или 2)привязки интерфейса к реализации.
#1)
$this->app->bind('FooBar', function($app)
{
return new FooBar($app['SomethingElse']);
});
#2) Cвязывать интерфейсы с различными их реализациями:
#Сервис провайдер:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\PusherEventPusher');
App::bind( 'SessionStorage', 'MysqlSessionStorage' );
$this->app->bind(
'App\Contracts\Repositories\ClientRepositoryInterface',
'App\Repositories\ClientRepository'
);
App::singleton(SaveStr::class, function(){
return new SaveEloquent();
});
#Клас (напремер контроллер):
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
#Вы можете добавить в контейнер существующий экземпляр класса:
# 1 способ:
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
# 2 способ:
public function register()
{
$obj = new SaveEloquent();
$this->app->instance('App\Helpers\Contracts\SaveStr', $obj);
}
#4. Получение из контейнера. Есть несколько способов получить (resolve) содержимое контейнера.
#Во-первых, вы можете использовать метод make():
$fooBar = $this->app->make('FooBar');
#доступ к функции get(), класса через фасад:
$fooBar = $app->make('FooBar')->get($var);
#Во-вторых, вы можете обратиться к контейнеру как к массиву:
$fooBar = $this->app['FooBar'];
$str = $this->app['App\Helpers\Contracts\SaveStr'];
#И, наконец, в-третьих (и в главных) вы можете явно указать тип аргумента в конструкторе класса и фреймворк сам возьмёт его из контейнера:
public function __construct(UserRepository $users)
{
$this->users = $users;
}
#-------------------------
# Для получения доступа к сервисам можно как использовать внедрение зависимостей:
public function form(SaveStr $save){}
# так и получать нужные объекты прямо из сервис-контейнера, в котором они регистрировались:
App::make('App\Helpers\Contracts\SaveStr')
app()->make('App\Helpers\Contracts\SaveStr');
#------------------------
#5. Для того, чтобы сделать сервис-провайдер отложенным, установите свойство defer в true и определить метод provides(), чтобы фреймворк знал, какие классы биндятся (регистрируются в сервис-контейнере, «связываются») в вашем провайдере.
/**
* Получить сервисы от провайдера. (если, напремер обратимся к Riak\Contracts\Connection, то это сервис-провайдер будет загрузен)
*
* @return array
*/
public function provides()
{
return ['Riak\Contracts\Connection'];
//return ['shop', 'command.laravel-shop.migration'];
}
# Фасады
/**
*
*Facades /src/Facades/Courier.php
*
**/
<?php
namespace Fomvasss\CourierManager\Facades;
use Illuminate\Support\Facades\Facade;
class CourierManager extends Facade
{
protected static function getFacadeAccessor()
{
// Указываем зарегистрированный ключ из контейнера, который нам нужно получить
return 'courier';
}
}
## Наконец, по желанию можно добавить псевдоним (alias) для этого фасада в массив aliases файла настроек config/app.php- тогда мы сможем вызывать метод process на классе Payment.
/**
*
*composer.json for PACKAGE
*
**/
#Add more information in composer.json which are required for our package.
{
"name": "alireza/myauth",
"description": "How create your laravel 5 package",
"keywords": ["laravel", "debugbar", "profiler"],
"license": "MIT",
"homepage": "https://github.com/tymondesigns/jwt-auth",
"authors": [
{
"name": "Alireza Rahmani khalili",
"email": "[email protected]",
"homepage": "http://tymon.xyz",
"role": "Developer"
}
],
"minimum-stability": "dev",
"require": {
"php": "^5.5.9 || ^7.0",
"Illuminate/support": "5.1.* || 5.2.* || 5.3.*"
},
"autoload": {
"classmap": [],
"files": [
"app/helpers.php"],
"psr-4": {
"Alireza\\Authentication\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tymon\\JWTAuth\\Test\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"prefer-stable": true,
"minimum-stability": "dev"
}
"prefer-stable": true,
"minimum-stability": "dev",
"require": {
"aimeos/aimeos-laravel": "~2016.10",
...
},
"scripts": {
...
"post-update-cmd": [
"php artisan vendor:publish --tag=public --force",
"php artisan vendor:publish",
"php artisan migrate",
...
]
}
#App/Config
<?php
return [
/*
|--------------------------------------------------------------------------
| PackageName
|--------------------------------------------------------------------------
|
*/
'origin' => [
'make' => true,
'path' => '/uploads/img/',
],
'private_key' => env('PACKAGE_PRIVATE_KEY', 'wrewr3453tr4r33'),
];
/**
Версионирование
При указании допустимых версий пакетов можно использовать точное соответствие (1.2.3), диапазоны с операторами сравнения (<1.2.3), комбинации этих операторов (>1.2.3 <1.3), “последняя доступная” (1.2.*), символ тильды (~1.2.3) и знак вставки (^1.2.3)
x.y.z - МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ
1) МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения API.
2) МИНОРНУЮ версию, когда вы добавляете новый функционал, не нарушая обратной совместимости.
3) ПАТЧ-версию, когда вы делаете обратно совместимые исправления.
1.0.2 - точная версия
>=1.0.2
1.0 - 2.0 - набор версий (включно)
1.0.* - эквивалент >=1.0 <1.1
~1.2.3 - разрешает увелечение последней цифры (эквивалент >=1.2.3 <1.3.0)
^1.2.3 - эквивалент >1.2.3 <2.0
**/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment