Створення пакета для Laravel

Привіт Хабр!

Фреймворк Laravel швидко набирає популярність і вже знайшов велику армію фанатів. У цій статті я опишу розробку простого пакету для Laravel, а так само публікацію створеного нами пакету на сайті packagist.org для того, щоб додавати наш пакет у проект одним рядком у composer.json.

Отже, про що це

Пакети (packages) — основний спосіб додавання нового функціоналу в Laravel, так говорить нам офіційна документація. Ми створимо пакет спеціально для Laravel, це буде простий блог зі списком записів і сторінкою для перегляду статті. Ми не ставимо за мету написати чудовий, універсальний блог з купою можливостей для кастомізації, ми лише розглянемо всі тонкощі створення пакета, як незалежного розширення, яке допоможе в майбутньому уникнути дублювання коду в своїх проектах, при розв'язанні рутинних завдань.

В результаті ми отримаємо .



Чому ми навчимося

  • Як створювати пакет, що розширює функціональність проекту;
  • Познайомимося з базовою структурою всередині пакету;
  • Розберемося як додавати свої маршрути;
  • Дізнаємося де зберігати шаблони;
  • Покажемо composer які класи нам потрібно додати в автозавантаження;
  • Створимо міграції і заповнимо таблиці початковими даними;
  • Зробимо доступними зовнішні ресурси (css, js файли пакета.
<habracut/>

Необхідні інструменти

Я веду розробку під MacOS, використовую IDE PhpStorm, а сам сайт буде крутитися на офіційному Vagrant боксі — Homestead.
На хабре вже були корисні статті про те, як налаштувати Vagrant образ, а так само про те, як встановити сам Laravel.

Що ж в кінцевому підсумку буде представляти наш пакет

У нього буде два роута:
  • Сторінка зі списком посад;
  • Окрема сторінка посту.


Кілька шаблонів:
  • Загальний layout блогу;
  • Сторінка зі списком записів;
  • Короткий вид запису в списку постів;
  • Повний вид запису.


Одна модель Post з полями:
  • Заголовок поста
  • Унікальна посилання поста
  • Текст запису
  • Час створення
  • Час оновлення


А так само обчислюване поле cut, де з тексту запису беруться перші 120 символів.
На додаток до всього, ми створимо клас контролера, який буде групувати логіку відображення сторінок, додатковий файл укладачів, необхідних для прив'язки даних до наших шаблонів.
У висновку створимо файл міграції таблиці з постами, так само seed клас з деякою кількістю початкових даних.
Опублікуємо стилі пакету public-папці програми.

Приступаємо до роботи

Отже, Laravel встановлено, улюблена IDE відкрита і ми можемо починати.



Пропишемо у файлі app/config/workbench.php параметри name та e, в подальшому вони будуть використані composer.json для вашого пакета.
Прописавши ці параметри виконайте в консолі:

php artisan workbench vendor/package --resources

Де vendor ім'я постачальника послуг, package це ім'я створюваного вами пакету.

Наприклад мій логін на github — cherryoff, а створюваний пакет я хочу назвати nbblog, відповідно я повинен виконати команду:

php artisan workbench --resourses cherryoff/nbblog




Де прапор resourses каже що необхідно створити специфічні для Laravel папки: migrations, views, config і тд
Якщо все пройшло гладко, то в корені проекту ви побачите:



Щоб Laravel при запуску програми автоматично довантажував наш пакет нам необхідно в файл app/config/app.php в масив providers " додати рядок:

'Cherryoff\Nbblog\NbblogServiceProvider',


Імена класів постачальника послуг слідують схемою [Package]ServiceProvider, але перед цим вказується повний namespace класу

Структура пакета

У папці src ми можемо бачити знайому структуру папок, назви яких говорять самі за себе.
Зауважимо, що в папці src/Cherryoff/Nbblog/ зберігається клас нашого постачальника послуг, а так само туди варто класти всі допоміжні класи нашого пакету.
Подивимося на клас NbblogServiceProvider. Метод register буде викликаний, як тільки пакет був зареєстрований, а метод boot викликається щоразу перед обробкою запиту.

Зосередимося на створення блогу

Створимо в папці src, нашого пакету файл routes.php з двома роутами:

Route::get('/blog/', array(
'as' => 'posts_list',
'uses' => 'Cherryoff\Nbblog\NbblogController@showList'
));

Route::get('/blog/{link}', array(
'as' => 'post',
'uses' => 'Cherryoff\Nbblog\NbblogController@showPost'
))->where'link', '[A-Za-z-_]+');


