当ページのリンクには広告が含まれています。
やっぱり基本ってすごく大事で。
遠回りしてるようで結局1番の近道だなとしみじみ感じてます。
DBと各種設定
設定開始。
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'ja_JP'),
'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', '+09:00'),
次。
ソルト書き換え。適当でいいけど長いほどいい。
'Security' => [
'salt' => env('SECURITY_SALT', '0EBBdcPB3kXvEjoFtCvsc6BPKmb8W4hHuBsV5XAJ'),
],
'Datasources' => [
'default' => [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'host' => 'localhost',
/*
* CakePHP will use the default DB port based on the driver selected
* MySQL on MAMP uses port 8889, MAMP users will want to uncomment
* the following line and set the port accordingly
*/
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'my_app',
/*
* You do not need to set this flag to use full utf-8 encoding (internal default since CakePHP 3.6).
*/
//'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'flags' => [],
'cacheMetadata' => true,
'log' => false,
/**
* Set identifier quoting to true if you are using reserved words or
* special characters in your table or column names. Enabling this
* setting will result in queries built using the Query Builder having
* identifiers quoted when creating SQL. It should be noted that this
* decreases performance because each query needs to be traversed and
* manipulated before being executed.
*/
'quoteIdentifiers' => false,
/**
* During development, if using MySQL < 5.6, uncommenting the
* following line could boost the speed at which schema metadata is
* fetched from the database. It can also be set directly with the
* mysql configuration directive 'innodb_stats_on_metadata = 0'
* which is the recommended value in production environments
*/
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
'url' => env('DATABASE_URL', null),
],
host
, username
, password
, database
を書き換え。timezone
を+09:00に書き換え。
date_default_timezone_set(Configure::read('App.defaultTimezone'));
date_default_timezone_set('Asia/Tokyo');
ページを表示させると、
無事にDBへ接続完了。
各ファイルの作成
何故か公式サイトがまともに表示されないので、チュートリアル記事を検索
bakeしないの?
先にテーブル作ったし、bakeしてテンプレファイル一括生成しない?って発想に至るかもしれません。個人的には、慣れるまでbakeしない方がいいです。
というのも、結局泥臭い方法が1番習得には早道で。
コード手打ち > コピペ > bake
この順番でやるのが言語の習得が早いし、どの方法を取るにしても毎回ソースを読んで理解できているか、確認して行かないと学習が進んで躓くんですよね。
あと今の段階でbakeできるほどきちんとテーブル設計できる人は、公式サイトで十分だったり見なくても今までの経験で何となく書けると思うw
基本ファイル作成
Controller, Model, Viwe(Template)ファイルを作成します。
CakePHPは規約をとても重視しているので、決まったルールに基づいてファイル名を決めると自動的に各ファイルが紐づきます。
DBでarticles
テーブルを作成したので、src/Model/Table/
の中にArticlesTable.php
を作成します。
Model
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
}
Modelは、DBのデータをやり取りしたり加工したりします。$this->addBehavior('Timestamp')
は、articles
テーブルのcreated
, modified
に自動で日時を入れるコードです。
※DBのカラム側に設定も必要ですが、コピペしてれば上手く行くので今回は割愛
Controller
<?php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->Articles->find('all');
$this->set(compact('articles'));
}
}
ここではまずindexというactionを作成しています。
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
Controllerで設定したactionはデフォルトではwww.example.com/articles/index
というURLでアクセス出来ます。
同様に、 foobar() という関数を定義すると、ユーザーは、www.example.com/articles/foobar
でアクセス出来ます。
www.example.com/articles/about
のURLでページを表示させるならpublic function about()
があるし、www.example.com/articles/stocks
ならpublic function
があります。
()stocks
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
$this->set(compact('articles');
は上の行で設定している$articles
を配列にしてviewへ渡す機能です。
compact()はもともとphpにある関数です。
compact()は簡単に言うと、変数を一気に配列にする関数です。
特にフレームワークは最後に変数を全部viewに渡さないといけないので、compact()は頻出です。
Viwe(Template)
<html>
<head></head>
<body>
<h1>ブログ記事</h1>
<table>
<tr>
<th>Id</th>
<th>タイトル</th>
<th>作成日時</th>
</tr>
<?php foreach ($articles as $article) : ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format('y/m/d'); ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
Viewファイルはコントローラーで作成したactionの内容をURLで表示させたい時にそのアクション名を使用して作成します。
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
今回のindexであれば作成場所はsrc/Template/Articles/index.ctp
となります。
参考サイトにもある通り、ArticlesController.php
にhoge
というアクションが作成されていた場合、そのactionに紐づけるviewファイルはsrc/Template/Articles/hoge.ctp
になるし、ArticlesController.php
にtest
というアクションが作成されていた場合、そのactionに紐づけるviewファイルはsrc/Template/Articles/test.ctp
になります。
<?php foreach ($articles as $article) : ?>
上記でコントローラーから
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita$this->set(compact('articles');
で受け取った値をforeach ($articles as $article)
で回して全件表示しています。
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
上記はヘルパーというcakeの機能を使用してidごとの詳細ページへのリンクを作成しています。
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
ところでこのチュートリアル記事、めちゃくちゃ丁寧…w
公式より親切www
www.example.com/articles/index
(indexなので、www.example.com/articles
でもOK)にアクセスすると以下が表示されます。
詳細ページ
上述のヘルパー機能によって生成したリンク先ページを作っていきます。
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
・コントローラーにviewアクションを追加
・リンク先ページviewを作成
<?php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->Articles->find('all');
$this->set(compact('articles'));
}
public function view($id = null)
{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}
}
今回は全件取得ではなく1件なので
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiitaget($id)
を記述します。
<html>
<head></head>
<body>
<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created:<?= $article->created->format('y/m/d') ?></small></p>
</body>
</html>
記事の追加・編集・削除
・ArticlesController.phpに「add(), edit(), delete()」アクションを追加
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
・各アクションへのリンクをArticles/index.ctp内に記述
CRUD(Create, Read, Update, Delete)はどんなシステムでも基本中の基本。
これを実装していきます。
追加・編集・削除
完成ソース。
<?php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->Articles->find('all');
$this->set(compact('articles'));
}
public function view($id = null)
{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}
public function add()
{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('記事を保存しました。'));
return $this->redirect(['action' => 'index']);
$this->Flash->error(__('記事の追加が出来ません。'));
}
}
$this->set('article', $article);
}
public function edit($id = null)
{
$article = $this->Articles->get($id);
if($this->request->is(['post','put'])){
$this->Articles->patchEntity($article, $this->request->getData());
if($this->Articles->save($article)){
$this->Flash->success(__('記事が更新されました'));
return $this->redirect(['action' =>'index']);//ここの記述で保存実行時にindexページへ戻っている
}
$this->Flash->error(__('更新出来ませんでした。'));
}
$this->set('article', $article);
}
public function delete($id)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if($this->Articles->delete($article)){
$this->Flash->success(__('id: {0} の記事が削除されました。', h($id)));
return $this->redirect(['action' => 'index']);
}
}
}
<html>
<head></head>
<body>
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>
<table>
<tr>
<th>Id</th>
<th>タイトル</th>
<th>作成日時</th>
<th>編集</th>
</tr>
<?php foreach ($articles as $article) : ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format('y/m/d'); ?>
</td>
<td>
<?= $this->Form->postLink(
'削除 ',
['action' => 'delete', $article->id],
['confirm' => '本当に削除しますか?'])
?>
<?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
追加
public function add()
{
// ModelのArticlesで定義された空の変数(エンティティ)を$articleにコピー
$article = $this->Articles->newEntity();
// post送信かチェック
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
// 保存処理実行
if ($this->Articles->save($article)) {
// 保存処理が成功した場合の、フラッシュメッセージ
$this->Flash->success(__('記事を保存しました。'));
// www.example.com/articles/index へリダイレクト
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('記事の追加が出来ません。'));
}
$this->set('article', $article);
}
・ユーザーがフォームを使ってデータを POST した場合、その情報は、
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita$this->request->getData()
の中に入ってきます。
・FlashComponent の success() および error() メソッドを使って セッション変数にメッセージをセット。これらのメソッドは PHP の マジックメソッド を利用→レイアウトでは<?= $this->Flash->render() ?>
を用いてメッセージを表示
<html></html>
<head></head>
<body>
<h1>記事追加</h1>
<?php
echo $this->Form->create($article);
<!-- <form method="post" action="/articles/add"> -->
echo $this->Form->control('title');
<!-- <div class="input text"> -->
<!-- <label for="title">Title</label> -->
<!-- <input type="text" name="title" maxlength="255" id="title"> -->
<!-- </div> -->
echo $this->Form->control('body');
<!-- <div class="input textarea"> -->
<!-- <label for="body">Body</label> -->
<!-- <textarea name="body" id="body" rows="5"></textarea> -->
<!-- </div> -->
echo $this->Form->button(__('Save Article'));
<!-- <button type="submit">Save Article</button> -->
echo $this->Form->end();
<!-- </form> -->
?>
作成されるコントロールの型は、カラムのデータ型に依存します。
Form – 3.10
$options パラメーターを使うと、必要な場合に特定のコントロールタイプを選択することができます。
Form – 3.10<!-- 例 --> echo $this->Form->control('published', ['type' => 'checkbox']);
www.example.com/articles/add
にアクセス。
index.ctp
に記事追加
のリンクを追加。
<html>
<head></head>
<body>
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>
<table>
<tr>
<th>Id</th>
<th>タイトル</th>
<th>作成日時</th>
</tr>
<?php foreach ($articles as $article) : ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format('y/m/d'); ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
バリデーション作成
バリデーションのルールは、モデルの中で定義することができます
ArticlesTable.phpの既存クラス内に
cakephp3 ブログチュートリアル解説 #cakephp3 – QiitavalidationDefault()
というアクションを追加します。
ここで定義したバリデーションルールはControllerからArticlesTableのModelをsave() メソッドで呼んだときに使用されます。
use Cake\Validation\Validator; //このクラスの読み取り追加
//以下のメソッドを追加
public function validationDefault(Validator $validator)
{
$validator
// ->notEmpty('title') //コンソールで「'notEmpty' is deprecated.」とnotEmptyは非推奨ですと表示されるのでnotBlankを使用
->notBlank('title')
->requirePresence('title')
->notBlank('body')
->requirePresence('body');
return $validator;
}
他のバリデーションルールは公式を参考に。
編集
public function edit($id = null)
{
// 記事IDを取得
$article = $this->Articles->get($id);
// post送信かつ、put(更新)メソッドかチェック
if($this->request->is(['post','put'])){
$this->Articles->patchEntity($article, $this->request->getData());
if($this->Articles->save($article)){
$this->Flash->success(__('記事が更新されました'));
return $this->redirect(['action' =>'index']);//ここの記述で保存実行時にindexページへ戻っている
}
$this->Flash->error(__('更新出来ませんでした。'));
}
$this->set('article', $article);
}
$this->request->is(['post','put'])
にて、putメソッドが出ていて必須ではないけれど、まあちらっと頭の片隅にはあった方がいいと思うので、お時間があれば↓の記事をどうぞ。
しいていうなら、get($id)でidが取得できないことを想定して、patchEntityではなくnewEntityを使用してDBに保存するケースも想定すること、と先輩から教わりましたw
public function edit($id = null)
{
$article = $this->Articles->get($id)
if ($this->request->is('post')) {
if (!emtpy($article)) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
} else {
$article = $this->Articles->newEntity($this->request->getData());
}
if ($this->Articles->save($article)) {
$this->Flash->success(__('記事を保存しました。'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('記事の追加が出来ません。'));
}
}
$this->set('article', $article);
}
6行目のif文で$idが取得できていることを確認しています。
<html>
<head></head>
<body>
<h1>記事の編集</h1>
<?php
echo $this->Form->create($article);
<!-- <form method="post" action="/articles/add"> -->
echo $this->Form->control('title');
<!-- <div class="input text"> -->
<!-- <label for="title">Title</label> -->
<!-- <input type="text" name="title" maxlength="255" id="title"> -->
<!-- </div> -->
echo $this->Form->control('body',['rows' =>'3']);
<!-- <div class="input textarea"> -->
<!-- <label for="body">Body</label> -->
<!-- <textarea name="body" id="body" rows="5"></textarea> -->
<!-- </div> -->
echo $this->Form->button(__('記事保存'));
<!-- <button type="submit">Save Article</button> -->
echo $this->Form->end();
<!-- </form> -->
?>
</body>
</html>
<html>
<head></head>
<body>
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>
<table>
<tr>
<th>Id</th>
<th>タイトル</th>
<th>作成日時</th>
<th>編集</th>
</tr>
<?php foreach ($articles as $article) : ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format('y/m/d'); ?>
</td>
<td>
<?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
削除
deleteはロジックを実行してリダイレクトする為、アクションに対してビューがありません。
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita
こういうパターンがあることを抑える。
public function delete($id)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if($this->Articles->delete($article)){
$this->Flash->success(__('id: {0} の記事が削除されました。', h($id)));
return $this->redirect(['action' => 'index']);
}
}
<html>
<head></head>
<body>
<h1>ブログ記事</h1>
<?= $this->Html->link('記事追加',['action' => 'add']) ?>
<table>
<tr>
<th>Id</th>
<th>タイトル</th>
<th>作成日時</th>
<th>編集</th>
</tr>
<?php foreach ($articles as $article) : ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format('y/m/d'); ?>
</td>
<td>
<?= $this->Form->postLink(
'削除 ',
['action' => 'delete', $article->id],
['confirm' => '本当に削除しますか?'])
?>
<?= $this->Html->link('編集', ['action' => 'edit', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
デバッグ
実はこのチュートリアル、動きませんw
記事追加の画面で、Save Article
を押下すると以下のエラー画面が表示されます。
Error: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (DB名
.articles
, CONSTRAINT articles_ibfk_1
FOREIGN KEY (user_id
) REFERENCES users
(id
))
articles
で外部キーが設定されているuser_id
カラムで制約違反が発生してる、とのこと。
ブログ投稿で投稿者(user_id)が空な状況も想像できないので、外部キーが設定されているarticles
.ueser_id
とついでに同じエラーが発生するだろうarticles
.slug
を必須項目にします。
Model
public function validationDefault(Validator $validator)
{
$validator
->notBlank('user_id')
->requirePresence('user_id')
->notBlank('slug')
->requirePresence('slug')
// ->notEmpty('title') //コンソールで「'notEmpty' is deprecated.」とnotEmptyは非推奨ですと表示されるのでnotBlankを使用
->notBlank('title')
->requirePresence('title')
->notBlank('body')
->requirePresence('body');
return $validator;
}
Viwe
<html>
<head></head>
<body>
<h1>記事追加</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('user_id', ['type' => 'text']); // typeでtextを指定しないとプルダウンになる
echo $this->Form->control('slug');
echo $this->Form->control('title');
echo $this->Form->control('body');
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>
</body>
</html>
<html>
<head></head>
<body>
<h1>記事の編集</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('user_id', ['type' => 'text']); //typeでtextを指定しないとプルダウンになる
echo $this->Form->control('slug');
echo $this->Form->control('title');
echo $this->Form->control('body',['rows' =>'3']);
echo $this->Form->button(__('記事保存'));
echo $this->Form->end();
?>
</body>
</html>
記事を新規投稿します。
新規投稿できました。
ちなみにまだ直した方がいいところはありますが、これは最低限とは違うので今後の課題ですね。
ルーティング
www.example.com
にアクセスすると、CakePHPのサンプルページが表示されます。
これは、config/routes.php
にそのルーティングの設定があるからです。
このルーティングを修正すると、www.example.com
にアクセスしたときにwww.example.com/articles/index
を表示するように設定することが出来ます。
デフォルトで
cakephp3 ブログチュートリアル解説 #cakephp3 – Qiita$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
となっている上記を$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
へ変更します。
これで、「/」でリクエストしてきたユーザーを、ArticlesController の index() アクションに 接続させることができます。
‘home’ってなに?
home
は、Viewファイル名です。
function名とViewファイル名は自動的に関連づくので、どちらも同じ名前ならViewファイル名を省略できますが、function名とViewファイル名が違う場合はコードに書く必要があります。
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
// $routes->connect('/', ['controller' => controller名, 'action' => function名, Viewファイル名]);
今後の課題
user_idのプルダウン選択化
現状はuser_idは必須項目で、空白以外の文字が1文字以上、というバリデーションのみ。
このままだと存在しないuser_idも当たり前に登録できます。
なのでuser_idはusersテーブルからデータを取得してプルダウン選択に組み込むのがいいです。
が、それにはusersテーブルのデータを全件取得する処理が必要です。
user_idのCRUD
現状usersテーブルには1人しか登録されておらず、新規作成・表示・編集・削除にはDBを直接見るしか方法がないです。
これだと不便なのでusersテーブルを操作する画面が必要かなと思います。
slugの重複チェック
articles
.slug
はユニークキーなんですよね。
つまり、同じslugで保存されようとしたときはバリデーションで弾いた方がいいです。
ぐぐればすぐに出てくるけど、このバリデーションは公式サイトに書いていないので、これも最低限とは違うかなと。
まとめ
チュートリアルは以上です。
個人的にはクエリビルダーが鬼門だと思ってるので、今度はそれ関係の記事を書こうかな。