При создании сайтов приходится некоторое время уделить форме ввода логина и пароля для доступа к панели управления. Для ускорения процесса разработки, хочу поделиться рецептом приготовления простой, удобной и элегантной формы входа в админку.
Статья содержит описание некоторых базовых приемов использования Laravel при разработки сайтов и будет полезна тем, кто начинает осваивать данный фреймворк. Для примера использую Ubuntu 12.04, PostgreSQL 9.3, Nginx 1.1.19, PHP 5.5.7, Composer и свежий проект, созданный с использованием Laravel 4.1. Под управление PostgreSQL крутится база данных examples, к которой имеет доступ пользователь examples c одноименным паролем. Nginx же настроен таким образом, что при обращении по адресу http://examples.loc
в браузере открывается главная страницу-заглушка, которая идет с Laravel в комплекте, с надписью «You have arrived.»
Все пути к редактируемым файлам указаны относительно директории проекта.
Окружение
Сначала настраиваю локальное окружение в Laravel. Для этого создаю директорию app/config/local
и добавляю в нее файл database.php
:
<?php
/**
* app/config/local/database.php
*/
return array(
'default' => 'pgsql',
'connections' => array(
'pgsql' => array(
'driver' => 'pgsql',
'host' => 'localhost',
'database' => 'examples',
'username' => 'examples',
'password' => 'examples',
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
),
),
);
Прошу Laravel использовать окружение local
по умолчанию. Для этого редактирую файл bootstrap/start.php и заменяю строку 'your-machine-name'
на '*'
:
// Фрагмент из bootstrap/start.php
$env = $app->detectEnvironment(array(
'local' => array('*'),
));
Подключение Sentry
Ссылку на инструкцию по подключению Sentry можно найти в конце статьи. Кратко, делаю следующее.
Добавляю в composer.json
строку "cartalyst/sentry": "2.0.*"
в блок require
.
// Фрагмент composer.json
{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"require": {
"laravel/framework": "4.1.*",
"cartalyst/sentry": "2.0.*"
},
...
Выполняю команду:
$ composer update
composer update
Добавляю в список сервис-провайдеров в файле app/config/app.php:
'CartalystSentrySentryServiceProvider',
Добавляю в список псевдонимов в файле app/config/app.php:
'Sentry' => 'CartalystSentryFacadesLaravelSentry',
Выполняю миграции Sentry:
$ php artisan migrate --package=cartalyst/sentry
Публикую конфигурационный файл Sentry:
$ php artisan config:publish cartalyst/sentry
Открываю на редактирование app/config/packages/cartalyst/sentry/config.php
. Нахожу в нем строку 'login_attribute' => 'email'
и заменяю на 'login_attribute' => 'username'
, для того чтобы Sentry выполнял аутентификацию пользователя по его логину, а не e-mail.
При выполнении миграций Sentry была создана таблица users
, в которой есть поле email
, но нет username
. Поэтому, чтобы Sentry работал, надо добавить недостающее поле. Для этого создаю миграцию:
$ php artisan migrate:make alter_users_add_username
В директории app/database/migration
появится файл имя, которого будет состоять из текущей даты и времени и заканчивается на alter_users_add_username.php
. Редактирую его следующим образом:
<?php
/**
* app/database/migration/0000_00_00_000000_alter_users_add_username.php
*/
use IlluminateDatabaseMigrationsMigration;
class AlterUsersAddUsername extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function($table)
{
$table->string('username');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('username');
});
}
}
Для проверки миграции выполняю:
$ php artisan migrate
Для проверки, что миграция успешно откатывается, выполняю:
$ php artisan migrate:rollback
Создаю еще одну миграцию, которая добавляет в таблицу users
запись о суперпользователе:
$ php artisan migrate:make add_user_admin
Нахожу в директории app/database/migrate
файл, который заканчивается на add_user_admin.php
и редактирую его:
<?php
/**
* app/database/migration/0000_00_00_000001_add_user_admin.php
*/
use IlluminateDatabaseMigrationsMigration;
class AddUserAdmin extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$user = Sentry::createUser(array(
'username' => 'admin',
'email' => 'admin@examples.loc',
'password' => 'password',
'activated' => 1,
'permissions' => array(
'superuser' => 1,
),
));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
User::where('username', '=', 'admin')->firstOrFail()->delete();
}
}
Проверяю, как миграции накатываются и откатываются. И перехожу к созданию контроллера, который будет отвечать за аутентификацию пользователей.
Форма входа
В директории app/controllers
создаю файл AuthController.php
:
<?php
/**
* app/controllers/AuthController.php
*/
class AuthController extends BaseController {
/**
* Отображает страницу входа
*
* @return IlluminateViewView
*/
public function getLogin()
{
$title = 'Вход';
return View::make('auth.login', compact('title'));
}
}
Метод getLogin()
передает заголовок страницы через переменную $title
в представление auth.login
, которое служит для отображения страницы входа в админку.
Создаю представление auth.login
. Для этого в директорию app/views
добавляю директорию auth
и создаю в ней файл login.blade.php
:
/**
* app/views/auth/login.blade.php
*/
@extends('layout')
@section('main')
<div class="container">
{{ Form::open(array('class' => 'form-signin')) }}
@if (!$errors->isEmpty())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
<h2 class="form-signin-heading">{{ $title }}</h2>
{{ Form::text('username', null, array('class' => 'form-control', 'placeholder' => 'Логин')) }}
{{ Form::password('password', array('class' => 'form-control', 'placeholde' => 'Пароль')) }}
<label class="checkbox">
{{ Form::checkbox('remember-me', 1) }} Запомни меня
</label>
{{ Form::submit('Войти', array('class' => 'btn btn-lg btn-primary btn-block')) }}
{{ Form::close() }}
</div>
@stop
Шаблон login.blade.php
расширяет шаблон layout.blade.php
. Поэтому создаю его в директории app/views
:
/**
* app/views/layout.blade.php
*/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ $title }}</title>
@section('styles')
{{ HTML::style('//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css') }}
{{ HTML::style(URL::asset('styles/base.css')) }}
@show
</head>
<body>
@yield('main')
@section('scripts')
{{ HTML::script('//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js') }}
@show
<body>
</html>
После того, как есть метод контроллера и возвращаемое им представление, необходимо настроить роутинг, чтобы GET запросы, отправляемые по адресу http://examples.loc/login
, обрабатывались методом getLogin()
. Для этого добавляю в файл app/routes.php
следующий код:
// Фрагмент app/routes.php
...
Route::group(array('before' => 'guest'), function ()
{
Route::get('login', array(
'as' => 'auth.login',
'uses' => 'AuthController@getLogin'
));
});
С помощью Route::get()
определяю роут с именем auth.login
, который направляет GET запросы по адресу /login
методу getLogin()
контроллера AuthController
.
Также поместил роут auth.login
в группы роутов. Перед любым роутом, входящим в данную группу будет выполнятся фильтр guest
. Данный фильтр означает, что обработка GET запросов по адресу /login
будет происходить только в том случает, если пользователя является гостем, т.е. не авторизован.
Перепишу фильтр guest
так, чтобы он использовал Sentry. Для этого в файле app/filters.php
изменю код фильтра guest
:
// Фрагмент app/filters.php
...
Route::filter('guest', function()
{
if (Sentry::check()) return Redirect::to('/');
});
...
Теперь можно посмотреть, как выглядит форма входа в браузере. Для этого перехожу по адресу http://examples.loc/login
и…
Вижу сообщение об ошибке Call to undefined method IlluminateCookieCookieJar::get()
.
Легким движением Google выясняю, что Sentry 2.0 совместим с Laravel 4.0, но не совместим 4.1. Хорошо, что уже зарелизился Sentry 2.1. Чтобы избавиться от ошибки, изменяю версию Sentry в composer.json
на 2.1:
// Фрагмент composer.json
{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"require": {
"laravel/framework": "4.1.*",
"cartalyst/sentry": "2.1.*"
},
...
Выполняю команду:
$ composer update
Еще раз пытаюсь открыть http://examples.loc/login
и вижу форму ввода логина и пароля. Для того чтобы форма выглядела элегантно, надо добавить немного CSS. Для этого в директории public
создаю директорию styles
и добавляю туда файл base.css
:
/**
* public/style/base.css
*/
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .form-signin-heading {
text-align: center;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
font-size: 16px;
height: auto;
padding: 10px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
Контролеры
Теперь форма отображается красиво, но при нажатии на кнопку «Войти», возвращается ошибка 404. Значит надо добавить в AuthController
обработчик POST запросов, отправляемых на адрес /login
.
<?php
/**
* app/controllers/AuthController.php
*/
class AuthController extends BaseController {
/**
* Отображает страницу входа
*
* @return IlluminateViewView
*/
public function getLogin()
{
$title = 'Вход';
return View::make('auth.login', compact('title'));
}
/**
* Аутентифицирует и редиректит в админку
*
* @return IlluminateHttpRedirectResponse
*/
public function postLogin()
{
Input::flash();
try {
$credentials = array(
'username' => Input::get('username'),
'password' => Input::get('password')
);
$user = Sentry::authenticate($credentials, Input::get('remember-me'));
} catch (Exception $e) {
return Redirect::to(route('auth.login'))
->withErrors(array($e->getMessage()));
}
return Redirect::intended(route('admin'));
}
/**
* Обрабатывает выход
*
* @return IlluminateHttpRedirectResponse
*/
public function getLogout()
{
Sentry::logout();
return Redirect::route('auth.login');
}
}
В методе postLogin()
данные, переданные через форму сохраняются в сессии с помощью Input::flash()
. В блоке try
Sentry пытается аутентифицировать пользователя. Если в форме ввода логина и пароля пользователь установит галочку «Запомни меня», то вторым параметром в Sentry::authenticate()
будет передано значение true
и Sentry запомнит пользователя в случае успешной аутентификации. Если аутентификация по какой-либо причине не прошла успешно, то в блоке catch
метод Redirect::to()
отправит нас на страницу ввода логина и пароля в месте с сообщением об ошибке. В случае успешной аутентификации метод Redirect::intended()
отправит пользователя на ту страницу, на которую он намеревался зайти, когда был перенаправлен на форму ввода логина и пароля. Если такая страница не задана, то откроется страница по адресу /admin
, с которой связан роут с именем admin
.
Метод getLogin()
самая проста реализация как можно разлогинить пользователя. После выхода пользователь будет перенаправление на страницу ввода логина и пароля.
Прежде чем переходить к настройке роутинга, надо добавить метод, который будет обрабатывать запросы, отправляемые по адресу /admin
. Для этого отредактирую файл app/controllers/HomeController.php
следующим образом:
<?php
/**
* app/controllers/HomeController.php
*/
class HomeController extends BaseController {
public function showWelcome()
{
return View::make('hello');
}
public function getAdmin()
{
return link_to(route('auth.logout'), 'Выход');
}
}
Метод getAdmin()
просто отображает на страницу ссылку «Выход», при нажатии на которою пользователь будет разлогиниваться.
Роутинг
Теперь, чтобы новые методы могли обрабатывать запрос, отредактирую в файл app/routes.php
:
/**
* app/routes.php
*/
Route::get('/', function()
{
return View::make('hello');
});
Route::group(array('before' => 'guest'), function ()
{
Route::get('login', array(
'as' => 'auth.login',
'uses' => 'AuthController@getLogin'
));
Route::post('login', array(
'before' => 'csrf',
'uses' => 'AuthController@postLogin'
));
});
Route::group(array('before' => 'auth'), function ()
{
Route::get('admin', array(
'as' => 'admin',
'uses' => 'HomeController@getAdmin',
));
Route::get('logout', array(
'as' => 'auth.logout',
'uses' => 'AuthController@getLogout'
));
});
Первая группа роутов, перед которой выполняется фильтр guest
, содержи роутинг для адреса /login
для GET и POST запросов. Фильтр guest
означает, что по адресу /login
будут обрабатываться запросы только от гостей, т.е. неавторизованных пользователей.
Вторая группа роутов, перед которой выполняется фильтр auth
, содержит роутинг для адресов /admin
и /logout
. Фильтр auth
означает, что по данным адресам будут обрабатываться запросы только от авторизованных пользователей.
Также необходимо отредактировать фильтр auth
, так чтобы он использовал Sentry. Для этого открываю файл app/filters.php
изменяю фильтр auth
следующим образом:
// Фрагмент app/filters.php
...
Route::filter('auth', function()
{
if (!Sentry::check()) return Redirect::guest(route('auth.login'));
});
...
Теперь можно пробовать логиниться в админку использую логин admin и пароль password. Неавторизованному пользователю не удастся зайти на страницу /admin
, она будет доступна, только после успешной аутентификации. Также после аутентификации будет невозможно зайти на страницу /login
, так как будет происходить редирект на главную страницу.
Надеюсь данный пример поможет начинающим разработчикам лучше понять некоторые элементы Laravel.
Ссылки по теме
getcomposer.org/doc/00-intro.md#installation-nix
laravel.com/docs/installation#install-laravel
cartalyst.com/manual/sentry/installation/laravel-4
Автор: bskton