Де в якості контролера вказуємо повний шлях до нашого ще не створеного контролера.
Далі в папці controllers створимо контролер NbblogController з наступним вмістом:


<?php namespace Cherryoff\Nbblog;

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL;

class NbblogController extends Controller {

public function showList()
{
return 'Posts list';
}

public function showPost($link)
{
return 'Get, post:'.$link;
}

}


Чудово, тепер у нас прописані шляхи та контролер повинен відповідати нам простими повідомленнями. Але якщо ми перейдемо по посиланню sandbox.local/blog/, то отримаємо виняток про те, що сторінка не знайдена. Вся справа в тому, що додаток не знає про те, що у нашого пакету є свої шляхи, і щоб виправити це підключимо файл routes.php в кінці методу boot класу NbblogServiceProvider:

include __DIR__.'/../../routes.php';


Але навіть після цього нічого не запрацює і ми спіймаємо помилку:

Class Cherryoff\Nbblog\NbblogController does not exist

Це ознака того, що composer в нашому пакеті нічого не знає про тільки що доданому контролері. Щоб показати coposer де шукати необхідні файли, додамо в composer.json (нашого пакета!!!) у секцію classmap рядок «src/controllers», після чого виконаємо
composer dump-autoload 

Тепер, якщо ми перейдемо за адресою:
sandbox.local/blog/
то побачимо рядок:
Post list
Ура! Це означає, що шляхи працюють, а наш контролер подцепился!
Далі, створюємо папку models файл Post.php в ній. Лістинг файлу представлений нижче:

<?php namespace Cherryoff\Nbblog;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Config;

/**
* Модель запису в блозі
*/

class Post extends Model {

protected $table = 'posts';
//Додаємо в видачу обчислюване поле
protected $appends = array('cut');
//Робимо поля доступними для автозаповнення
protected $fillable = array('header', 'link', 'article');

//Деякі правила валидиции
public static $rules = array(
'header' => 'required|max:256',
'link' => 'required|between:2,32|unique',
'article' => 'required'
);

public function getCutAttribute(){
return Str::limit($this->attributes['article'], 120);
}

}


Тепер необхідно додати папку models в секцію автозавантаження composer.json (потрібно додати вище рядки "src/controllers") нашого проекту і виконати composer dump-autoload так само, як ми це робили у випадку з контролером.

Докладніше про Eloquent моделях можна почитати на оф сайті Laravel

Рядком в консолі створимо міграцію для нашого пакету:

php artisan migrate:make create_nbblog_posts_table --bench="cherryoff/nbblog"


В папці src/migrations/ з'явився клас тільки що створеної міграції. У його методі up пропишемо:

Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('link', 32);
$table->string('header', 256);
$table->text('article');
$table->timestamps();
$table->softDeletes();
});


У методі down:

Schema::dropIfExists('posts');


Таким чином в методі up ми створюємо таблицю з необхідними полями для нашої моделі, а в методі down видаляємо її, якщо вона існує

Отже, прийшов час виконати нашу міграцію:

php artisan migrate --bench="cherryoff/nbblog"


Якщо ви правильно налаштували з'єднання з базою даних, то в консолі побачите щось подібне:

Migration table created successfully.
Migrated: 2014_10_23_115450_create_nbblog_posts_table


Тепер заповнимо тільки що створену таблицю початковими даними. Для цього в папці src створимо папку seeds з файлом NbblogSeeder.php з наступним вмістом:

NbblogSeeder.php
<?php namespace Cherryoff\Nbblog;

use Illuminate\Database\Seeder;

