「基本からしっかり学ぶ Symfony2 入門」メモ(3)
「基本からしっかり学ぶ Symfony2 入門」を買った。
- 作者: 後藤秀宣,金本貴志
- 出版社/メーカー: 技術評論社
- 発売日: 2015/12/16
- メディア: 大型本
- この商品を含むブログを見る
基本からしっかり学ぶ Symfony2入門:書籍案内|技術評論社
hidenorigoto/symfony2-book: 基本からしっかり学ぶSymfony2入門 サポートサイト
Writer::createFromStringの引数について
前回、Write::createFromString('', '')
の引数は2つだったと書いたけど、その意味を見ていなかった。第二引数はv7.0では改行コードだったが、v8.0では削除され引数は1つになったようだ。
8-3 独自のサービスの定義
debug:container
でエラーとなった。
> php.exe C:\Users\ishida\PhpstormProjects\classic-symfony\bin\console debug:container app.inquiry_csv_builder [Symfony\Component\DependencyInjection\Exception\InvalidArgumentException] The file "C:\Users\ishida\PhpstormProjects\classic-symfony\src\AppBundle\De pendencyInjection/../Resources/config\services.yml" does not contain valid YAML. [Symfony\Component\Yaml\Exception\ParseException] The reserved indicator "@" cannot start a plain scalar; you need to quote t he scalar at line 7 (near "arguments: [%csv_encoding%, @app.inquiry_reposit ory]").
'@app.inquiry_repository'
とシングルクォートで囲む必要があった。
8-4 独自の設定値の定義
この本の山場であり、Symfonyの最大の特徴ではないかと思う。
Member
のjoinedDate
は@var \DateTime
となっているがstringなのでは?
prototype
がわからない。
members: 山田: {part: トランペット, joinedDate: "2010-10-01"} 田中: {part: バイオリン, joinedDate: "2008-04-10"}
同じようなものを繰り返す時に使うということか。
例えばこれを
members: - {name: 山田, part: トランペット, joinedDate: "2010-10-01"} - {name: 田中, part: バイオリン, joinedDate: "2008-04-10"}
と書くこともできるのだろうか。
できた。
https://github.com/iakio/classic-symfony/commit/d9f4e70820c180996ec9b8704290f450e5893d73
でも一意な識別子がある場合は元の書き方の方が良さそうだ。
「基本からしっかり学ぶ Symfony2 入門」メモ(2)
「基本からしっかり学ぶ Symfony2 入門」を買った。
- 作者: 後藤秀宣,金本貴志
- 出版社/メーカー: 技術評論社
- 発売日: 2015/12/16
- メディア: 大型本
- この商品を含むブログを見る
基本からしっかり学ぶ Symfony2入門:書籍案内|技術評論社
hidenorigoto/symfony2-book: 基本からしっかり学ぶSymfony2入門 サポートサイト
6-2 管理者用画面の実装
「お問い合わせエンティティへの管理フィールドの追加」で、
Inquiry
にprocess_status
、process_memo
を追加するところでエラーとなった。
> php.exe C:\Users\ishida\PhpstormProjects\classic-symfony\bin\console doctrine:schema:update --force Updating database schema... [Doctrine\DBAL\Exception\NotNullConstraintViolationException] An exception occurred while executing 'ALTER TABLE inquiry ADD process_stat us VARCHAR(20) NOT NULL': SQLSTATE[23502]: Not null violation: 7 ERROR: 列"process_status"にはNULL値 があります [Doctrine\DBAL\Driver\PDOException] SQLSTATE[23502]: Not null violation: 7 ERROR: 列"process_status"にはNULL値 があります [PDOException] SQLSTATE[23502]: Not null violation: 7 ERROR: 列"process_status"にはNULL値 があります doctrine:schema:update [--complete] [--dump-sql] [-f|--force] [--em [EM]] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [-e|--env ENV] [--no-debug] [--] <command> Process finished with exit code 1 at 21:06:39. Execution time: 858 ms.
process_status
, process_memo
はNOT NULLだが、この時点で既にレコードが挿入されていたため。
それぞれデフォルト値を指定することにした。
* @ORM\Column(name="process_status", type="string", length=20, options={"default":"0"}) ... * @ORM\Column(name="process_memo", type="text", options={"default":""})
7-1 データフィクスチャ
app/console
の実行にPhpStormのCommand Line Tool Supportを使っていたのだが、doctrine/doctrine-fixtures-bundle
をインストールして新しいコマンドを追加したときは、コマンド一覧のリロードが必要となる。
7-2 CSVダウンロード機能の作成
何故Write::createFromString(",")
と引数にカンマを渡しているのだろうと疑問に思っていたのだけれど、サンプルコードを見て意味が分かった。これはダブルクォートじゃなくてシングルクォート2つだった。
つまり、
Write::createFromString(",")
と、カンマを渡しているように見えたがWrite::createFromString('', '')
と、空文字を2つ渡していた
買ったのはKindle版。紙の本では見ていない。
7-4 APIの作成
この本は全体的にSymfony以外の部分については、よく言えばシンプルに書いてある。Concert
の設計がdate:date
、time:time
というのはなかなか大胆。
「ちょっとした確認用にテストコマンドを用意しておくと便利」はとても良いアドバイスだと思う。
「基本からしっかり学ぶ Symfony2 入門」メモ(1)
「基本からしっかり学ぶ Symfony2 入門」を買った。
- 作者: 後藤秀宣,金本貴志
- 出版社/メーカー: 技術評論社
- 発売日: 2015/12/16
- メディア: 大型本
- この商品を含むブログを見る
基本からしっかり学ぶ Symfony2入門:書籍案内|技術評論社
hidenorigoto/symfony2-book: 基本からしっかり学ぶSymfony2入門 サポートサイト
環境について
本書での確認環境は、
とのことだが、そのままやってもつまらないので
- PHP 7.0.2
- Symfony 3.0.1
- PostgreSQL 9.3.1
を使っている。
4-2 フォームの基本
$this->createFormBuilder()->add('name', 'text')
は第二引数にクラス名を渡して->add('name', TextType::class)
と書くようになったようだ。
choiceの時の引数が変わったようだ。
->add('type', ChoiceType::class, [ 'choices' => [ '公演について' => 0, 'その他' => 1 ],
4-3 メール通知機能の作成
app/config/parameters.yml
とapp/config/parameters.yml.dist
の違いは?
Gmailを使うのであれば、2段階認証を無効にした開発用メールアドレスを用意するという方法の他に、アプリケーションパスワードを設定しても良さそうだ。
5-4 BlogArticleの作成
レコードの追加時にエラーが発生した。
INSERT INTO blog_article(title, target_date, content) VALUES ('本年もよろしくお願い致します', '2015-1-3', '');
[2016-01-25 20:56:06] [23502] ERROR: 列"id"内のNULL値はNOT NULL制約違反です 詳細: 失敗した行は(null, 本年もよろしくお願い致します, 2015-01-03, )を含みます
blog_article
テーブル定義で、id
列にDEFAULT値が設定されていなかった。Doctrine ORMでは、@GeneratedValueのstrategyにAUTO、SEQUENCE、IDENTITYといったものがあり、PostgreSQLを使う場合はAUTOだとこうなる模様。
SEQUENCEにしても良さそうだけど、とりあえずデータの投入方法を以下のようにしてお茶を濁した。
INSERT INTO blog_article(id, title, target_date, content) VALUES (nextval('blog_article_id_seq'), '本年もよろしくお願い致します', '2015-1-3', '');
2015年はこんな年でした
- アウトプットを増やさねばと思いつつ、はてなを始めてから一番ブログを書かなかった1年でした。Qiitaはちょっと書いてます
- OSC2015北海道で発表してきました。多分前にOSCで登壇したのは2010年
- PhpStorm買いました
- 将棋ウォーズは去年4級だったけど、今年やっと3級になりました
- 会社を辞めてフリーランスになりました
とりあえず今年のお気に入りの動画置いておきます
もっと前はこんな年でした
久しぶりにDockerを動かしてみた(Docker Toolbox)
前回から2年近くたってるのか。
coreos-vagrantでDockerしてみてわかったこととかハマったこととか - iakioの日記
ホストはWindows10。
インストール
boot2dockerというのを使うんだっけと思って見てみたらDeprecatedと言われたのでDocker Toolboxを使うことにした。
なんとなくKitematicはインストールしなかった。
デスクトップにできたDocker QuickStart Terminalのアイコンを起動してもエラーが出る。VirtualBoxのVMはできているがストレージがマウントされていない。どうやらこの辺のバグを踏んだ模様。VirtualBoxを5.0.4にアップデートして解決。
- Cannot create host-only adapter with VBox 5 on Windows 10 (using administrative shell) · Issue #1521 · docker/machine · GitHub
- #14040 (Windows host-only adapter creation fails due to slow background processing) – Oracle VM VirtualBox
Hello World
ドキュメントがいっぱいある。Hello Worldからみてみる。
$ docker run ubuntu:14.04 /bin/echo 'Hello world' Error response from daemon: Cannot start container cde072590d243c292d3aee79e0af405d3d7e2ace3be670ebbd6bec9023686b0c: [8] System error: exec: "C:/Program Files/Git/usr/bin/echo": stat C:/Program Files/Git/usr/bin/echo: no such file or directory
これはスラッシュを1つ増やせばいいとどこかで見た。
$ docker run ubuntu:14.04 //bin/echo 'Hello world' Hello world
しかしbashの方は動かなかった。
$ docker run -t -i ubuntu:14.04 //bin/bash cannot enable tty mode on non tty input
まあdocker-machine sshすればVirtualBoxの方にはsshできるので、そこから同じことはできる。
$ docker-machine.exe ssh default ... docker@default:~$ docker run -t -i ubuntu:14.04 /bin/bash root@83ecba104bec:/#
network
Working with containersをやってみる。
ドキュメントに、boot2dockerを使ってる場合はlocalhostじゃなくてvirtual hostのIP使ってね。 boot2dockerコマンドで調べられるよと書いてるみたいだけど、今回の場合はdocker-machineコマンド のことだと思う。
$ docker-machine.exe ip default 192.168.99.100
取りあえずこの辺まで。
と思ったけどもうちょっと続く。
volume
VirtualBox側には、C:\Users
がそのまま/c/Users
にマウントされている。名前が一緒なのはかなり便利だ。
例えば僕はWindows上のC\Users\ishida\src\github.com\iakio\phpunit-smartrunner
というディレクトリでPHPの何かしらを開発していて、これをテストしたい場合はこのディレクトリで
$ vendor/bin/phpunit tests
と実行している(だいたいGit-Bashを使っている)。これをDockerのコンテナ内で実行したい場合は、PHPがインストールされたコンテナを用意して(phpというそのままの名前のイメージがDocker Hubにある)、同じディレクトリで、
$ docker run -v /$(pwd):/work -w //work php vendor/bin/phpunit tests
としてやればよい。php:7.0などとしてやれば簡単に複数バージョンでのテストもできそうだ。
シンプルなテンプレートエンジンPlatesをSilexで使ってみる
Platesという、シンプルなテンプレートエンジンを使ってみた。 小さなWebアプリケーションでTwigが必要なほどではない場合にはちょうど良いと思う。
まずはSilexで最低限のものを実装しよう。
{ "require": { "silex/silex": "~1.3" } }
<?php // web/index.php require_once __DIR__ .'/../vendor/autoload.php'; $app = new Silex\Application(); $app->get('/', function () { return "Hello"; }); $app->run();
Getting Started
composer.jsonにPlatesを追加してcomposer updateする。
{ "require": { "silex/silex": "~1.3", "league/plates": "3.*" } }
では最低限の実装を。
<?php // web/index.php require_once __DIR__ .'/../vendor/autoload.php'; $app = new Silex\Application(); $app->get('/', function () { $templates = new League\Plates\Engine(__DIR__ . '/../templates'); return $templates->render('home', ['title' => 'Hello']); }); $app->run();
<!-- templates/home.php --> <h1><?=$this->e($title)?></h1>
見ての通り、テンプレートエンジンというよりほぼPHPそのままだ(まあそもそもPHPってそういうものなんだけど)。 PHPにテンプレートエンジンとして使うための便利機能を追加したもの、と考えた方がいいかもしれない。
ルートのアクション内でEngineをnewするのはあんまりなので改善しよう。行儀よくやるならServiceProviderを使うのかもしれないけどここはより単純に、単にコンテナに追加してしまおう。
<?php // web/index.php ... $app['view'] = $app->share(function () { return new League\Plates\Engine(__DIR__ . '/../templates'); }); $app->get('/', function () use ($app) { return $app['view']->render('home', ['title' => 'Hello']); });
Silexのバージョンは1.3.0で、Pimpleが1.1.1なので$app->share()
を使っている。
layoutとinsert
もう少し色々試してみたいんだけど、ネタがないと難しいのでRails Tutorialの静的ページ部分を借りてくることにした。詳細は省くがnpmでbootstrap-sassをインストールし、gruntでビルドできるようにした。
さてレイアウトを導入しよう。
<!-- templates/home.php --> <?php $this->layout('layouts/application', ['title' => 'Home']) ?> <div class="center hero-unit"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </h2> </div>
<!DOCTYPE html> <!-- templates/layouts/application.php --> <html> <head> <title><?=$title?></title> <link rel="stylesheet" type="text/css" href="app.css"> <?=$this->fetch('layouts/shim')?> </head> <body> <?=$this->fetch('layouts/header')?> <div class="container"> <?=$this->section('content')?> <?=$this->fetch('layouts/footer')?> </div> </body> </html>
$this->layout()
でレイアウトを指定し、$this->section()
で呼び出している。$this->section()
の引数はセクション名で、content
は全ての出力を表す唯一の予約済みのセクション名だ。
明示的にセクションを作る場合は $this->start('name') ... $this->stop()
を使う。
$this->layout()
でレイアウトを指定すると同時にtitleも渡しているので、index.php側からはtitleを渡す必要は無くなった。
$app->get('/', function () use ($app) { return $app['view']->render('home'); });
<?=$this->fetch('layouts/shim')?>
は<?php $this->insert('layouts/shim')?>
と書くこともできる。
full_titleヘルパー関数
手順は前後するけど、Rails Tutorialの中にfull_titleというヘルパー関数が出てくるのでこれを実装してみよう。
<?php // web/index.php ... $app['view'] = $app->share(function () { $engine = new League\Plates\Engine(__DIR__ . '/../templates'); $engine->registerFunction('full_title', function ($title) { $base_title = 'Ruby on Rails Tutorial'; if (empty($title)) { return $base_title; } return "$base_title | $title"; }); return $engine; });
<!-- templates/layouts/application.php --> <html> <head> <title><?=$this->full_title($title)?></title> <link rel="stylesheet" type="text/css" href="app.css"> <?=$this->fetch('layouts/shim')?> </head> ...
以下のようにtitleに空文字以外を渡すと、Ruby on Rails Tutorial | Home
と表示され、
<!-- templates/home.php --> <?php $this->layout('layouts/application', ['title' => 'Home']) ?>
titleに空文字を渡すと、Ruby on Rails Tutorial
と表示される。
<!-- templates/home.php --> <?php $this->layout('layouts/application', ['title' => '']) ?>
ところで、titleはエスケープして出力したほうが良いだろう。つまりfull_titleを通してからescapeすることになる。 これにはいくつかやり方がある。
<?=$this->e($this->full_title($title))?> <?=$this->batch($title, 'full_title|e')?> <?=$this->e($title, 'full_title')?>
1つ目は説明するまでもないだろう。2つ目の $this->batch()
は、第二引数に複数のヘルパー関数名を |
で繋いで記述することができ、
これらが左から順番に実行されるというもの。3つ目も同じような意味で、$this->e()
関数のみ、$this->chan()
と同様に
第二引数に複数の関数名を|
で繋いで記述することができ、そして最後にエスケープが行われる。
titleのデフォルト値
またちょっと戻るのだけれど、titleを表示したくないときに、
<!-- templates/home.php --> <?php $this->layout('layouts/application', ['title' => '']) ?>
とやるのはちょっとカッコ悪い。しかしなにも渡さないと Undefined variable になってしまう。 レイアウト側でデフォルトを指定する方法も無くはなさそうだけど不格好なので、 Engine 側でデフォルト値を指定してしまおう。
<?php // web/index.php ... $app['view'] = $app->share(function () { $engine = new League\Plates\Engine(__DIR__ . '/../templates'); $engine->addData(['title' => '']); ...
こうすると、全てのテンプレートで title
に空文字列がセットされるので、home.php側からはtitleを渡す必要がなくなった。
<!-- templates/home.php --> <?php $this->layout('layouts/application') ?>