他の
PHP
フレームワークに精通している場合-難しいことではありませんが、そうでない場合-これは最初のフレームワークの優れた選択肢です。
記事は非常に大きいです。 週末に完全に読むことをお勧めします。
怠け者の場合:
Github
アプリ
設置
Laravelをインストールするには、 Composerが必要です
Composerは、PHP
依存関係管理ツールです。 プロジェクトに必要な依存ライブラリを宣言し、プロジェクトにインストールできます。
- 作曲家
環境は
*nix
環境にインストールされます(サイトにはWindowsにインストールするためのマニュアルがあり、さらにWAMPやGitなどのサーバーが必要です)。
非常にクリーンなOSがあるとします。 次に、ターミナルを開き、
# sudo apt-get update sudo apt-get install -y build-essential sudo apt-get install -y python-software-properties # php 5.5 sudo add-apt-repository ppa:ondrej/php5 sudo apt-get update # sudo apt-get install -y php5 sudo apt-get install -y apache2 sudo apt-get install -y libapache2-mod-php5 sudo apt-get install -y mysql-server sudo apt-get install -y php5-mysql sudo apt-get install -y php5-curl sudo apt-get install -y php5-gd sudo apt-get install -y php5-mcrypt sudo apt-get install -y git-core sudo apt-get install -y phpmyadmin # phpmyadmin echo "Include /etc/phpmyadmin/apache.conf" | sudo tee -a /etc/apache2/apache2.conf # mod_rewrite sudo a2enmod rewrite # apache sudo /etc/init.d/apache2 restart # Composer curl -sS https://getcomposer.org/installer | php sudo mv composer.phar /usr/local/bin/composer
しばらくすると、必要なツールがすべてインストールされます。
Laravel
のインストールに直接進みます。
# cd # /home/%user% mkdir workspace # workspace cd workspace # mkdir php # php cd php # php
habrフォルダーに
laravel
プロジェクトを作成します
composer create-project laravel/laravel habr --prefer-dist # .... ....
作成されたプロジェクトに移動し、
php artisan serve
コマンドを実行してすべてが機能することを確認しましょう
cd habr php artisan serve
ローカルサーバーはhttp:// localhost:8000で利用できます。
念のため、 職人はLaravel
いるコマンドラインスクリプトです。 開発で使用するための便利なコマンドが多数用意されています。Symfony
コンソールコンポーネントの上で実行されます。 ( Artisan CLI )。 コマンドラインでさまざまな便利なものを作成できる便利なコマンドがたくさんあります。 コマンドのリストをphp artisan list
は、コマンドラインでphp artisan list
と入力します。
アドレスhttp:// localhost:8000に移動すると、投稿の最初に美しいスクリーンセーバーが表示されます。
カスタマイズ
データベース(以降DBと呼びます)に接続するために、
Laravel
にはapp / config /フォルダーにある構成ファイルdatabase.phpがあります。
まず、
MySQL
データベースとユーザーを作成し
MySQL
mysql -u root -p # > CREATE DATABASE `habr` CHARACTER SET utf8 COLLATE utf8_general_ci; > CREATE USER 'habr'@'localhost' IDENTIFIED BY 'my_password'; > GRANT ALL PRIVILEGES ON habr.* TO 'habr'@'localhost'; > exit
いいね!
MySQL
にアクセスするためのすべてのデータがあり
MySQL
。パスワードmy_passwordを持つユーザーhabrと、 localhostホスト上のデータベースhabrです。 データベース構成ファイルに移動して、設定を変更しましょう。
Laravel
は、 移行とスキームビルダーという優れたツールがあります。
移行は、データベースのバージョン管理の一種です。 これにより、開発チームはデータベーススキーマを変更し、スキーマの現在の状態を常に更新できます。 通常、移行とスキーマビルダーを併用すると、データベーススキーマの管理が容易になります。
- 移行
Schema BuilderはSchemaクラスです。 データベース内のテーブルを操作することができます。Laravel
サポートするすべてのデータベースでうまく機能し、これらすべてのシステムに単一のAPI
を備えています。
- スキームビルダー
最初に、移行テーブルを作成します。
php artisan migrate:install
データベース接続設定が正しい場合、移行とテーブルを作成する準備ができています。
しかし、その前に、Webアプリケーションをより効率的かつ迅速に作成するために使用できる追加パッケージのインストールを紹介します。
Laravel 4ジェネレーター
ジェフリーウェイの超便利なツールジェネレーター 。 Github
次のような多くの便利なコマンドを職人リストに追加します。
- generate:model-モデルを作成します
- generate:controller-コントローラーを作成します
- generate:seed-データベースに設定/偽の情報を入力するためのファイルを作成します
- generate:view-テンプレートを作成します
- generate:移行-移行を作成します
- generate:resource-リソースを作成します
- generate:scaffold-プロトタイプを作成します(最も興味深いのは、後ほど詳しく調べます!)
- generate:form-フォームを作成します
- generate:test-テストを作成します
- generate:ピボット-ピボットテーブルの移行を作成します
パッケージのインストール
Composer
を使用したパッケージのインストールは非常に簡単です。 アプリケーションのルートにあるcomposer.jsonファイルを編集し、
"way/generators": "1.*"
を
"require"
リストに追加
"require"
ます。
"require": { "laravel/framework": "4.1.*", "way/generators": "1.*" },
その後、プロジェクトの依存関係を更新する必要があります。 ターミナルに入る
composer update
最後の仕上げは、 app / config / app.php構成ファイルのアプリケーションプロバイダーのリストへのエントリです。
'Way\Generators\GeneratorsServiceProvider'
これで、
php artisan
コマンドリストにも新しい
generate
コマンドが含ま
generate
ようになります。 次のセクションでは、
generate
を使用してアプリケーションを作成し、開発を高速化
generate
方法を示します。
アプリケーション作成
割引付きの
- メール、ユーザー名、パスワードを含むユーザーテーブル
- 役割表
- ユーザー役割表
- 都市の表
- 会社表
- タグ表
- フィールドのある割引テーブル:タイトル、説明、都市、会社、割引率、写真、割引の有効期限
- 評価付きの評価表
- 割引タグ表
データベースのテーブルスキーマの概要を説明します。 私はこのようなものを得ました:
このジェネレーター 'yに感謝します。 ちなみに、10行を登録するだけなので、次のとおりです。
php artisan generate:migration create_users_table --fields="email:string:unique, password:string[60], username:string:unique, remember_token:string:nullable" php artisan generate:scaffold role --fields="role:string:unique" php artisan generate:pivot users roles php artisan generate:scaffold city --fields="name:string:unique" php artisan generate:scaffold company --fields="title:string:unique" php artisan generate:scaffold tag --fields="title:string:unique" php artisan generate:scaffold offer --fields="title:string, description:text, city_id:integer:unsigned, company_id:integer:unsigned, off:integer:unsigned, image:string, expires:date" php artisan generate:scaffold comment --fields="body:text, user_id:integer:unsigned, offer_id:integer:unsigned, mark:integer" php artisan generate:pivot offers tags # php artisan migrate
最後のコマンドを使用すると、まだ記録されていないすべての移行がデータベースに入力されます。 重要なことは、すべての新しい移行が1つのスタックによって起動されることです。 マイグレーションをロールバックするために、
php artisan migrate:rollback
コマンドがあり、すべてのマイグレーションをゼロにロールバックするには、
migrate:reset
、ゼロにロールするには、すべての
migrate:refresh
マイグレーションを実行します。
4.1.25以降のLaravel
バージョンでは、盗まれたCookieで穴を塞ぐセキュリティアップデートがありました。 アップデートと手順の詳細については、 http : //laravel.com/docs/upgradeをご覧ください 。バージョンがLaravel
< 4.1.26の場合 。 または、 vlom88 http://habrahabr.ru/post/197454/#comment_7510479からコメントを読んで ください 。
ジェネレーターコマンドの詳細:
- generate:migration移行引数名を受け入れ、適切なスキーマを作成します。 スキーマ名にキーワードを指定できます。たとえば、 create -createの後に、テーブル名とテーブルキーワードが続きます 。 オプション--fields = ""を使用して、テーブルに追加するフィールドを指定することもできます。オプションでは、フィールドのデータタイプをコンマで区切ってリストします。 移行 、 データ型などを 作成する
- generate:scaffoldは引数としてリソース(例えばrole )を受け入れ、これらのファイルを作成します:
- app / models / Role.php-ロールテーブルを操作するためにEloquent ORMから継承されたモデルのクラス(テーブル自体の名前はリソース名の複数形です)
- app / controllers / RolesController.php-サイトへのリクエストに応答するコントローラークラスはRESTコントローラーでもあります
HTTPメソッド パス(URL) アクション ルート名 ゲット /リソース インデックス resource.index ゲット /リソース/作成 作成する resource.create 投稿 /リソース 店 resource.store ゲット /リソース/ {id} 見せる resource.show ゲット /リソース/ {id} /編集 編集する resource.edit PUT / PATCH /リソース/ {id} 更新する resource.update 削除 /リソース/ {id} 破壊する resource.destroy
- app / views / roles / index.blade.php-すべてのリソースのリストを担当するテンプレート(通常は
URL
/ rolesによるGET
リクエスト中に生成されURL
)、テンプレートエンジンについては後で説明します - app / views / roles / show.blade.php-特定のリソースの表示を担当するテンプレート(
GET
request forURL
/ roles / {id} ) - app / views / roles / create.blade.php-リソースを追加するためのフォームが配置されているテンプレート(
URL
GET
/ roles / create ) - app / views / roles / edit.blade.php-リソースを編集するためのフォームが配置されているテンプレート(
URL
/ roles / {id} / edit}でGET
) - app / views / layouts / scaffold.blade.php-アプリケーションのメインレイアウト(基本的なhtml +ブートストラップ+挿入されたコンテンツのコンテナを含む)
- アプリ/データベース/移行/ Create_roles_table.php-移行
- app / database / seeds / RolesTableSeeder.php-テーブルにデータを入力するテスト用ファイル
- app / tests / controllers / RolesTest.php-さまざまなテスト
データを更新してファイルに追加します
- app / database / seeds / DatabaseSeeder.php -RolesTableSeederへの呼び出しを追加します
- app / routes.php-すべてのリソースメソッド( REST )をルートレジスタに追加します
- generate:ピボット 2つの引数(テーブル名)を取ります。 2つの
foreign key
を含むピボットテーブルを作成します
このジェネレーターの使用例が、その使用方法とその有用性を明確に示していることを願っています。
まだ不足しているのは、テーブル間のリンクです。
知っておくことが重要です! テーブルの列に外部キーを追加する場合、列が署名されていないことを確認する必要があります。
さて、それらを追加します:
php artisan generate:migration add_foreign_user_id_and_offer_id_to_comments_table php artisan generate:migration add_foreign_city_id_and_company_id_to_offers_table
このような変更は自動的に作成されないため、移行ファイル自体にインデックスの追加を登録する必要があります。
... class AddForeignUserIdAndOfferIdToCommentsTable extends Migration { ... public function up() { Schema::table('comments', function(Blueprint $table) { $table->index('user_id'); $table->index('offer_id'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('offer_id')->references('id')->on('offers')->onDelete('cascade'); }); } ... public function down() { Schema::table('comments', function(Blueprint $table) { $table->dropForeign('comments_user_id_foreign'); $table->dropForeign('comments_offer_id_foreign'); $table->dropIndex('comments_user_id_index'); $table->dropIndex('comments_offer_id_index'); }); } } ... class AddForeignCityIdAndCompanyIdToOffersTable extends Migration { ... public function up() { Schema::table('offers', function(Blueprint $table) { $table->index('city_id'); $table->index('company_id'); $table->foreign('city_id')->references('id')->on('cities')->onDelete('cascade'); $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); }); } ... public function down() { Schema::table('offers', function(Blueprint $table) { $table->dropForeign('offers_city_id_foreign'); $table->dropForeign('offers_company_id_foreign'); $table->dropIndex('offers_city_id_index'); $table->dropIndex('offers_company_id_index'); }); } }
データベーススキーマを見ると、より良い状況がわかります。
現時点では、リソースへのリンクはすべて開いており、誰にでも渡すことができます。
adminロールを追加するとします。 リンクhttp:// localhost:8000 / rolesで 、次の図が表示されます。
LaravelのテンプレートとBladeテンプレートエンジンについて少し。
テンプレートファイルの場合、拡張子.blade.phpが使用されます。 ファイルアプリ/ビュー/レイアウト/ scaffold.blade.phpを見ると、
// app/views/layouts/scaffold.blade.php <!doctype html> <html> <head> <meta charset="utf-8"> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> <style> table form { margin-bottom: 0; } form ul { margin-left: 0; list-style: none; } .error { color: red; font-style: italic; } body { padding-top: 20px; } </style> </head> <body> <div class="container"> @if (Session::has('message')) <div class="flash alert"> <p>{{ Session::get('message') }}</p> </div> @endif @yield('main') </div> </body> </html>
ここで何が起こっていますか? ファイル自体はスケルトンであり、コンテンツまたは別のテンプレートを
main
セクションに追加することで拡張できるレイアウトです。 二重中括弧{{$ var}}は<?Php echo $ var;に類似しています。 ?> ここでSessionクラスは、何らかのメッセージを渡す場合にユーザーにメッセージを表示するために使用されます。 メッセージは一時的なものであり、ページが更新されると消えます。 新しく作成したテンプレートアプリ/ビュー/ロール/index.blade.phpを開くと
// app/views/roles/index.blade.php @extends('layouts.scaffold') @section('main') <h1>All Roles</h1> <p>{{ link_to_route('roles.create', 'Add new role') }}</p> @if ($roles->count()) <table class="table table-striped table-bordered"> <thead> <tr> <th>Role</th> </tr> </thead> <tbody> @foreach ($roles as $role) <tr> <td>{{{ $role->role }}}</td> <td>{{ link_to_route('roles.edit', 'Edit', array($role->id), array('class' => 'btn btn-info')) }}</td> <td> {{ Form::open(array('method' => 'DELETE', 'route' => array('roles.destroy', $role->id))) }} {{ Form::submit('Delete', array('class' => 'btn btn-danger')) }} {{ Form::close() }} </td> </tr> @endforeach </tbody> </table> @else There are no roles @endif @stop
このテンプレートがapp / views / layouts / scaffold.blade.phpテンプレートを
@extends('layouts.scaffold')
、
@extends('layouts.scaffold')
コードがこのことを示していることが明らかになります 。 ここでは、 /を使用することもできますが、フォルダーを区切るためにドットが使用されていることに注意してください。
その後、
@stop
が最初に現れるまで、すべてが
main
セクションに書き込まれます。 また、おなじみの
if - else - endif
および
foreach - endforeach
、ヘルパー関数
link_to_route
、Laravel(ヘルパー関数)および
Form
クラスが
Form
作成に提供するものを使用します(少なくともForm :: open()を使用する必要があります) _token形式の追加属性の作成方法-PUT / PATCHまたはDELETEの場合のクロスサイトリクエストフォージェリおよび_methodに対する保護)。
まず、すべてのリソースを保護することについて考えてみましょう。 これを行うには、承認を入力する必要があります。
app / controllersフォルダーに新しい
LoginContoller
を作成します
php artisan generate:controller LoginController
そして、いくつかのテンプレートを追加します
mkdir app/views/login php artisan generate:view index --path="app/views/login" php artisan generate:view register --path="app/views/login" php artisan generate:view dashboard --path="app/views/login"
それでは、コントローラー自体を変更しましょう。 5つのメソッドが必要です。
- index-ログインフォームの生成を担当
- 登録-登録フォームの生成を担当します
- store-新規ユーザーの登録を担当します
- login-ユーザーのログインを担当します
- ログアウト-ユーザーのログアウトを担当
変更されたLoginControllerは次のようになります。
// app/controllers/LoginController.php class LoginController extends BaseController { /** * Login Form. * * @return Response */ public function index() { return View::make('login.index'); } /** * Registration form. * * @return Response */ public function register() { return View::make('login.register'); } /** * Registring new user and storing him to DB. * * @return Response */ public function store() { $rules = array( 'email' => 'required|email|unique:users,email', 'password' => 'required|alpha_num|between:4,50', 'username' => 'required|alpha_num|between:2,20|unique:users,username' ); $validator = Validator::make(Input::all(), $rules); if($validator->fails()){ return Redirect::back()->withInput()->withErrors($validator); } $user = new User; $user->email = Input::get('email'); $user->username = Input::get('username'); $user->password = Hash::make(Input::get('password')); $user->save(); Auth::loginUsingId($user->id); return Redirect::home()->with('message', 'Thank you for registration, now you can comment on offers!'); } /** * Log in to site. * * @return Response */ public function login() { if (Auth::attempt(array('email' => Input::get('email'), 'password' => Input::get('password')), true) || Auth::attempt(array('username' => Input::get('email'), 'password' => Input::get('password')), true)) { return Redirect::intended('dashboard'); } return Redirect::back()->withInput(Input::except('password'))->with('message', 'Wrong creadentials!'); } /** * Log out from site. * * @return Response */ public function logout() { Auth::logout(); return Redirect::home()->with('message', 'See you again!'); } }
最初の2つのメソッドは、HTMLテンプレートから生成されます。
store
メソッドは新しいユーザーをデータベースに保存し、
POST
介して
Input::all()
れる
Input::all()
からのすべてのデータを受け入れます。 ( 詳細 )。
Input
クラスには、POST要求中に送信されたデータが含まれます。
all()
、
get()
、
has()
およびその他( Basic Input )などの静的メソッドが多数あります。
Hash
は、データベース内のパスワードが暗号化された形式で保存されるように、 bcryptメソッドを使用する暗号化クラスです( Laravel Security )。
ただし、登録する前に、受信データを検証する必要があります。
このため、
Laravel
にはValidatorクラスがあります。
Validation::make
メソッドは、2つまたは3つの引数を取ります。
-
$input
必須、チェックする入力の配列 -
$rules
-必須、受信データのルールを含む配列 -
$messages
オプション、エラーメッセージを含む配列
利用可能なルールの完全なリストは、利用可能な検証ルールにあります。
fails()
メソッドは、
make
メソッドに渡したルールに従ってデータが検証されたかどうかに応じて、 trueまたはfalseを返します 。
Redirectクラスは、リダイレクトに使用されます。 彼の方法:
- 戻る()-要求の送信元のページにリダイレクトします
- 意図した(「フォールバック」)-ユーザーが認証フィルターの下にあるページにリダイレクトし、存在しない場合は、
fallback
に渡されたURLに送信します - withInput()-入力から一時セッションにデータを転送します
- withErrors($バリデーター)-
$validator
$ errors$validator
データを転送し$validator
(! $ errors変数はGETリクエストを含むすべてのページで作成されるため、常にすべてのページで利用可能です)。 - with( 'variable'、 'Your message here')-指定したメッセージとともに変数 'variable'を一時セッションに渡します
Auth
クラスは承認クラスであり、データベースから指定された識別子( Authenticating Users )によってユーザーを承認する
loginUsingId($id)
など、いくつかのメソッドがあります。 登録後、ユーザーを自動的に承認するため、使用します。
Controller
login()
のメソッドは、ユーザーを
email
または
username
で認証し、認証フィルターの下にあるページにリダイレクトします。 データが一致しない場合、受信データ、エラーメッセージ、パスワードなしでリダイレクトします。
そのため、承認を担当するコントローラーがあります。
すべてのリソースをアクセスから隠すための次のステップは、アプリケーションのルートを含むapp / routes.phpファイルを変更することです 。
// app/routes.php ... Route::get('/', array('as' => 'home', function() { return View::make('hello'); })); Route::get('logout', array('as' => 'login.logout', 'uses' => 'LoginController@logout')); Route::group(array('before' => 'un_auth'), function() { Route::get('login', array('as' => 'login.index', 'uses' => 'LoginController@index')); Route::get('register', array('as' => 'login.register', 'uses' => 'LoginController@register')); Route::post('login', array('uses' => 'LoginController@login')); Route::post('register', array('uses' => 'LoginController@store')); }); Route::group(array('before' => 'admin.auth'), function() { Route::get('dashboard', function() { return View::make('login.dashboard'); }); Route::resource('roles', 'RolesController'); Route::resource('cities', 'CitiesController'); Route::resource('companies', 'CompaniesController'); Route::resource('tags', 'TagsController'); Route::resource('offers', 'OffersController'); Route::resource('comments', 'CommentsController'); }); Route::filter('admin.auth', function() { if (Auth::guest()) { return Redirect::to('login'); } }); Route::filter('un_auth', function() { if (!Auth::guest()) { Auth::logout(); } });
/ロールなどのリンクをたどると、 / loginページにリダイレクトされます
"index.blade.php"
ページには、これまで標準テキスト
"index.blade.php"
のみが表示されます。
Route::group(array('before' => 'admin.auth'))
で囲まれたすべてのルートに対して、 admin.authフィルターが適用され、ユーザーがゲストであるかどうか、および-ログインページに送信します。 ここでフィルタについて、そしてルートのグループ化についてここで読むことができます 。 別のフィルター
Route::group(array('before' => 'un_auth'))
は、ユーザーがサイトにログオンしているかどうかを確認し、検証が完了したらログオフします。
通常の操作では、ログインファイルと登録ファイルを変更します。
// app/views/login/index.blade.php @extends('layouts.scaffold') @section('main') <h1>Login</h1> <p>{{ link_to_route('login.register', 'Register') }}</p> {{ Form::open(array('route' => 'login.index')) }} <ul> <li> {{ Form::label('email', 'Email or Username:') }} {{ Form::text('email') }} </li> <li> {{ Form::label('password', 'Password:') }} {{ Form::password('password') }} </li> <li> {{ Form::submit('Submit', array('class' => 'btn btn-info')) }} </li> </ul> {{ Form::close() }} @include('partials.errors', $errors) @stop // app/views/login/register.blade.php @extends('layouts.scaffold') @section('main') <h1>Register</h1> <p>{{ link_to_route('login.index', 'Login') }}</p> {{ Form::open(array('route' => 'login.register')) }} <ul> <li> {{ Form::label('email', 'Email:') }} {{ Form::text('email') }} </li> <li> {{ Form::label('username', 'Username:') }} {{ Form::text('username') }} </li> <li> {{ Form::label('password', 'Password:') }} {{ Form::password('password') }} </li> <li> {{ Form::submit('Submit', array('class' => 'btn btn-info')) }} </li> </ul> {{ Form::close() }} @include('partials.errors', $errors) @stop // app/views/login/dashboard.blade.php @extends('layouts.scaffold') @section('main') <h1>Administrative Dashboard</h1> <p>Nice to see you, <b>{{{ Auth::user()->username }}}</b></p> @stop // app/views/partials/errors.blade.php @if ($errors->any()) <ul> {{ implode('', $errors->all('<li class="error">:message</li>')) }} </ul> @endif
お気づきのように、ここでは
@include('view', $variable)
テンプレート
@include('view', $variable)
新しいトリックを使用しました。 アプリケーションでは、非常に簡単です-2つの引数を渡します。
- view-特定のテンプレートに含まれるテンプレート
- $ variable-テンプレートをレンダリングするために渡す必要がある変数
サイトに登録して、サイトにアクセスします。
さて、今あなたはリソースを行うことができます。都市から始めましょう。まず、Modelの
City
検証ルールを変更します:
// app/models/City.php class City extends Eloquent { protected $guarded = array(); public static $rules = array( 'name' => 'required|alpha|min:2|max:200|unique:cities,name' ); }
それは同様にしてモデル検証ルールを変更した後だ
Company
、
Role
と
Tag
:
// app/models/Company.php ... public static $rules = array( 'name' => 'required|alpha|min:2|max:200|unique:companies,name' ); ... // app/models/Role.php ... public static $rules = array( 'role' => 'required|alpha|min:2|max:200|unique:roles,role' ); ... // app/models/Tag.php ... public static $rules = array( 'name' => 'required|min:2|max:200|unique:tags,name' ); ...
リンクの切り替えの利便性のために、app / views / layouts / scaffold.blade.phpにメニューを追加し、将来のニーズに合わせてjQueryとjQuery-UIを追加します
// app/views/layouts/scaffold.blade.php <!doctype html> <html> <head> <meta charset="utf-8"> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> <link href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" rel="stylesheet"> <style> table form { margin-bottom: 0; } form ul { margin-left: 0; list-style: none; } .error { color: red; font-style: italic; } body { padding-top: 20px; } input, textarea, .uneditable-input {width: 50%; min-width: 200px;} </style> @yield('styles') </head> <body> <div class="container"> <ul class="nav nav-pills"> <li>{{ link_to_route('offers.index', 'Offers') }}</li> <li>{{ link_to_route('tags.index', 'Tags') }}</li> <li>{{ link_to_route('roles.index', 'Roles') }}</li> <li>{{ link_to_route('cities.index', 'Cities') }}</li> <li>{{ link_to_route('comments.index', 'Comments') }}</li> <li>{{ link_to_route('companies.index', 'Companies') }}</li> <li class="pull-right">{{ link_to_route('login.logout', 'Logout') }}</li> </ul> @if (Session::has('message')) <div class="flash alert"> <p>{{ Session::get('message') }}</p> </div> @endif @yield('main') </div> <script type="text/javascript" src="//code.jquery.com/jquery.min.js"></script> <script type="text/javascript" src="//code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script> @yield('scripts') </body> </html>
次に、モデルの検証ルールを編集します
Offer
。
// app/models/Offer.php ... public static $rules = array( 'title' => 'required|between:5,200', 'description' => 'required|min:10', 'city_id' => 'required|exists:cities,id', 'company_id' => 'required|exists:companies,id', 'off' => 'required|numeric|min:1|max:100', 'image' => 'required|regex:/\/images\/\d{4}\/\d{2}\/\d{2}\/([A-z0-9]){30}\.jpg/', // matches /images/2012/12/21/ThisIsTheEndOfTheWorldMaya2112.jpg 'expires' => 'required|date' );
ここでは、写真をアップロード
image
する手段
AJAX
を使用し、サーバー上の写真へのパスのみを検証自体に転送する必要があるため、フィールドに複雑なパターンを使用しました。それでは、テンプレートアプリ/ views / offers / create.blade.phpを変更し、スクリプト用の別のファイルを作成することから始めましょう。
// app/views/offers/create.blade.php ... {{ Form::label('file', 'Image:') }} {{ Form::file('file')}} <img src="" id="thumb" style="max-width:300px; max-height: 200px; display: block;"> {{ Form::hidden('image') }} <div class="error"></div> ... @section('scripts') @include('offers.scripts') @stop // app/views/offers/scripts.blade.php <script> $(document).ready(function(){ // $('#expires').datepicker({dateFormat: "yy-mm-dd"}); var uploadInput = $('#file'), // imageInput = $('[name="image"]'), // URL thumb = document.getElementById('thumb'), // error = $('div.error'); // uploadInput.on('change', function(){ // FormData var data = new FormData(); // data.append('file', uploadInput[0].files[0]); // $.ajax({ // URL url: '/upload', // type: 'POST', // data: data, // jQuery processData: false, // jQuery contentType: false, // dataType: 'json', // success: function(result) { // ( result) // filelink if (result.filelink) { // URL thumb.setAttribute('src', result.filelink); // input' imageInput.val(result.filelink); // error.hide(); } else { // error.text(result.message); error.show(); } }, // - error: function (result) { // error.text("Upload impossible"); error.show(); } }); }); }); </script>
ここでは、押して画像を追加する
input[name="file"]
と、使用してそれを送信する
AJAX
には
URL
/アップロード。このURLからの応答は、ダウンロードした画像へのリンクになります。このリンクを#thumb画像のsrc属性に挿入し、非表示の入力に保存します
image
。次に、app / routes.phpファイルにmarshout を追加する必要があります
upload
。
// app/routes.php ... Route::group(array('before' => 'admin.auth'), function(){ ... Route::resource('comments', 'CommentsController'); Route::post('upload', array('uses' => 'HomeController@uploadOfferImage')); } ...
さて、URLを登録しました
HomeController
。ロジックをに登録するのは残ります。これを行うには、app / controllers / HomeController.phpファイルに
uploadOfferImage
min メソッドを追加します:
// app/controllers/HomeController.php class HomeController extends BaseController { ... public function uploadOfferImage() { $rules = array('file' => 'mimes:jpeg,png'); $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { return Response::json(array('message' => $validator->messages()->first('file'))); } $dir = '/images'.date('/Y/m/d/'); do { $filename = str_random(30).'.jpg'; } while (File::exists(public_path().$dir.$filename)); Input::file('file')->move(public_path().$dir, $filename); return Response::json(array('filelink' => $dir.$filename)); } }
ルール、検証、エラー、回答など、すべてが非常に簡単です。最初に保存するために、保存するフォルダを設定します-これはpublic_path()/ images /現在の年/月/日付/(
public_path()
これは
Laravel
パブリックファイルへのパスの補助関数です)、そして
str_random(30)
30文字のランダムなファイル名を作成し、拡張子
jpg
。その後、クラス
Input
とそのメソッドを使用し
file('file')->move('destination_path', 'filename')
ます。「file」は着信ファイル、「destination_path」はファイルの移動先フォルダー、「filename」は保存するファイルの名前です。
Response::json
形式で答えを与えるでしょう
json
。
いいね!ファイルはで読み込まれ
AJAX
ます。
次のステップは変更になる
Form::input('number', 'city_id')
と、
Form::input('number', 'company_id')
実際のデータとSELECTAで。
// app/views/offers/create.blade.php ... <?php $cities = array(0 => 'Choose city'); foreach (City::get(array('id', 'name')) as $city) { $cities[$city->id] = $city->name; } ?> <li> {{ Form::label('city_id', 'City_id:') }} {{ Form::select('city_id', $cities) }} </li> <?php $companies = array(0 => 'Choose company'); foreach (Company::get(array('id', 'name')) as $company) { $companies[$company->id] = $company->name; } ?> <li> {{ Form::label('company_id', 'Company_id:') }} {{ Form::select('company_id', $companies) }} </li> ...
選択がどのように機能するかは、フォームとHTML(ドロップダウンリスト)で確認できます。したがって、データベース内の既存の都市や企業から選択できます。
まだ不足しているのは、割引にタグを追加することです。ここで、オートコンプリートを備えたjquery-uiは、複数の値を追加するのに役立ちます。これを行うには、スクリプトapp / views / offers / create.blade.phpでファイルを展開します:
// app/views/offers/scripts.blade.php <script> $(document).ready(function(){ ... function split( val ) { return val.split( /,\s*/ ); } function extractLast( term ) { return split( term ).pop(); } $( "#tags" ) // don't navigate away from the field on tab when selecting an item .bind( "keydown", function( event ) { if ( event.keyCode === $.ui.keyCode.TAB && $( this ).data( "ui-autocomplete" ).menu.active ) { event.preventDefault(); } }) .autocomplete({ source: function( request, response ) { $.getJSON( "/tags", { term: extractLast( request.term ), }, function( data ) { response($.map(data, function(item) { return { value: item.name } })) } ); }, search: function() { // custom minLength var term = extractLast( this.value ); if ( term.length < 2 ) { return false; } }, focus: function() { // prevent value inserted on focus return false; }, select: function( event, ui ) { console.log(ui); console.log(this); var terms = split( this.value ); // remove the current input terms.pop(); // add the selected item terms.push( ui.item.value ); // add placeholder to get the comma-and-space at the end terms.push( "" ); this.value = terms.join( ", " ); return false; } }); }); </script>
これはjqueryui.comの標準的な使用例であり、サーバーからの応答ポイントでわずかに変更されています。ご覧のとおり、アドレスはに移動し
/tags
ます。私たちはこれの
AJAX
要求への応答の論理を組織し
URL
ます。
// app/controllers/TagController.php class TagsController extends BaseController { ... /** * Display a listing of the resource. * * @return Response */ public function index() { $tags = $this->tag->all(); // AJAX if (Request::ajax()) { // , $tags = Tag::where('name', 'like', '%'.Input::get('term', '').'%')->get(array('name')); // json return $tags; } return View::make('tags.index', compact('tags')); } ...
おもしろいのは、返されると
Eloquent
フォーマットに変換されるため、
json
使用する必要がないこと
Response::json()
です。そして、ここにオートコンプリートタグがあります。
最後に行う必要があるのは、割引を作成するためのロジックを変更することです。
// app/controllers/OffersController.php class OffersController extends BaseController { ... /** * Store a newly created resource in storage. * * @return Response */ public function store() { $rules = Offer::$rules; $rules['expires'] .= '|after:'.date('Ym-d', strtotime('+1 day')).'|before:'.date('Ym-d', strtotime('+1 month')); $validation = Validator::make(Input::all(), $rules); if ($validation->passes()) { $tags = array(); foreach (explode(', ', Input::get('tags')) as $tag_name) { if ($tag = Tag::where('name', '=', $tag_name)->first()) { $tags[] = $tag->id; } } if (count($tags) == 0) { return Redirect::route('offers.create') ->withInput() ->with('message', 'Insert at least one tag.'); } $offer = $this->offer->create(Input::except('tags', 'file')); $offer->tags()->sync($tags); return Redirect::route('offers.index'); } return Redirect::route('offers.create') ->withInput() ->withErrors($validation) ->with('message', 'There were validation errors.'); } ...
最初に、割引が明日より早くなく、1か月後に遅く終了するように、期限切れルールを拡張します。次に、
id
別の配列内のすべてのタグを選択し、データベース内のタグの存在を確認します。次に、タグが入力されているかどうかを確認する小さなチェックがあります。そして最後に、非常に興味深いトリック:Eloquentでは、テーブルをリンクするために異なるリレーション(Eloquent Relationships)を使用できます。たとえば、Offersモデルには多くのタグを含めることができ、それに応じてModelに記述します
// app/models/Offer.php ... public function tags() { return $this->belongsToMany('Tag'); } ...
したがって、offersテーブルの1つのレコードとtagsテーブルの多くのレコードの間に関係を作成しました。メソッドを参照する
$offer->tags()
と、特定の割引に関連付けられているすべてのタグを取得できます。ただし、この例では、中間テーブルを操作するための特別なメソッドを使用してい
sync(array(1, 2, 3))
ます。これは、中間テーブルの
offer_id
必要なテーブルに書き込みます
tag_id
。表offer_tag:
我々はまた、テーブル内のレコード間のリンクを指定する必要が申し出テーブルでレコードの都市と企業:
// app/models/Offer.php ... public function city() { return $this->belongsTo('City'); } public function company() { return $this->belongsTo('Company'); } public function tags() { return $this->belongsToMany('Tag'); } // + public function webDescription($options = array()) { $str = $this->description; if (isset($options['shorten'])) { $length = isset($options['length']) ? (int) $options['length'] : 250; $end = isset($options['end']) ? : '…'; if (mb_strlen($str) > $length) { $str = mb_substr(trim($str), 0, $length); $str = mb_substr($str, 0, mb_strlen($str) - mb_strpos(strrev($str), ' ')); $str = trim($str.$end); } } $str = str_replace("\r\n", '<br>', e($str)); return $str; } }
ファイルアプリ/ビュー/オファー/index.blade.phpを変更するために残っています
// app/views/offers/index.blade.php @if ($offers->count()) <table class="table table-striped table-bordered"> <thead> <tr> <th>Title</th> <th>Description</th> <th>City</th> <th>Company</th> <th>Off</th> <th>Image</th> <th>Tags</th> <th>Expires</th> </tr> </thead> <tbody> @foreach ($offers as $offer) <tr> <td>{{{ $offer->title }}}</td> <td>{{ $offer->webDescription(array('shorten' => true, 'length' => 60)) }}</td> <td>{{{ $offer->city->name }}}</td> <td>{{{ $offer->company->name }}}</td> <td>{{{ $offer->off }}}</td> <td><img src="" style="max-width: 200px; max-height:150px;"></td> <td> @foreach($offer->tags as $tag) <span class="badge">{{{$tag->name}}}</span> @endforeach </td> <td>{{{ $offer->expires }}}</td> <td> {{ link_to_route('offers.edit', 'Edit', array($offer->id), array('class' => 'btn btn-info')) }} </td> <td> {{ Form::open(array('method' => 'DELETE', 'route' => array('offers.destroy', $offer->id))) }} {{ Form::submit('Delete', array('class' => 'btn btn-danger')) }} {{ Form::close() }} </td> </tr> @endforeach </tbody> </table> @else There are no offers @endif
そして、割引の構造を完全に反映した素晴らしい写真を見ることができます。
{{{ $string }}} $string ,htmlentities
, , XSS.<?php echo htmlentities($string); ?>
Laravel
e($string)
app/views/offers/edit.blade.php , app/views/offers/show.blade.php
update
app/controllers/OfferController.php .
app/views/edit.blade.php
// app/views/offers/edit.blade.php @extends('layouts.scaffold') @section('main') <h1>Edit Offer</h1> {{ Form::model($offer, array('method' => 'PATCH', 'route' => array('offers.update', $offer->id))) }} <ul> <li> {{ Form::label('title', 'Title:') }} {{ Form::text('title') }} </li> <li> {{ Form::label('description', 'Description:') }} {{ Form::textarea('description') }} </li> <?php $cities = array(0 => 'Choose city'); foreach (City::get(array('id', 'name')) as $city) { $cities[$city->id] = $city->name; } ?> <li> {{ Form::label('city_id', 'City_id:') }} {{ Form::select('city_id', $cities) }} </li> <?php $companies = array(0 => 'Choose company'); foreach (Company::get(array('id', 'name')) as $company) { $companies[$company->id] = $company->name; } ?> <li> {{ Form::label('company_id', 'Company_id:') }} {{ Form::select('company_id', $companies) }} </li> <li> {{ Form::label('off', 'Off:') }} {{ Form::input('number', 'off') }} </li> <li> {{ Form::label('file', 'Image:') }} {{ Form::file('file')}} <img src="" id="thumb" style="max-width:300px; max-height: 200px; display:block; "> {{ Form::hidden('image') }} <div class="error"></div> </li> <li> {{ Form::label('expires', 'Expires:') }} {{ Form::text('expires') }} </li> <li> {{ Form::label('tags', 'Tags:') }} {{ Form::text('tags', Input::old('tags', implode(', ', array_fetch($offer->tags()->get(array('name'))->toArray(), 'name')))) }} </li> <li> {{ Form::submit('Update', array('class' => 'btn btn-info')) }} {{ link_to_route('offers.show', 'Cancel', $offer->id, array('class' => 'btn')) }} </li> </ul> {{ Form::close() }} @if ($errors->any()) <ul> {{ implode('', $errors->all('<li class="error">:message</li>')) }} </ul> @endif @stop @section('scripts') @include('offers.scripts') @stop
app/views/offers/create.blade.php ,
{{ Form::text('tags', ... }}
. : — , —
image
.
Form::text('tags', ... )
, , ,
$offer->tags()
name
.
Laravel
array_fetch , , , .
update
OfferController
:
// app/controllers/OfferController.php class OffersController extends BaseController { ... public function update($id) { $offer = $this->offer->findOrFail($id); $rules = Offer::$rules; $rules['expires'] .= '|after:'.date('Ym-d', strtotime('+1 day')).'|before:'.date('Ym-d', strtotime('+1 month')); $validation = Validator::make(Input::all(), $rules); if ($validation->passes()) { $tags = array(); foreach (explode(', ', Input::get('tags')) as $tag_name) { if ($tag = Tag::where('name', '=', $tag_name)->first()) { $tags[] = $tag->id; } } if (count($tags) == 0) { return Redirect::route('offers.create') ->withInput() ->withErrors($validation) ->with('message', 'Insert at least one tag.'); } $offer->update(Input::except('tags', 'file', '_method')); $offer->tags()->sync($tags); return Redirect::route('offers.show', $id); } return Redirect::route('offers.edit', $id) ->withInput() ->withErrors($validation) ->with('message', 'There were validation errors.'); } ...
addメソッドとの違いは最小限です。間違ったものが指定された場合、最初に404エラーをスローし、
id
次にメソッドを使用し
update($id)
ます。それがすべての変更です。
次に、ファイルapp / views / offers / show.blade.phpを変更します:
// app/views/offers/show.blade.php ... <thead> <tr> <th>Title</th> <th>Description</th> <th>City_id</th> <th>Company_id</th> <th>Off</th> <th>Image</th> <th>Tags</th> <th>Expires</th> </tr> </thead> <tbody> <tr> <td>{{{ $offer->title }}}</td> <td>{{ $offer->webDescription(array('shorten' => true, 'length' => 60)) }}</td> <td>{{{ $offer->city->name }}}</td> <td>{{{ $offer->company->name }}}</td> <td>{{{ $offer->off }}}</td> <td><img src="" style="max-width: 200px; max-height:150px;"/></td> <td> @foreach($offer->tags as $tag) <span class="badge">{{{ $tag->name }}}</span> @endforeach </td> <td>{{{ $offer->expires }}}</td> ...
今、割引を変更した後、画像とすべてのリレーショナルデータでその構造を美しく表示します。
ホームページ
最後に、サイトのメインページを作成します。
まず、新しいものを作成します
layout
。
// app/views/layouts/main.blade.php <!doctype html> <html> <head> <meta charset="utf-8"> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="{{ asset('css/main.css') }}"> @yield('styles') </head> <body> <div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="{{ route('home') }}">Habr Offers</a> <ul class="nav"> <li><a href="{{ route('home') }}">Home</a></li> </ul> </div> </div> </div> <div class="container"> @if (Session::has('message')) <div class="flash alert"> <p>{{ Session::get('message') }}</p> </div> @endif @yield('main') </div> <script type="text/javascript" src="//code.jquery.com/jquery.min.js"></script> <script type="text/javascript" src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script> @yield('scripts') </body> </html>
また、スタイルシート:
// public/css/main.css /* - */ body {padding-top: 60px;} /* , */ .no_decoration:hover, .no_decoration:focus {text-decoration: none;} /* / */ .thumbnail .image-container {width: 100%; max-height: 200px; overflow: hidden;} .thumbnail .image-container img {min-width: 100%; min-height: 100%;} .thumbnail h3 {height: 40px; overflow: hidden;} .thumbnail .description {height: 100px; overflow: hidden;}
次に、メインページのルートを再定義します。
// app/routes.php Route::get('/', array('as' => 'home', 'uses' => 'HomeController@index'));
HomeController
不足しているメソッドに追加し
index
ます。
// app/controllers/HomeController.php ... /** * Display a listing of offers. * * @return Response */ public function index() { $offers = Offer::orderBy('created_at', 'desc')->get(); return View::make('home.index', compact('offers')); } ...
app / views / homeフォルダーを作成し、そこにindex.blade.phpファイルを追加し、app / views / offersフォルダーに_preview.blade.phpファイルを作成します
// app/views/home/index.blade.php @extends('layouts.main') @section('main') <h1>{{ $title }}</h1> @if ($offers->count()) @foreach ($offers as $key => $offer) @if($key % 3 == 0) <div class="row-fluid"> <ul class="thumbnails"> @endif <li class="span4"> <div class="thumbnail"> @include('offers._preview', $offer) </div> </li> @if($key % 3 == 2 || $key == count($offers) - 1) </ul> </div> @endif @endforeach @else There are no offers @endif @stop // app/views/offers/_preview.blade.php <div class="image-container"> <img src=""> </div> <div class="caption"> <h3>{{{ $offer->title }}}</h3> <hr> <p class="description">{{ $offer->webDescription() }}</p> <hr> <p><span class="label label-important">{{{ $offer->off }}} % off</span></p> <p>Location: {{{ $offer->city->name }}}</p> <p>Offer by: {{{ $offer->company->name }}}</p> <p>Expires on: <span class="label label-warning">{{{ $offer->expires }}}</span></p> <p>Tags: @foreach($offer->tags as $tag) <span class="badge">{{{$tag->name}}}</span> @endforeach </p> </div>
次に、タグ、都市、および会社による割引検索を追加する必要があります。これを行うには、すぐ後にapp / routes.phpファイルに3つのルートを追加します
home
:
// app/routes.php ... Route::get('by_tag/{name}', array('as' => 'home.by_tag', 'uses' => 'HomeController@byTag'))->where('name', '[A-Za-z0-9 -_]+'); Route::get('by_city/{name}', array('as' => 'home.by_city', 'uses' => 'HomeController@byCity'))->where('name', '[A-Za-z0-9 -_]+'); Route::get('by_company/{name}', array('as' => 'home.by_company', 'uses' => 'HomeController@byCompany'))->where('name', '[A-Za-z0-9 -_]+'); ...
不足しているメソッドを追加します
HomeController
:
// app/controllers/HomeController.php ... /** * Display a listing of offers that belongs to tag. * * @param string $name * @return Response */ public function byTag($name) { $tag = Tag::whereName($name)->firstOrFail(); $offers = $tag->offers; $title = "Offers tagged as: " . $tag->name; return View::make('home.index', compact('offers', 'title')); } /** * Display a listing of offers that belongs to city. * * @param string $name * @return Response */ public function byCity($name) { $city = City::whereName($name)->firstOrFail(); $offers = $city->offers; $title = "Offers in: " . $city->name; return View::make('home.index', compact('offers', 'title')); } /** * Display a listing of offers that belongs to company. * * @param string $name * @return Response */ public function byCompany($name) { $company = Company::whereName($name)->firstOrFail(); $offers = $company->offers; $title = "Offers by: " . $company->name; return View::make('home.index', compact('offers', 'title')); } ...
これらのメソッドを正しく動作させるには、Models
City
で関係を設定する必要が
Company
あり
Tag
ます。
// app/models/City.php ... public function offers() { return $this->hasMany('Offer'); } // app/models/Company.php ... public function offers() { return $this->hasMany('Offer'); } // app/models/Tag.php ... public function offers() { return $this->belongsToMany('Offer'); }
このすべてを再生するには、ファイルapp / views / offers / _preview.blade.phpを変更し、リンクを追加します:
// app/views/offers/_preview.blade.php <a class="image-container" href="{{ route('home.offer', $offer->id) }}"> <img src=""> </a> <div class="caption"> <h3>{{{ $offer->title }}}</h3> <hr> <p class="description">{{ $offer->webDescription() }}</p> <hr> <p><span class="label label-important">{{{ $offer->off }}} % off</span></p> <p>Location: <a href="{{ route('home.by_city', $offer->city->name) }}">{{{ $offer->city->name }}}</a></p> <p>Offer by: <a href="{{ route('home.by_company', $offer->company->name) }}">{{{ $offer->company->name }}}</a></p> <p>Expires on: <span class="label label-warning">{{{ $offer->expires }}}</span></p> <p>Tags: @foreach($offer->tags as $tag) <a class="no_decoration" href="{{ route('home.by_tag', $tag->name) }}"> <span class="badge">{{{$tag->name}}}</span> </a> @endforeach </p> </div>
クリック、ゴー、割引は基準に従ってソートされ表示されます。
それでは、別の割引を表示するプレゼンテーションを作成しましょう。
// app/views/offers/_show.blade.php @extends('layouts.main') @section('main') <div class="page-header"> <h1> <span class="label label-important label-big">{{{ $offer->off }}}%</span> {{{ $offer->title }}} <small> by <a href="{{{ route('home.by_company', $offer->company->name) }}}">{{{ $offer->company->name }}}</a> </small> </h1> </div> <div class="pull-left image-container-big"> <img class="img-rounded" src="" alt="{{{ $offer->title }}}"> </div> <div class="description"> <p>{{ $offer->webDescription() }}</p> </div> <div class="clearfix"></div> <hr> <p>Location: <a href="{{ route('home.by_city', $offer->city->name) }}">{{{ $offer->city->name }}}</a> </p> <p>Tags: @foreach($offer->tags as $tag) <a class="no_decoration" href="{{ route('home.by_tag', $tag->name) }}"> <span class="badge">{{{$tag->name}}}</span> </a> @endforeach </p> <hr> <div class="page-header"> <h3>User's comments <small>leave and yours one</small></h3> </div> {{ Form::open() }} {{ Form::textarea('body', Input::old('body'), array('class' => 'input-block-level', 'style' => 'resize: vertical;'))}} <div class="input-append"> {{ Form::select('mark', array(0 => 5, 1 => 4, 2 => 3, 3 => 2, 4 => 1), Input::old('mark', 0)) }} {{ Form::submit('Comment', array('class' => 'btn btn-success', 'style' => 'clear: both;')) }} </div> {{ Form::close() }} @include('partials.errors', $errors) @stop // public/css/main.css body {padding-top: 60px;} .error {color: red;} .no_decoration:hover, .no_decoration:focus {text-decoration: none;} .thumbnail .image-container {width: 100%; max-height: 200px; overflow: hidden; display: block;} .thumbnail .image-container img {min-width: 100%; min-height: 100%;} .thumbnail h3 {height: 40px; overflow: hidden;} .thumbnail .description {height: 100px; overflow: hidden;} .image-container-big {width: 500px; height: 300px; margin: 0 20px 20px 0; text-align: center;} .image-container-big img {max-height: 300px; margin: 0 auto;} .label.label-big {font-size: 32px; line-height: 1.5em; padding: 0 15px; margin-bottom: 5px;}
割引を完全に表示できるようにするために、ルートとメソッドを追加し、最後にコメントフォームを追加しました。その機能のために、目的のコントローラーにルートとメソッドを追加する必要もあります。
// app/routes.php ... Route::get('offer_{id}', array('as' => 'home.offer', 'uses' => 'HomeController@showOffer'))->where('id', '[0-9]+'); Route::post('offer_{id}', array('before' => 'not_guest', 'uses' => 'HomeController@commentOnOffer'))->where('id', '[0-9]+'); ... Route::filter('not_guest', function(){ if (Auth::guest()) { return Redirect::back()->withInput()->with('message', 'You should be logged in to provide this action.'); } }); // app/controllers/HomeController.php ... /** * Display an offer. * * @param int $id * @return Response */ public function showOffer($id) { $offer = Offer::findOrFail($id); return View::make('offers._show', compact('offer')); } /** * Storing comment on offer. * * @param int $id * @return Response */ public function commentOnOffer($id) { $offer = Offer::findOrFail($id); if ($offer->usersComments->contains(Auth::user()->id)) { return Redirect::back()->withInput()->with('message', 'You have already commented on this Offer'); } $rules = array('body' => 'required|alpha|min:10|max:500', 'mark' => 'required|numeric|between:1,5'); $validator = Validator::make(Input::all(), $rules); if ($validator->passes()) { $offer->usersComments()->attach(Auth::user()->id, array('body' => Input::get('body'), 'mark' => Input::get('mark'))); return Redirect::back(); } return Redirect::back()->withInput()->withErrors($validator); } ...
すべてを順番に処理しましょう。
- 割引のプレゼンテーションでは、問題がないことを願っています-これは同じレイアウト+テンプレートエンジンです。
- ルートでは、すべてが単純で、以前と同様にすべてが同じです。リンクは、
Route::post('/offer_{id}'...)
承認なしでカスタムメッセージを生成する新しいフィルターを使用することを除いて、コントローラー@メソッドです。 -
showOffer($id)
複雑でもありません。 - . , ,
id
.
offers
.Offer
// app/models/Offer.php ... public function usersComments() { return $this->belongsToMany('User', 'comments')->withPivot('body', 'mark')->withTimestamps(); } ...
,comments
, ,body
mark
+ ( ).
, ( contains() ), . — .
割引ページにコメントを表示するには、ファイルapp / views / offers / _show.blade.phpを少し変更します
// app/views/offers/_show.blade.php ... @if(!$offer->usersComments->count()) <div class="well">You can be first to comment on this offer!</div> @endif @if(Auth::guest() || (!Auth::guest() && !$offer->usersComments->contains(Auth::user()->id))) {{ Form::open() }} {{ Form::textarea('body', Input::old('body'), array('class' => 'input-block-level', 'style' => 'resize: vertical;'))}} <div class="input-append"> {{ Form::select('mark', array(5 => 5, 4 => 4, 3 => 3, 2 => 2, 1 => 1), Input::old('mark', 5)) }} {{ Form::submit('Comment', array('class' => 'btn btn-success', 'style' => 'clear: both;')) }} </div> {{ Form::close() }} @include('partials.errors', $errors) @endif @foreach($offer->usersComments as $user) <div class="media"> <a class="pull-left" href="#"> <img class="media-object" data-src="holder.js/64x64"> </a> <div class="media-body"> <h4 class="media-heading">{{{ $user->username }}} <span class="label label-success">mark: {{{ $user->pivot->mark }}}</span></h4> <p class="muted">{{ str_replace("\r\n", '<br>', e($user->pivot->body)) }}</p> </div> </div> @endforeach @stop
これで、割引の下で、ユーザーはコメントを1つずつ残すことができ、ユーザーが既にコメントを残している場合、フォームは表示されません。
次のステップは、サイトへのアクセス権を割り当てることです。最初に、ユーザーとロールの関係を示します。
// app/models/User.php ... public function roles() { return $this->belongsToMany('Role'); } ...
次に、管理パネルでユーザーロール管理を追加します。
// app/routes.php ... Route::group(array('before' => 'admin.auth'), function() { ... Route::resource('users', 'UsersController'); Route::post('upload', array('uses' => 'HomeController@uploadOfferImage')); }); ... // app/views/layouts/scaffold.blade.php ... <li>{{ link_to_route('users.index', 'Users') }}</li> <li class="pull-right">{{ link_to_route('login.logout', 'Logout') }}</li> ...
モデル
User
では、ロールとの関係を追加する必要があることに注意してください。
// app/models/User.php ... public function roles() { return $this->belongsToMany('Role'); } ...
コントローラーを作成します
UserController
。
// app/controllers/UsersController.php class UsersController extends BaseController { /** * User Repository * * @var User */ protected $user; public function __construct(User $user) { $this->user = $user; } /** * Display a listing of the resource. * * @return Response */ public function index() { $users = $this->user->all(); return View::make('users.index', compact('users')); } /** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { $user = $this->user->findOrFail($id); return View::make('users.show', compact('user')); } /** * Show the form for editing the specified resource. * * @param int $id * @return Response */ public function edit($id) { $user = $this->user->findOrFail($id); return View::make('users.edit', compact('user')); } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { $user = $this->user->findOrFail($id); $roles = array(); foreach (explode(', ', Input::get('roles')) as $role_name) { if ($role = Role::where('role', '=', $role_name)->first()) { $roles[] = $role->id; } } $user->roles()->sync($roles); return Redirect::route('users.show', $id); } /** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id) { $this->user->findOrFail($id)->delete(); return Redirect::route('users.index'); } }
app / views / usersフォルダーを作成し、そこに3つのファイルを追加します。
// app/views/users/index.blade.php @extends('layouts.scaffold') @section('main') <h1>All Users</h1> @if ($users->count()) <table class="table table-striped table-bordered"> <thead> <tr> <th>Username</th> <th>Email</th> <th>Roles</th> </tr> </thead> <tbody> @foreach ($users as $user) <tr> <td>{{{ $user->username }}}</td> <td>{{{ $user->email }}}</td> <td> @foreach($user->roles as $role) <span class="badge">{{{$role->role}}}</span> @endforeach </td> <td>{{ link_to_route('users.edit', 'Edit', array($user->id), array('class' => 'btn btn-info')) }}</td> <td> {{ Form::open(array('method' => 'DELETE', 'route' => array('users.destroy', $user->id))) }} {{ Form::submit('Delete', array('class' => 'btn btn-danger')) }} {{ Form::close() }} </td> </tr> @endforeach </tbody> </table> @else There are no users @endif @stop // app/views/users/show.blade.php @extends('layouts.scaffold') @section('main') <h1>Show User</h1> <p>{{ link_to_route('users.index', 'Return to all users') }}</p> <table class="table table-striped table-bordered"> <thead> <tr> <th>Username</th> <th>Email</th> <th>Roles</th> </tr> </thead> <tbody> <tr> <td>{{{ $user->username }}}</td> <td>{{{ $user->email }}}</td> <td> @foreach($user->roles as $role) <span class="badge">{{{ $role->role }}}</span> @endforeach </td> <td>{{ link_to_route('users.edit', 'Edit', array($user->id), array('class' => 'btn btn-info')) }}</td> <td> {{ Form::open(array('method' => 'DELETE', 'route' => array('users.destroy', $user->id))) }} {{ Form::submit('Delete', array('class' => 'btn btn-danger')) }} {{ Form::close() }} </td> </tr> </tbody> </table> @stop // app/views/users/edit.blade.php @extends('layouts.scaffold') @section('main') <h1>Edit User</h1> {{ Form::model($user, array('method' => 'PATCH', 'route' => array('users.update', $user->id))) }} <ul> <li> {{ Form::label('username', 'Username:') }} {{ Form::text('username', $user->username, array('disabled')) }} </li> <li> {{ Form::label('email', 'Email:') }} {{ Form::text('email', $user->email, array('disabled')) }} </li> <li> {{ Form::label('roles', 'Roles:') }} {{ Form::text('roles', Input::old('roles', implode(', ', array_fetch($user->roles()->get(array('role'))->toArray(), 'role')))) }} </li> <li> {{ Form::submit('Update', array('class' => 'btn btn-info')) }} {{ link_to_route('users.show', 'Cancel', $user->id, array('class' => 'btn')) }} </li> </ul> {{ Form::close() }} @if ($errors->any()) <ul> {{ implode('', $errors->all('<li class="error">:message</li>')) }} </ul> @endif @stop @section('scripts') <script> $(document).ready(function(){ function split( val ) { return val.split( /,\s*/ ); } function extractLast( term ) { return split( term ).pop(); } $( "#roles" ) // don't navigate away from the field on tab when selecting an item .bind( "keydown", function( event ) { if ( event.keyCode === $.ui.keyCode.TAB && $( this ).data( "ui-autocomplete" ).menu.active ) { event.preventDefault(); } }) .autocomplete({ source: function( request, response ) { $.getJSON( "/roles", { term: extractLast( request.term ), }, function( data ) { response($.map(data, function(item) { return { value: item.role } })) } ); }, search: function() { // custom minLength var term = extractLast( this.value ); if ( term.length < 2 ) { return false; } }, focus: function() { // prevent value inserted on focus return false; }, select: function( event, ui ) { console.log(ui); console.log(this); var terms = split( this.value ); // remove the current input terms.pop(); // add the selected item terms.push( ui.item.value ); // add placeholder to get the comma-and-space at the end terms.push( "" ); this.value = terms.join( ", " ); return false; } }); }); </script> @stop
また、少しmetd
index
コントローラーを変更します
RolesController
... public function index() { $roles = $this->role->all(); if (Request::ajax()) { $roles = Role::where('role', 'like', '%'.Input::get('term', '').'%')->get(array('id', 'role')); return $roles; } return View::make('roles.index', compact('roles')); } ...
自動補完が機能するようになりました。
さらに、矛盾がないように、すべての移行をロールバックし、それを提供する優れたツールを使用します
Laravel
-これはDatabaseSeederです。これを使用して、データベースに何らかの構成を入力したり、データを開始/テストしたりできます。これを行うには、まずapp / database / seeds
UsersTableSeeder
フォルダーにクラスを作成します:
// app/database/seeds/UsersTableSeeder.php class UsersTableSeeder extends Seeder { public function run() { $users = array( array( 'username' => 'habrahabr', 'email' => 'habrahabr@habr.com', 'password' => Hash::make('habr'), 'updated_at' => DB::raw('NOW()'), 'created_at' => DB::raw('NOW()'), ) ); DB::table('users')->insert($users); } }
ロジックは次のとおりです。テーブルをクリアし、データの配列を作成して、データベースに挿入します。
同じことをしましょう
RolesTableSeeder
:
// app/database/seeds/RolesTableSeeder.php class RolesTableSeeder extends Seeder { public function run() { $roles = array( array( 'role' => 'admin', 'updated_at' => DB::raw('NOW()'), 'created_at' => DB::raw('NOW()') ), array( 'role' => 'manager', 'updated_at' => DB::raw('NOW()'), 'created_at' => DB::raw('NOW()') ), array( 'role' => 'moderator', 'updated_at' => DB::raw('NOW()'), 'created_at' => DB::raw('NOW()') ) ); DB::table('roles')->insert($roles); } }
ここでもロール
manager
を追加
moderator
し、これらのロールを持つユーザーに管理パネルの個々のリソースへのアクセスを提供します。
次に、別のクラスを作成します
Seeder
。
// app/database/seeds/RoleUserTableSeeder.php class RoleUserTableSeeder extends Seeder { public function run() { // Uncomment the below to wipe the table clean before populating DB::table('role_user')->truncate(); $role_user = array( array('user_id' => 1, 'role_id' => 1) ); // Uncomment the below to run the seeder DB::table('role_user')->insert($role_user); } }
したがって、ロールを
admin
最初のユーザーに追加しました。
データベースをクリアして初期データを入力するには、まずファイルapp / database / seeds / DatabaseSeeder.phpを次のように変更します。
// app/database/seeds/DatabaseSeeder class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Eloquent::unguard(); // $this->call('UsersTableSeeder'); $this->call('RolesTableSeeder'); $this->call('RoleUserTableSeeder'); } }
そして、すべての変更を受け入れるには、コンソールからコマンドを実行します(/ workspace / php / habr / folderにあります):
php artisan migrate:refresh --seed
migrate:refresh
すべての移行をロールバックしてから再度開始します。オプション
--seed
は、実行する必要があることを示します
DatabaseSeeder
。
次に、権利に関するロジックを構築します。モデルに変更を加えます
User
。
// app/models/User.php ... public function isAdmin() { $admin_role = Role::whereRole('admin')->first(); return $this->roles->contains($admin_role->id); } ... public function isManager() { $manager_role = Role::whereRole('manager')->first(); return $this->roles->contains($manager_role->id) || $this->isAdmin(); } ... public function isModerator() { $admin_role = Role::whereRole('admin')->first(); return $this->roles->contains($admin_role->id) || $this->isAdmin(); } ... public function isRegular() { $roles = array_filter($this->roles->toArray()); return empty($roles); } }
次に、サイトを使用する権限に一致するように、routesファイルを変更します。
// app/routes.php ... Route::post('offer_{id}', array('before' => 'not_guest|regular_user', 'uses' => 'HomeController@commentOnOffer'))->where('id', '[0-9]+'); ... Route::group(array('before' => 'admin.auth'), function() { Route::get('dashboard', function() { return View::make('dasboard'); }); Route::group(array('before' => 'manager_role_only'), function() { Route::resource('cities', 'CitiesController'); Route::resource('companies', 'CompaniesController'); Route::resource('tags', 'TagsController'); Route::resource('offers', 'OffersController'); Route::post('upload', array('uses' => 'HomeController@uploadOfferImage')); }); Route::resource('comments', 'CommentsController'); Route::group(array('before' => 'manager_role_only'), function() { Route::resource('roles', 'RolesController'); Route::resource('users', 'UsersController'); }); }); Route::when('comments*', 'moderator_role_only'); Route::filter('admin_role_only', function() { if (Auth::user()->isAdmin()) { return Redirect::intended('/')->withMessage('You don\'t have enough permissions to do that.'); } }); Route::filter('manager_role_only', function() { if (!Auth::user()->isManager()) { return Redirect::intended('/')->withMessage('You don\'t have enough permissions to do that.'); } }); Route::filter('moderator_role_only', function() { if (!Auth::user()->isModerator()) { return Redirect::intended('/')->withMessage('YYou don\'t have enough permissions to do that.'); } }); Route::filter('admin.auth', function() { if (Auth::guest()) { return Redirect::to('login'); } }); Route::filter('un_auth', function() { if (!Auth::guest()) { Auth::logout(); } }); Route::filter('not_guest', function(){ if (Auth::guest()) { return Redirect::intended('/')->withInput()->with('message', 'You should be logged in to provide this action.'); } }); Route::filter('regular_user', function(){ if (!Auth::guest()) { if (!Auth::user()->isRegular()) { return Redirect::back()->with('message', 'You cannot do that due to your role.'); } } });
お気づきのように、コメントルートに追加のフィルターを追加しました。したがって、サイトの一般ユーザー以外は誰も割引に関するコメントを投稿できません。
また、ここではルートが使用されました
Route::when()
-これは、いわゆるパターンフィルターです。テンプレート
URL
を最初のパラメーターとして渡し、フィルターを2番目
HTTP
に適用し、フィルターを適用する配列を3番目のパラメーターとして渡すことができます。コントローラーの
メソッドを変更し
login()
ます
LoginController
。
// app/controllers/LoginController.php ... public function login() { if (Auth::attempt(array('email' => Input::get('email'), 'password' => Input::get('password')), true) || Auth::attempt(array('username' => Input::get('email'), 'password' => Input::get('password')), true)) { if (!Auth::user()->isRegular()) { return Redirect::to('dashboard'); } return Redirect::intended('/'); } return Redirect::back()->withInput(Input::except('password'))->with('message', 'Wrong creadentials!'); }
これで、サイトに入ると、一般ユーザーはメインページにリダイレクトされ、管理者パネル、管理者、モデレーター、マネージャーがリダイレクトされます。
管理用のナビゲーションメニューを少し変更します。
// app/views/layouts/scaffold.blade.php @if(!Auth::guest()) <ul class="nav nav-pills"> @if(Auth::user()->isManager()) <li>{{ link_to_route('offers.index', 'Offers') }}</li> <li>{{ link_to_route('companies.index', 'Companies') }}</li> <li>{{ link_to_route('tags.index', 'Tags') }}</li> <li>{{ link_to_route('cities.index', 'Cities') }}</li> @endif @if(Auth::user()->isModerator()) <li>{{ link_to_route('comments.index', 'Comments') }}</li> @endif @if(Auth::user()->isAdmin()) <li>{{ link_to_route('roles.index', 'Roles') }}</li> <li>{{ link_to_route('users.index', 'Users') }}</li> @endif <li class="pull-right">{{ link_to_route('login.logout', 'Logout') }}</li> </ul> @endif
すばらしい-各ロールは、アクセスできるリソースを見るようになりました。
メール
Webアプリケーションの重要な側面は、メールの送信です。手紙の作成に
Laravel
使用
SwiftMailer
します(Laravel Mail)。
まず、メールを送信するための設定を構成する必要があります。手紙を送信するためのデモとして、私はのアカウントを使用し
gmail
ますが、サーバーからメールを送信する機能を提供するサービス(Postmarkappなど)は基本的に使用できます。
メールのセットアップ:
// app/config/mail.php ... return array( ... 'driver' => 'smtp', ... 'host' => 'smtp.gmail.com', ... 'port' => 587, ... 'from' => array('address' => 'habrahabr@habr.com', 'name' => 'Habra Offers'), ... 'encryption' => 'tls', ... 'username' => 'mygmailaccount@gmail.com', ... 'password' => 'mypassword', ... 'pretend' => false );
このパラメーター
pretend
は、メッセージの送信を担当します。trueに設定されている場合、メールは送信されませんが、送信レポートはサイトのログ(app / storage / logs)に保存されます。
まず、登録中にユーザーにウェルカムメールを送信する必要があります。このため、app / views / emailsフォルダーにテンプレートを作成します。
// app/views/emails/welcome.blade.php <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> </head> <body> <h1>Welcome to Habra Offers!</h1> <div> We are glad that you are interested in us, {{{ $username }}}! </div> </body> </html>
次に、メソッドを変更し
store()
ます
LoginController
。
// app/controllers/LoginController.php ... $user->save(); Mail::send('emails.welcome', array('username' => $user->username), function($message) use ($user) { $message->to($user->email, $user->username)->subject('Welcome to Habra Offers!'); }); Auth::loginUsingId($user->id); ...
Mailクラスは、メソッドを使用
send()
して3つの引数を取るメールを送信します。
- $ view-使用するテンプレート(または2つのテンプレートの配列、最初はhtmlテンプレート、2番目はプレーンテキスト)
- $ data-キーがテンプレート内の変数になるデータの配列
- $コールバック -メッセージパラメータを設定するために起動される関数
ただし、必要なのはウェルカムレターだけではありません。ユーザーがパスワードを忘れて、それを回復したい場合はどうなりますか?これを行うに
Laravel
は、パスワードリマインダとリセットを提供します。
必要なこと:
cd /workspace/php/habr php artisan auth:reminders php artisan migrate
パスワードを回復するには、電話で十分で
Password::remind(array('email' => $email))
あり、パスワードをリセットするためのリンクを記載したメールが送信されます。
2つのテンプレートを作成する必要があります。
- app / views / auth / remind.blade.php-パスワード回復のためのメールを送信する
// app/views/auth/remind.blade.php @extends('layouts.scaffold') @section('main') @if (Session::has('error')) <div class="alert alert-error"> {{ trans(Session::get('reason')) }} </div> @elseif (Session::has('success')) <div class="alert alert-success"> An e-mail with the password reset has been sent. </div> @endif <h1>Forgot your password?</h1> <p>{{ link_to_route('login.index', 'No') }}</p> {{ Form::open() }} <ul> <li> {{ Form::label('email', 'Your email')}} {{ Form::email('email') }} </li> <li> {{ Form::submit('Send reminder', array('class' => 'btn')) }} </li> </ul> {{ Form::close() }} @stop
- app / views / auth / reset.blade.php-パスワード回復フォーム
// app/views/auth/reset.blade.php @extends('layouts.scaffold') @section('main') @if (Session::has('error')) <div class="alert alert-error"> {{ trans(Session::get('reason')) }} </div> @endif <h1>Reset your password</h1> {{ Form::open() }} {{ Form::hidden('token', $token) }} <ul> <li> {{ Form::label('email', 'Email')}} {{ Form::email('email', Input::old('email')) }} </li> <li> {{Form::label('password', 'New password')}} {{ Form::password('password')}} </li> <li> {{Form::label('password', 'New password confirmation')}} {{ Form::password('password_confirmation')}} </li> </ul> {{ Form::submit('Reset', array('class' => 'btn'))}} {{ Form::close() }} @stop
関数trans()
は、構成からローカライズされた文字列を出力するヘルパー関数です。app / lang / ja / reminders.phpフォルダーを見て、表示されるエラーを確認してください。ローカライズを、たとえばロシア語に変更するには、app / config / app.phpファイルでロケール値をenからruに変更し、app / lang / enフォルダーのようにファイルを再作成するapp / lang / ruフォルダーを追加する必要があります。
次に、4つのルートを追加します。
// app/routes.php ... Route::group(array('before' => 'un_auth'), function() { ... Route::get('password/remind', array('as' => 'password.remind', 'uses' => 'LoginController@showReminderForm')); Route::post('password/remind', array('uses' => 'LoginController@sendReminder')); Route::get('password/reset/{token}', array('as' => 'password.reset', 'uses' => 'LoginController@showResetForm')); Route::post('password/reset/{token}', array('uses' => 'LoginController@resetPassword')); }); ...
復旧に切り替えるには、ログインページにリンクも追加します。
// app/views/login/index.blade.php ... {{ Form::close() }} <p>{{ link_to_route('password.remind', 'Forgot password?') }}</p> ...
で不足しているメソッドと同様に
LoginController
:
// app/controllers/LoginController.php ... /** * Show reminder form. * * @return Response */ public function showReminderForm() { return View::make('auth.remind'); } /** * Send reminder email. * * @return Response */ public function sendReminder() { $credentials = array('email' => Input::get('email')); return Password::remind($credentials, function($message, $user) { $message->subject('Password Reminder on Habra Offers'); }); } /** * Show reset password form. * * @return Response */ public function showResetForm($token) { return View::make('auth.reset')->with('token', $token); } /** * Reset password. * * @return Response */ public function resetPassword($token) { $credentials = array('email' => Input::get('email')); return Password::reset($credentials, function($user, $password) { $user->password = Hash::make($password); $user->save(); Auth::loginUsingId($user->id); return Redirect::home()->with('message', 'Your password has been successfully reseted.'); }); }
これで、すべてのユーザーがパスワードをリセットできます。
別のリンクを追加して、メインページのサイトに入力して登録します。
// app/views/layouts/main.blade.php ... <a class="brand" href="{{ route('home') }}">Habr Offers</a> <ul class="nav"> <li><a href="{{ route('home') }}">Home</a></li> </ul> <div class="btn-group pull-right"> @if(Auth::guest()) <a href="{{ route('login.index') }}" class="btn">Login</a> <a href="{{ route('login.register') }}" class="btn">Register</a> @else <a href="{{ route('login.logout') }}" class="btn">Logout</a> @endif </div> ...
まだ終了していない割引のみのページの出力を制限するには、別のメソッドをModelに追加する必要があります
Offer
。
// app/controllers/Offer.php ... public function scopeActive($query) { return $query->where('expires', '>', DB::raw('NOW()')); } public function scopeSortLatest($query, $desc = true) { $order = $desc ? 'desc' : 'asc'; return $query->orderBy('created_at', $order); } ...
したがって、我々はこの方法でできる
HomeController@index
だけの変更
Offer::orderBy('created_at', 'desc')->get()
で
Offer::active()->sortLatest()->get()
。新しく作成したメソッドは、必要な条件を条件のチェーンに追加します。タグ、都市、および企業によるソート方法についても同じことを行います。
// app/controllers/HomeController.php ... public function byTag($name) { ... $offers = $tag->offers()->active()->sortLatest()->get(); ... }
ページネーション
重要な側面はページネーションです。はい、もちろん、クエリをデータベースに送信し、数千行の回答を取得し、それらをすべてページにプッシュできます。しかし、これはほとんど誰のアプローチでもありません。データベースから返される結果の数を制限するのは非常に簡単です。クエリの最後
paginate()
に
get()
、またはの代わりにメソッドを使用する必要があります
all()
。簡単な例:
// app/controllers/HomeController.php ... public function index() { $offers = Offer::active()->sortLatest()->paginate(); ... } ... // app/views/home/index.blade.php ... @if ($offers->count()) {{ $offers->links() }} ... {{ $offers->links() }} @else There are no offers @endif ...
したがって、1ページに表示される結果は15のみであり、下にページナビゲーションがあります。結果の数は簡単に変更でき
paginate(1)
ます。たとえば、目的の数をメソッドに渡すだけです。たとえば、ページごとに1つの結果を返します。
// app/controllers/HomeController.php ... public function byTag($name) { $tag = Tag::whereName($name)->firstOrFail(); $offers = $tag->offers()->active()->sortLatest()->paginate(); $title = "Offers tagged as: " . $tag->name; return View::make('home.index', compact('offers', 'title')); } ... public function byCity($name) { $city = City::whereName($name)->firstOrFail(); $offers = $city->offersr()->active()->sortLatest()->paginate(); $title = "Offers in: " . $city->name; return View::make('home.index', compact('offers', 'title')); } ... public function byCompany($name) { $company = Company::whereName($name)->firstOrFail(); $offers = $company->offers()->active()->sortLatest()->paginate(); $title = "Offers by: " . $company->name; return View::make('home.index', compact('offers', 'title')); } ...
複雑なことは何もありません。
便宜上、管理パネルでも同じ操作を行います。
// app/controllers/OffersController ... /** * Display a listing of the resource. * * @return Response */ public function index() { $offers = $this->offer->sortLatest()->paginate(); return View::make('offers.index', compact('offers')); } ...
サイトに最後に追加するのは、ユーザーがコメントを残した割引のページおよびブックマークに最新のコメントを表示することです。
ページフレームにコメントを追加することから始めましょう。
// app/views/layouts/main.blade.php <div class="container"> @if (Session::has('message')) <div class="flash alert"> {{ Session::get('message') }} </div> @endif <div class="row-fluid"> <div class="span3"> <h2>Last Comments</h2> @if (count($comments = Comment::take(5)->get()) > 0) @foreach ($comments as $comment) @include('partials.comment', $comment) @endforeach @else There are no comments yet @endif </div> <div class="span9"> @yield('main') </div> </div> </div>
また、テンプレート自体も作成します
comment
。
// app/views/partials/comment.blade.php <div class="well"> <a href="{{ route('home.offer', $comment->offer_id) }}"> {{ $comment->user->username }} <span class="label label-success pull-right">mark: {{ $comment->mark }}</span> </a> <div>{{ $comment->webBody() }}</div> </div>
Model
Comment
User
と
Offer
:の間に接続を追加することを忘れないでください
// app/models/Comment.php ... public function user() { return $this->belongsTo('User'); } public function offer() { return $this->belongsTo('Offer'); } public function webBody($options = array()) { $str = $this->body; if (isset($options['shorten'])) { $length = isset($options['length']) ? (int) $options['length'] : 50; $end = isset($options['end']) ? : '…'; if (mb_strlen($str) > $length) { $str = mb_substr(trim($str), 0, $length); $str = mb_substr($str, 0, mb_strlen($str) - mb_strpos(strrev($str), ' ')); $str = trim($str.$end); } } $str = str_replace("\r\n", '<br>', e($str)); return $str; } ...
同様に、
html-
コメントを減らして取り除く補助機能もあります。
ユーザーにブックマークを追加することは残ります:
// app/routes.php Route::get('/', array('as' => 'home', 'uses' => 'HomeController@index')); Route::get('bookmarks', array('before' => 'auth', 'as' => 'home.bookmarks', 'uses' => 'HomeController@bookmarks')); ... // app/views/layouts/main.blade.php ... @if(Auth::guest()) <a href="{{ route('login.index') }}" class="btn">Login</a> <a href="{{ route('login.register') }}" class="btn">Register</a> @else <a href="{{ route('home.bookmarks') }}" class="btn">My Bookmarks</a> <a href="{{ route('login.logout') }}" class="btn">Logout</a> @endif ... // app/models/User.php ... public function usersOffers() { return $this->belongsToMany('Offer', 'comments')->withPivot('body', 'mark')->withTimestamps(); } ... // app/controllers/HomeController.php ... /** * Display a listing of bookmarked offers. * * @return Response */ public function bookmarks() { $offers = Auth::user()->usersOffers()->paginate(); $title = "My Bookmarked Offers"; return View::make('home.index', compact('offers', 'title')); } ...
まず、app / route.phpにルートを追加し、次にapp / views / layouts / main.blade.phpにリンクを追加し、Model
User
との間の接続を設定
Offer
し、最後にメソッドを実装し
bookmarks
ました
HomeController
。
展開する
展開の時間が来ました!このため、私はfortrabbit.comを選択しました -上のアプリケーションのホスティング
PHP
。それはサポートしています
Git
、
SSH
、
Memcached
、
Composer
、
MySQL
およびより。
そこでの登録プロセスは非常に簡単です。
次に、新しいアプリケーションを作成します。
彼に電話しましょう
habr
。プロジェクト名は、habr.eu1.frbit.net /へのリンクになります。メモ(Habra Offers)を追加
ssh
し、車からキーを追加します。
ssh
キーを確認するには、ターミナルに入力します:
cat ~/.ssh/id_rsa.pub
最後のステップは、環境の構成を待つことです。リポジトリへのアクセスを形成することになる
Git
、
SSH
と
SFTP
、
MySQL
カスタマイズと
ReSync
アクセス。
環境が稼働しています。
fortrabbit
非アクティブなアプリケーションをフリーズします。ここでアプリケーションの凍結を解除する方法を読むことができます。
ここで、アプリケーションに入力するため
fortrabbit
に、ターミナルに移動します。
cd && cd workspace/php/ git clone git@git1.eu1.frbit.com:habr.git fort_habr
fortrabbit
'a を含む空のリポジトリのクローンが作成されます。次に、プロジェクト全体をワークスペース/ php / habrフォルダーからワークスペース/ php / fort_habrフォルダーに転送します。データベース構成ファイルに移動し、新しいデータで修正します
MySQL
。これで、アプリケーションをアップロードする準備ができました。
cd fort_habr git add . git commit -am "Initial Commit" git push -u origin master
結局のところ、
ssh
移行を開始して移行を開始する必要があります。だから:
ssh u-habr@ssh1.eu1.frbit.com
次に、パスワードを入力すると、サーバー上にいます。htdocs
フォルダーに移動して、以下を実行します。
cd htdocs php artisan migrate:install php artisan migrate --seed
データベース設定が正しい場合-問題は発生しません。ホスティングを操作する
ため
Composer
に使用する必要さえありません
ssh
-コミットにトリガーを追加するだけです:
git commit --allow-empty -am "Update dependencies [trigger:composer:update]" git push -u origin master
このオプションは
--allow-empty
、ファイルに変更を加えずにコミットを開始できるようにするためのものです。空のコミットのように。しかし、コメントを見る
[trigger:composer:update]
と、ホスティングは自動的にチームを立ち上げ、
composer update
すべてのプロジェクトの依存関係が更新されます。
ちなみに、GitHubのリポジトリには、
seeds
割引のために写真も追加しました。
最後に、サイトにアクセスする前に
Domains
、サーバー
Root Path
でvalueと一致することを確認してください
public
。正確にこのように配置されているので
Laravel
。
ここで遊ぶことができます:Habra Offers。
おわりに
読んで興味深く、そうするのに役立つことを願っています。
Laravel
-さまざまな複雑さのWebアプリケーションを開発するための優れたフレームワーク。
私が説明しようとした主な、そしてさらに多くの側面。そして興味のために宿題をします:
- メインメニューにリンクを追加して、1週間または1日以内に期限切れになるオファーのみを表示できるようにします。
- コメントブロックを管理パネルに追加して、コメントリストで非表示にします。
- 割引の評価数(平均評価)を追加します。
- イメージ管理パッケージを追加します。
- ユーザーがアバターをアップロードする機能を追加します。
WYSIWYG
管理パネルでエディターを追加します。
おそらく良い仕事だと思いますか?
著者について
- 私は24歳、結婚しています。
- 最初の上位:UEP "KROK"。専門分野:国際経済学、マスター。
- 現時点では、応用数学学部のNTUU KPIの3年生です。専門分野:ソフトウェアエンジニアリング。
- 私はハーフタイムで15か月間Web開発者として働いています。
Laravel
バージョン3から学んでいます。
統計収集
- 開発に関する記事を書くのに一週間弱かかりました。
- 記事には3040が含まれています行れています(テキストエディター内)。
- 記事には100500文字が含まれています(テキストエディター)。
すべての文法エラーをPMに書いてください。
憎しみは死ぬ(これを書くに違いない)。
UPD:便利なリンク