class NbblogSeeder extends Seeder {

public function run()
{
$posts = [
[
'header'=>'Header post number one',
'link'=>'one',
'article'=>'
In condimentum facilisis porta. Sed nec diam eu diam mattis viverra. Nulla fringilla, orci ac euismod semper, magna diam
porttitor mauris, quis sollicitudin sapien justo in libero. Vestibulum mollis mauris enim. Morbi euismod magna ac lorem
rutrum elementum. Donec viverra auctor lobortis. Pellentesque eu est a nulla placerat dignissim. Morbi a enim in magna
semper bibendum. Etiam scelerisque, nunc ac egestas consequat, odio nibh euismod nulla, eget auctor orci nibh vel nisi.
Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac
tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec
purus convallis consequat. Vivamus id mollis quam. Morbi ac commodo nulla. In condimentum orci id nisl volutpat bibendum.
Quisque commodo hendrerit lorem quis egestas. Maecenas quis tortor arcu. Vivamus rutrum nunc non neque consectetur quis placerat
neque lobortis. Nam vestibulum, arcu sodales feugiat consectetur, nisl orci bibendum elit, eu euismod magna sapien ut nibh.
Donec semper quam scelerisque tortor dictum gravida. In hac habitasse platea dictumst. Nam pulvinar, sed odio rhoncus suscipit,
sem diam ultrices mauris, eu consequat purus metus eu velit. Proin metus odio, aliquam eget molestie nec, gravida ut sapien.
Phasellus quis est sed turpis sollicitudin venenatis sed eu odio. Praesent eget neque eu eros interdum malesuada non vel leo.
Sed fringilla porta ligula egestas tincidunt. Nullam risus magna, ornare vitae varius eget, scelerisque.
',
],
[
'header'=>'Very important news',
'link'=>'news',
'article'=>'
Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu ullamcorper orci. Quisque eget odio ac
lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor.
Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus
ornare ante, ac egestas est urna sit amet arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos
himenaeos. Sed molestie augue sit amet leo consequat posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
posuere cubilia Curae; Proin vel ante a orci tempus eleifend ut et magna. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vivamus luctus urna sed urna ultricies ac tempor dui sagittis. In condimentum facilisis porta. Sed nec diam eu diam mattis
viverra. Nulla fringilla, orci ac euismod semper, magna diam porttitor mauris, quis sollicitudin sapien.
',
],
];

foreach ($posts as $post){
Post::create($post);
}
}

} 



Додамо папку seeds в секцію автозавантаження composer.json і знову виконаємо
composer dump-autoload

Тепер завантажимо початкові дані командою:

php artisan db:seed --class="\Cherryoff\Nbblog\NbblogSeeder"


Якщо ми все зробили правильно, то все завершилось без помилок.

Створимо шаблони видів для нашого блогу, розмістивши в папці src/views:

layout.blade.php
<!DOCTYPE html>
<html>
<head>
<link href='http://fonts.googleapis.com/css?family=Lora&subset=latin,cyrillic' rel='stylesheet' type='text/css'>
<title>
@yield('title')
</title>
</head>
<body>
<div class="content">
<header>
<h1>My simple blog</h1>
<small>Just blog package for Laravel</small>
</header>
<nav>
<ul>
<li><a href="/">Main page</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
</nav>
@yield('content')
</div>
</body>
</html>


list.blade.php

@section('title')
List
@stop

@section('content')
<small>Number of posts in the blog: {{$count}}</small>
<ul class="posts-list">
@forelse($posts as $post)
@include('nbblog::preview')
@empty
<li><h3>No records</h3></li>
@endforelse
</ul>
@stop



preview.blade.php
<li>
<span><small>{{$post->created_at}}</small></span>
<h2><a href="/blog/{{$post->link}}">{{$post->header}}</a></h2>
<p>{{$post->cut}}</p>
</li>


post.blade.php
@section('title')
{{$header}}
@stop

@section('content')
<div class="post-block">
<span><small>{{$created_at}}</small></span>
<h2>{{$header}}</h2>
<p>
{{$article}}
</p>
</div>
@stop


Хотілося б відзначити, що звертатися до видів пакету потрібно так:
ім'я_пакету:: шлях_до_файлу, що в нашому випадку:

nbblog::preview

Отже, з шаблонами закінчено, тепер можна приступити до наповнення їх даними. Для цього створимо файл viewComposers.php прямо в папці src. (Ми можемо створити цей файл у будь-якому місці нашого пакету, головне не забути його підключити).

viewComposers.php
<?php
/**
* Не забуваємо використовувати ім'я свого пакета перед назвою виду
*/
View::composer(array('nbblog::list', 'nbblog::post'), function($view){
$view->with('uri', 'blog');
});

View::composer('nbblog::list', function ($view) {
$view->with('count', \Cherryoff\Nbblog\Post::count())->with('posts', \Cherryoff\Nbblog\Post::all());
});



Ми тільки що прив'язали змінну uri до шаблону списку постів і шаблоном поста (надалі, коли ми будемо отримувати цю змінну з налаштувань, нам буде зручніше передавати її види в одному місці) і разом з шаблоном списку постів ми віддаємо відразу всі записи.

Тепер необхідно підключити файл в класі нашого постачальника послуг (src/Cherryoff/Nbblog/NbblogServiceProvider.php), так само, як ми це робили з файлом route.php.

/**
* Підключаємо власний viewComposers
*/
include __DIR__.'/../../viewComposers.php';


Міняємо клас нашого контролера, щоб він виглядав як то так:

class NbblogController extends Controller {

public function __construct(){
$this->layout = View::make('nbblog::layout');
}

public function showList()
{
$this->layout->content = View::make('nbblog::list');
}

public function showPost($link)
{
$post = Post::where'link', '=', $link)->firstOrFail();
$this->layout->content = View::make('nbblog::post', $post);
}
}



Все, тепер наш блог готовий. Можемо оцінити це за адресою: sandbox.local/blog/
Список записів виводиться, пости проглядаються.



Але виглядає він як то не дуже і нам хотілося б це виправити. Для цього в папці public нашого пакету створимо папку css і додамо туди файл main.css з наступним вмістом:

main.css
html, body {
font-family: 'Lora', "Times New Roman", serif;
padding: 0;
color: #383840;
background-color: #F2F2F2;
}

a {
color: #676673;
}

a:hover {
color: #383840;
}

span, small {
color: #B8B8BF;
}

.content {
width: 600px;
margin: 0 auto;
}

header {
text-align: center;
}

header h1 {
margin-bottom: 5px;
}

nav {
width: auto;
margin: 0 auto;
text-align: center;
}

nav ul {
padding: 0;
margin-top: 10px;
margin-bottom: 20px;
}

nav li {
list-style: none;
display: inline-block;
padding: 10px 5px;
margin: 0 20px;
}

.posts-list {
padding: 0;
}

.posts-list li {
list-style: none;
border-bottom: 1px dotted #B8B8BF;
padding-bottom: 10px;
margin-top: 20px;
margin-bottom: 60px;
}

.posts-list li > span, .post-block > span {
width: 100%;
text-align: center;
display: inline-block;
border-bottom: 1px dotted #B8B8BF;
height: 10px;
}

.posts-list li > span small, .post-block > span small {
background-color: #F2F2F2;
padding: 10px;
}

.posts-list h2 {
text-align: center;

}

.posts-list a {
text-decoration: none;
padding: 10px 20px;
}



Опублікуємо зовнішні ресурси нашого пакету командою:

php artisan asset:publish --bench="cherryoff/nbblog"


Можливо ми цього не помітили, але в папці public нашого додатка з'явився файл main.css, який розташувався в папці packages/cherryoff/nbblog/css/
Як ви встигли здогадатися, так laravel робить з усіма зовнішніми ресурсами пакетів. А значить, що це угода про імена зовнішніх ресурсів допоможе нам звернеться до цього файлу з нашого шаблону.

В шаблоні layout.blade.php перед тегом title вставити рядок:

{{ HTML::style('/packages/cherryoff/nbblog/css/main.css') }}


Це шлях до нашого файлу в папці public нашої програми.

Якщо ми оновимо сторінку, то побачимо найпростіший блог, який ми тільки що створили.



Зі стилями він став виглядати значно краще.
У вигляді домашнього завдання вам залишиться створити файл налаштувань з налаштуваннями url блогу та іменами шаблонів, що зробить можливим змінювати шлях до блогу на вашому сайті, а так само використовувати свої шаблони для виведення вмісту.

Тут ви можете подивитися проект на github.
Живе демо.
Або просто додати в залежність composer.json вашого Laravel-проекту рядок: «cherryoff/nbblog»: «dev-master»

Підсумок

Отже, ми навчилися створювати пакети для Laravel, побачили з чого складається пакет, тепер ніщо не становить праці форкнуть пакет стороннього розробника і адаптувати його під свої потреби. Ми побачили, як відбувається маршрутизація в пакеті, дізналися як показати composer де брати файли для автозапуску, дізналися як робляться міграції і сиди в рамках пакету, а так само опублікували свої ресурси в public папці нашої програми.

Стаття вийшла, трохи більше, ніж я планував, тому трохи про те, що я розповім в наступних статтях:
  • Покажу як винести установки пакета і надати можливість змінювати їх користувачеві не чіпаючи самі файли пакета;
  • Розгорнемо git репозиторій в папці пакета;
  • І запишемо все це справа на Github;
  • При бажанні опублікуємо тільки що створений пакет в packagist;
  • Включимо оновлення пакета в packagist при оновленні на github;
  • Накатим наш пакет на чистий Laravel.


Якщо і це буде цікаво, то далі ми розширимо функціональність нашого пакету:
  • Додамо до постів теги;
  • Додамо інтерфейс додавання/редагування/видалення постів;
  • Зробимо наш пакет більш кастомизируемым.


Сподіваюся не втомив, чистого коду!

Посилання, використовувані при написанні статті

Сайт Laravel;
Переклад офіційної документації;
Вводить у курс справи;
Розповідає про Homestead.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.