- 投稿日:2020-01-04T23:43:21+09:00
プログラミングをかじったからには何らかの制作物を作りたい#3 ~作り直し編、完成したもの~
説明
このエントリーは初心者がとりあえず何かを作りたいと考え、それのみを理由にして記述しているものの3です。そのため、技術的な誤りや勘違いが多分に含まれている可能性があります。ご了承くださいませ。もしよろしければご指摘やご教示を頂けましたら幸いです。
前回のあらすじ
プログラミングをかじったからには何らかの制作物を作りたい# 2
https://qiita.com/tatsuki1112/items/bf363ff7e0a1678eebd6
じゃんけん自体を書いたり、Bootstrapでページを一応レスポンシブ化した。作り直しとは
じつはこのじゃんけんサイトは最初PythonのFlaskで作成していた。しかしなんとなくやる気が起きずにだいぶ放置していた。その後、PHP、Laravelを使用している会社でインターンを行うこととなったので、勉強をかねてLaravelで作成しようと思い立ち作り直して再開した。
できたもの
このエントリーを行ったときにはこのじゃんけんサイトもうすでに完成していて、やったことの整理という意味で記述しています。
一応の成果物はこちらにあるので、お暇な際にでも遊んでいただけると幸いです。
https://www.mend0.top実際に使ったもの
Docker 19.03.5
Docker-compose 1.25.0
PHP 7.3.13
Laravel 6.3.0
nginx 1.17
mysql 8.0
jquery
Bootstrap 4conoha vps 最安プラン
centos 7.7こんな感じでしょうか
大いに参考にさせていただいたもの
いきなりDockerやLaravelの環境を構築するのは非常に困難なので、以下を参考に環境を整えました。
Laravelの開発環境をDockerを使って構築する
https://qiita.com/ucan-lab/items/17c806973e69792ada99Laravel 6.0 基本のタスクリスト
https://qiita.com/ucan-lab/items/36f6e89abad26a68f69aというかほとんどこちらのタスクリストを改変して作成したようなものです。偉大ですね。
あそびかた
それぞれの制作や公開の過程でどのような手順を踏んだかはともかくとして、とりあえずはどんな物ができたのかを御覧いただきたいと思います。
トップページ
強い手を探す
指定した回数じゃんけんを行い、そのなかで最も勝数が多かった手を表示するものです。じゃんけんの手を決める自信がないときに利用してください。
このように試行回数などとともに一番つよいじゃんけんの手を教えてくれます。
勝敗を決める
じゃんけんを行う際に、3回勝負などで決着を付ける場合があります。しかしもはや3回では納得ができない場合に利用してください。
例えばこの二者にじゃんけんをおこなわせると...回数を見る
こちらの画面ではいままでに行われたじゃんけんの総回数、最大回数、各手の勝利回数を見ることができます。それだけです。
レッツじゃんけん!!!!
実際にどのように作成したか、どういったところに苦労したかなどは次回以降記述します。
- 投稿日:2020-01-04T23:01:21+09:00
風水プログラミング
今日は趣向を変えて風水のプログラミングをしてみます。
風水?オカルト?
「そんなの風水師が適当なこと言ってるんじゃないの?」
と思う人もいるかもしれません。いいえ、そんなことはありません。
風水、九星気学、四柱推命は計算のアルゴリズムがしっかりと確立していて、入力が同じならば出力は同じになるという特徴を持っています。成立した経緯はわかりませんが、昔の中国の人がたくさんの運命を見てきて統計的な法則を見つけたけどわかりにくいから陰陽五行にあてはめてみたとかそういう類じゃないかと思います。
例えば、風水ならば、竣工年と建物の向きが決まれば、結果は一意に定まります。
四柱推命も誕生の年月日と時間で結果は一意に決まります。入力が決まれば出力も一意に決まる っていいですね。
そのアルゴリズムはちゃんとした本を読めば書いてあります。陰陽五行の話とかいろいろ出てくるのですが、結局のところは、
- 足し算や引き算をする
- テーブルを引く
- 割り算して余りを求める
- if文で分類して、パターンを減らす
ということを繰り返しています。
ロジカルで、まさにプログラミングにぴったりですね!
作ったプログラムの一部を示しましょう。
まずは基本のチャートを描くためのもの。$a = array(); $a[0] = $basenum + 8; $a[1] = $basenum + 4; $a[2] = $basenum + 6; $a[3] = $basenum + 7; $a[4] = $basenum + 0; $a[5] = $basenum + 2; $a[6] = $basenum + 3; $a[7] = $basenum + 5; $a[8] = $basenum + 1; $text = ""; for($i = 0; $i < 9;$i++ ) { $text .= strval(($a[$i] - 1) % 9 + 1); if($i != 8) $text .= ","; }次は、運星から山星・水星を求めるプログラム。ちょっと複雑になってくるので変数名がaとかだと破綻します。
$運星 = array(); // $運星["東南"] = 剰余($era + 8); // $運星["南" ] = 剰余($era + 4); // $運星["南西"] = 剰余($era + 6); // $運星["東" ] = 剰余($era + 7); // $運星["中央"] = 剰余($era + 0); // $運星["西"] = 剰余($era + 2); // $運星["北東"] = 剰余($era + 3); // $運星["北"] = 剰余($era + 5); // $運星["北西"] = 剰余($era + 1); // switch($向) { case "N1": case "N2": case "N3": $中央水星 = $運星["北"]; $中央山星 = $運星["南"]; break; case "NE1": case "NE2": case "NE3": $中央水星 = $運星["北東"]; $中央山星 = $運星["南西"]; break; case "E1": case "E2": case "E3": $中央水星 = $運星["西"]; $中央山星 = $運星["東"]; break; case "SE1": case "SE2": case "SE3": $中央水星 = $運星["東南"]; ・・・(中略)・・・ case "NW1": case "NW2": case "NW3": $中央水星 = $運星["北西"]; $中央山星 = $運星["東南"]; break; } $山星 = array(); if( (preg_match("/[23]/",$向) && !($中央山星 % 2)) // 偶数で区分2か3 || (preg_match("/[1]/",$向) && ($中央山星 % 2))) { // 奇数で区分1 $山星順行 = true; } else { $山星順行 = false; } $水星 = array(); if( (preg_match("/[23]/",$向) && !($中央水星 % 2)) // 偶数で区分2か3 || (preg_match("/[1]/",$向) && ($中央水星 % 2))) { // 奇数で区分1 $水星順行 = true; ・・・(中略)・・・ if($中央山星 == 5) { // 5は特別扱い if( (preg_match("/[23]/",$向) && !($era % 2)) // 偶数で区分2か3 || (preg_match("/[1]/",$向) && ($era % 2))) { // 奇数で区分1 $山星順行 = true; } else { $山星順行 = false; } }怪しげな変数名が並んでいます。
PHPは変数名に普通に漢字が使えるし、連想配列が使えるのでわかりやすく書けました。
もし、英数字しか使えないとどうやって英語に訳せばよいかわからなくて困っていたことでしょう。
こういう計算を延々とやっていくと、下の図のようなチャートが作られます。
こうして年飛星まで入ったチャートをPNGファイルで出力するPHPのプログラムが出来ました。
この数字を求めるところまでは機械的にできるのですが、数字を解釈するところに風水師の経験や勘といったものが必要になります。もちろん、私にはできません。
ある程度の一般的な解釈まではプログラムでもできるかもしれませんが、最終的には人間がいるのでしょう。
- 投稿日:2020-01-04T22:33:32+09:00
PHP7.4をCentOS8にインストール
はじめに
お正月を使って自宅色々兼用サーバを役割ごとに仮想化させました。
仮想化ベースはCentOS8/KVMで、本稼働VM部分とは別に検証し放題のテストVM環境も作ったので今後色々遊べそうです。
サーバ構築部分はいつか別途書こうと思います。
ちなみに現状VM4インスタンス同時起動でも、ほとんどの時間がアイドル状態なので35w程度の消費電力です。んで、PHP7.4.1が安定版になってるのでインストール用メモ。
例のごとくRemi、epelリポジトリからダウンロードします。インストール
リポジトリのインストール
# epel $ sudo dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm # -略-# remi $ sudo dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm # -略-PHP7.4.1のインストール
インストール可能なphpモジュールを確認。
$ sudo dnf module list php # -略-リストで確認できたremi-7.4を選択
$ sudo dnf module enable php:remi-7.4 ========= パッケージ アーキテクチャー バージョン リポジトリ サイズ ========= Enabling module streams: php remi-7.4必要なパッケージをインストール
$ sudo dnf install php php-cli php-common # -略-インストール確認
$ php -v PHP 7.4.1 (cli) (built: Dec 17 2019 16:35:58) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.1, Copyright (c), by Zend Technologies以上です。
- 投稿日:2020-01-04T21:18:50+09:00
Laravelでよく使うコマンド、記法
新規プロジェクト作成
$ laravel new (アプリ名)または
$ composer create-project laravel/laravel (アプリ名) --prefer-distartisanコマンド
コントローラの作成
$ php artisan make:controller (コントローラ名)マイグレーションの作成
$ php artisan make:migration create_(テーブル名)_tableモデルの作成
$ php artisan make:model (モデル名)シーダーの作成
$ php artisan make:seeder (テーブル名)TableSeederマイグレーションとシーダーの反映
$ php artisan migrate --seedデータベースのリフレッシュ
$ php artisan migrate:resetweb.php記法
getメソッド
routes/web.phpRoute::get('(相対パス)', function () { // 処理 });routes/web.phpRoute::get('(相対パス)', 'コントローラ名@関数')->name('名前');postメソッド
routes/web.phpRoute::post('(相対パス)', 'コントローラ名@関数')->name('名前');URLに変数を混ぜる
routes/web.phpRoute::get('/hoge/{id}', 'コントローラ名@関数')->name('名前');routes/web.phpRoute::get('/hoge/{id?}', 'コントローラ名@関数')->name('名前');マイグレーション記法
Schema::table('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); });カラムタイプ
コマンド 説明 $table->bigIncrements('id');
BIGINT $table->integer('votes');
integer(整数) $table->string('name', 100);
string(文字列) $table->float('amount', 8, 2);
float(実数) $table->boolean('confirmed');
boolean(真偽) $table->date('created_at');
date(日付) $table->timestamp('added_on');
timestamp(時間) カラム修飾子
コマンド 説明 ->unique()
uniqueキー追加 ->nullable()
NULL値を許容 モデル記法
fillable
create()やupdate()を受け付けるホワイトリスト
protected $fillable = [ 'name', 'email', 'password' ];guarded
create()やupdate()を受け付けないブラックリスト
protected $guarded = [ 'create_at', 'update_at' ];hidden
パスワードなどの秘匿性の高いカラム
protected $hidden = [ 'password', 'remember_token' ];シーダー記法
database/seeds/DatabaseSeeder.php<?php use Illuminate\Database\Seeder; use Illuminate\Database\Eloquent\Model; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { Model::unguard(); $this->call('Seeder名'); Model::reguard(); } }<?php use Illuminate\Database\Seeder; use App\モデル名; class MemoTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('テーブル名')->delete(); //最初に全件削除 (モデル名)::create([ 'name' => 'aaa', 'email' => 'aaa@example.com' ]); } }データの取得
全件取得
$items = Item::get();最初の要素だけ取得
$item = Item::first();id指定で取得
idというカラムがある場合のみ使用可能
$id = 1; $item = Item::find($id); // 配列ではなく要素$items = Item::find([1, 3]); // 長さ2の配列条件付きでデータを取得
nameというカラムの値が「名前1」というデータを全件取得する。
$items = Item::where('name', '名前1')->get();AND/OR検索
AND検索
15歳の鈴木さんのデータのみ取得する。
$items = Item::where('age', 15) ->where('name', '鈴木') ->get();$items = Item::where([ ['age', 15], ['name', '鈴木'] ])->get();OR検索
鈴木さんと山田さんのデータを取得する。
$items = Item::where('name', '鈴木') ->orWhere('name', '山田') ->get();特定の文字列を含んだデータの取得
nameに「川」という文字列を含むデータを全件取得する。
$items = Item::where('name', 'like', '%川%')->get();比較によるデータの取得
idが5よりも大きいデータを全件取得する。
$items = Item::where('id', '>', 5)->get();idが5以下のデータを全件取得する。
$items = Item::where('id', '<=', 5)->get();時間情報で検索
日付で検索
$items = Item::whereDate('created_at', '2020-01-04')->get();年で検索
$items = Item::whereYear('created_at', '2020')->get();月で検索
$items = Item::whereDate('created_at', '1')->get();日で検索
$items = Item::whereDay('created_at', '4')->get();並び替えて取得
昇順 (1, 2, 3, 4)
$items = Item::orderBy('id', 'asc')->get();降順 (4, 3, 2, 1)
$items = Item::orderBy('id', 'desc')->get();集約したデータの取得
件数の取得
$count = Item::count();最大値の取得
idが一番大きいデータを取得
$item = Item::max('id');データの編集や削除
データの更新
nameを鈴木に変更する
$item = Item::first(); $item->update([ 'name' => '鈴木' ]);データの追加
25歳の鈴木さんのデータを追加する
Item::create([ 'name' => '鈴木', 'age' => 25 ]);データの削除
全件削除
Item::get()->delete();特定のデータのみ削除
Item::where('id', 2)->delete();id指定で削除
idというカラムがある場合のみ使用可能
$id = 1; Item::destroy($id);複数削除できる
Item::destroy([1, 3]);コントローラ記法
class コントローラ名 extends Controller { public function 関数名() { // 処理 } }web.phpでURLに変数を含めた場合、引数としてその変数を受け取ることができる。
class コントローラ名 extends Controller { public function 関数名($変数名) { // 処理 } }Viewの表示
Viewの名前はresources/views/からのパスを記述する。
return view("Viewの名前");Viewにデータを渡す。
return view("Viewの名前", ['item' => $item]);リダイレクト
web.phpのnameメソッドで指定した名前を入れる。
return redirect()->route('名前');URLに変数がある場合
return redirect()->route('名前', ['item' => $item]);formで送信されたデータの処理
$request->input()
で取得可能public function 関数名(Request $request) { $name = $request->input('name'); }
- 投稿日:2020-01-04T20:49:08+09:00
Uncaught Error: Call to undefined function~が出た時は
前回のあらすじ
アンケートシステム作ってるなう
・タグでいたずらされないようにしてた。今日やったこと(ハイライト)
・XAMPPでMySQLのDBを構築した
・DBにアンケートデータが自動で保存されるよう頑張った
(今回の教科書『いきなりはじめるPHP ワクワク・ドキドキの入門教室』)困ったこと
・Uncaught Error: Call to undefined function mb_internal_encording()~~って名前のエラーが出た
原因
mb_internal_enco「 r 」ding スペルミスってた!!
mb_internal_encoding 直した!!やったね!!感想
自動保存の機能を追加するためのソースを書いてる上で、文字列の連結を理解するのが難しかった。てことで、明日以降の私に丸投げした。
次回の目標
アンケートシステムをもっと便利にするよう学習していくぞい
- 投稿日:2020-01-04T19:00:00+09:00
Atom の PHP 補完を vim-lsp で使う
Vim で PHP を書くときは vim-lsp で Intelephense を使っているのですが、プロプライエタリなソフトウェアなのでサーバサイドのコードが公開されていません。Intelephense のウェブサイトはあまり親切ではなく、初めてインストールしたときは正しく動かせているのかよく分からなかった事を覚えています。
そのため OSS の PHP LSP を探していたところ Serenata というソフトウェアを見つけました。Serenata は Atom エディタのプラグインとして公開されていたようです。サーバ型の Serenata 本体と、Atom プラグインの php-ide-serenata の2つで構成されています。バージョン4までは Atom エディタ用ですがバージョン5で Language Server Protocol をサポートしたようです。
対応している LSP の機能は Language Server Protocol Support Table のページ で確認できます。php-ide-serenataの動作画面
今回はこの Serenata をvim-lspから使えるように設定してみました。
Serenata はTCPソケットしかサポートされていなかったので、vim-lsp の標準入出力とつなげるために
socat
コマンドを使いました。
ひとまず vim 側での補完やホバー表示ができるところまでは確認したのですが、速度面で難があったり、vim-lsp の起動initialize
/終了exit
とうまく連動できないので実用レベルには至りませんでした。 もう少し調査して使えるようにしたいのですが一旦ここまでの記録を公開します。事前準備
vim-lsp が動く状態になっていることが前提です。その他に必要なソフトウェアは以下の通りです。
- PHP 7.1+
- mbstring
- xml
- libxml
- dom
- openssl
- pdo_sqlite
socat
コマンド- vim
- vim-lsp
socat
コマンドがインストールされているか確認してください。macOS は brew でインストール可能です。$ which socat /usr/local/bin/socat $ socat -V socat by Gerhard Rieger and contributors - see www.dest-unreach.org socat version 1.7.3.3 on May 11 2019 02:21:21 ... $ brew install socat # インストールされていなければSerenata の導入
1. 実行可能な PHAR 形式のファイルをダウンロード
Serenata サーバをダウンロードしてください。インストール方法はいくつかありますが実行可能な PHAR 形式のファイルが簡単です。PHAR compatible with PHP 7.x を選択してください。
2. ポート
11111
で起動Serenata サーバをポート
11111
で起動してみましょう。Serenata はソースコードの解析結果をデータベースに保存します。デフォルト設定では SQLite をインメモリで使用します。メモリ使用量が多いのでphp
の起動オプションでメモリを多めに確保します。$ php -d memory_limit=2048M ~/bin/serenata.phar --uri tcp://127.0.0.1:11111 Starting server bound to socket on URI tcp://127.0.0.1:11111...正常に起動できれば
Starting server bound ....
と表示されて LISTEN 状態になります。3. 動作確認
動作確認のために
initialize
メソッドを実行してみます。socat
を使って Serenata サーバのポート11111
に送信します。Content-Length
には JSON のバイト数が書かれていますので、rootPath
rootUri
を変更したらContent-Length
も忘れずに変更してください。1バイトでも合わないと正しく動作しません。
また、保存するファイルのフォーマットにも注意してください。LSP の仕様で改行コードはCRLF
、ファイル最終行の行末には改行を含めないようにしてください。serenata.jsonrpcContent-Length: 1429 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":19002,"rootPath":"/Users/foobar/src/foobar/foobarbazfo/","rootUri":"file:///Users/foobar/src/foobar/foobarbazfo/","workspaceFolders":[],"capabilities":{"workspace":{"applyEdit":true,"configuration":false,"workspaceEdit":{"documentChanges":true},"workspaceFolders":false,"didChangeConfiguration":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":false},"symbol":{"dynamicRegistration":false},"executeCommand":{"dynamicRegistration":false}},"textDocument":{"synchronization":{"dynamicRegistration":false,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":false,"completionItem":{"snippetSupport":true,"commitCharactersSupport":false},"contextSupport":true},"hover":{"dynamicRegistration":false},"signatureHelp":{"dynamicRegistration":false},"references":{"dynamicRegistration":false},"documentHighlight":{"dynamicRegistration":false},"documentSymbol":{"dynamicRegistration":false,"hierarchicalDocumentSymbolSupport":true},"formatting":{"dynamicRegistration":false},"rangeFormatting":{"dynamicRegistration":false},"onTypeFormatting":{"dynamicRegistration":false},"definition":{"dynamicRegistration":false},"codeAction":{"dynamicRegistration":false},"codeLens":{"dynamicRegistration":false},"documentLink":{"dynamicRegistration":false},"rename":{"dynamicRegistration":false}},"experimental":{}}}}動作確認するだけならパスの変更は不要です
実行して
Content-Length
で始まるレスポンスが返ってくれば成功です。$ socat stdio tcp4:127.0.0.1:11111,shut-none < serenata.jsonrpc Content-Length: 951 {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"textDocumentSync":{"openClose":false,"change":1,"willSave":false,"willSaveWaitUntil":false,"save":{"includeText":true}},"hoverProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":null},"signatureHelpProvider":{"triggerCharacters":["(",","]},"definitionProvider":true,"typeDefinitionProvider":false,"implementationProvider":false,"referencesProvider":false,"documentHighlightProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":false,"codeActionProvider":false,"codeLensProvider":{"resolveProvider":true},"documentFormattingProvider":false,"documentRangeFormattingProvider":false,"documentOnTypeFormattingProvider":null,"renameProvider":false,"documentLinkProvider":null,"colorProvider":false,"foldingRangeProvider":false,"executeCommandProvider":null,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"experimental":null}}}vim-lsp 設定
1.
lsp#register_server
の設定Serenata が正しく動いていることが確認できたので vim-lsp の設定を追加してください。
au User lsp_setup call lsp#register_server({ \ 'name': 'serenata', \ 'cmd': {server_info->['socat', 'stdio', 'tcp4:127.0.0.1:11111,shut-none']}, \ 'initialization_options': {"rootPath":"/Users/foobar/src/foobar/foobarbazfo/","rootUri":"file:///Users/foobar/src/foobar/foobarbazfo/"}, \ 'whitelist': ['php'], \ 'workspace_config': { 'serenate': { \ 'files.associations': ['*.php'], \ }}, \ })2. 動作確認
Serenataサーバを起動した状態で vim で PHP のファイルを開きます。vim-lsp が Serenata サーバに正しく接続できれば
:LspStatus
の実行結果がserenata: running
になります。vim-lsp と Serenata の起動・終了の仕様に齟齬があるので、vim を終了した場合は Serenata サーバも再起動しないとLspStatus
の結果がfailed
になります。まとめ
Serenata はサーバ型なので LSP の
exit
メソッド実行されても終了せず、vim-lsp が再度initialize
メソッドを実行するとエラーになります。速度も Atom エディタで使うより遅いのでもう少し改善の余地がありそうです。
また成果があれば公開します。その他のスクリーンショット
- 投稿日:2020-01-04T14:17:06+09:00
Laravelで簡単なメモアプリを作る(3)~データの更新と削除~
はじめに
Laravelとは、Webに特化したPHPフレームワーク。セキュリティ対策(CSRF対策など)もこれでできます。MVCアーキテクチャを採用しています。今回は、データの更新と削除をしていきたいと思います。
Laravelで簡単なメモアプリを作る(1)~Viewの作成と表示~
Laravelで簡単なメモアプリを作る(2)~データの取得と保存~今回の目標物
データの更新
更新するデータの取得
id=1のデータを取得する時は、このように書く。
$memo = Memo::find(1);whereメソッドでも、取得することができる。ただ、whereメソッドは、findメソッドと異なって、条件に合うものを全て取ってくるため、たとえ条件に合うものが一つであったとしても配列として取得するので注意。なのでfirstメソッドで、要素として取得する必要がある。
$memo = Memo::where('id', 1)->first();データの更新
updateメソッドで、データベース上のデータを書き換えることができる。
$memo->update([ 'title' => $title, 'content' => $content ]);MemoControllerを以下のように編集する。
app/Http/Controllers/MemoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Memo; class MemoController extends Controller { public function showHome() { $memos = Memo::get(); return view("home", ['memos' => $memos]); } public function showSubmit($id = 0) { if ($id != 0) { $memo = Memo::where('id', $id)->get()->first(); } else { $memo = (object) ["id" => 0, "title" => "", "content" => ""]; } return view("submit", ['memo' => $memo]); } public function postSubmit(Request $request, $id = 0) { $title = $request->input('title'); $content = $request->input('content'); if ($id == 0) { Memo::create([ 'title' => $title, 'content' => $content ]); } else { $memo = Memo::find($id); $memo->update([ 'title' => $title, 'content' => $content ]); } return redirect()->route('home'); } }データの削除
メモを削除する関数の作成
destroyメソッドを使うと、引数に指定したidのデータだけが削除される。下のコードをMemoControllerに追加する。
public function deleteMemo($id) { Memo::destroy($id); return redirect()->route('home'); }web.phpにdeleteMemoメソッドの登録
/delete/{id}
というURLを受け取ったら、メモが削除されるようにする。下のコードをweb.phpに追加する。Route::get('/delete/{id}', 'MemoController@deleteMemo')->name('delete');削除ボタンを押したら削除されるようにする
resources/views/home.blade.phpの削除ボタンを以下のように修正する。
<td><a href="{{ route('delete', ['id' => $memo->id])}}">削除</a></td>おまけ
モーダルを使って、削除の確認をする
bootstrap4のモーダルを使用する。
https://getbootstrap.com/docs/4.3/components/modal/resources/views/home.blade.php@extends('layouts.app') @section('css') <style> header { height: 50px; background-color: #000; color: white; padding-left: 20px; font-size: large; color: #ddd; } .title { position: absolute; top: 10px; } .card { margin-top: 40px; } .left { width: 70%; } .submit { position: absolute; top: 10px; right: 20px; } </style> @endsection @section('content') <div class="card" style="width: 100%;"> <div class="card-header"> メモ一覧 <a href="{{ route('submit')}}" class="submit">メモを追加</a> </div> <table class="table"> <tbody> @foreach ($memos as $memo) <tr> <td class="left">{{$memo->title}}</td> <td><a href="{{ route('submit', ['id' => $memo->id])}}">編集</a></td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr> <!-- Modal --> <div class="modal fade" id="modal{{$memo->id}}" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">以下のメモを本当に削除しますか?</h5> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> {{$memo->title}} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <button type="button" class="btn btn-primary" onclick="location.href='{{ route('delete', ['id' => $memo->id])}}'"> 削除 </button> </div> </div> </div> </div> @endforeach </tbody> </table> </div> @endsectionsubmitページのボタンを新規作成の時と編集の時で変える
新規作成の時は
$memo->id == 0
で、編集の時は$memo->id != 0
なので、if文で条件分岐する。
resources/views/submit.blade.phpの追加ボタンを以下のように書き換える。@if($memo->id == 0) <button type="submit" class="btn btn-success">追加</button> @else <button type="submit" class="btn btn-success">適用</button> @endifテーブルの列をクリックしたら、詳細画面に飛べるようにする
下のコードを、app.blade.phpのbodyタグの一番下に追加する。
resources/views/layouts/app.blade.php<script> jQuery(function($) { $('tbody tr[data-href]').addClass('clickable').click(function() { window.location = $(this).attr('data-href'); }).find('a').hover(function() { $(this).parents('tr').unbind('click'); }, function() { $(this).parents('tr').click( function() { window.location = $(this).attr('data-href'); }); }); }); </script>home.blade.phpの編集ボタンを消して、trタグにdata-href属性を追加する。
resources/views/home.blade.php<tr data-href="{{route('submit', ['id' => $memo->id])}}"> <td class="left">{{$memo->title}}</td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr>テーブルの列をホバーすると色が変わるようにする
trタグに
class="table-row"
を追加する。resources/views/home.blade.php<tr data-href="{{route('submit', ['id' => $memo->id])}}" class="table-row"> <td class="left">{{$memo->title}}</td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr>resources/views/home.blade.php.table-row { background-color: #fff; } .table-row:hover { background-color: #ddd; }追加ボタンをいい感じにする。ヘッダーも固定する。
app.blade.phpでFont Awesomeを読み込む。
resources/views/layouts/app.blade.php<link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">app.blade.phpを以下のように編集する。
resources/views/layouts/app.blade.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>MemoApp</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.0/css/bootstrap.min.css" integrity="sha384-SI27wrMjH3ZZ89r4o+fGIJtnzkAnFs3E4qz9DIYioCQ5l9Rd/7UAa8DHcaL8jkWt" crossorigin="anonymous"> <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"> </script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"> </script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.0/js/bootstrap.min.js" integrity="sha384-3qaqj0lc6sV/qpzrc1N5DC6i1VRn/HyX4qdPaiEFbn54VjQBEU341pvjz7Dv3n6P" crossorigin="anonymous"> </script> </head> <style> header { background-color: #000; background-position: 0px 50px; padding-left: 20px; font-size: large; width: 100%; height: 50px; clear: both; position: fixed; top: 0; margin-top: 0px; z-index: 1; } .title { position: absolute; top: 10px; color: #fff; } .container { margin-top: 70px; } </style> @yield('css') <body> <header> <span class="title">MemoApp</span> </header> <div class="container"> @yield('content') </div> <script> jQuery(function($) { $('tbody tr[data-href]').addClass('clickable').click(function() { window.location = $(this).attr('data-href'); }).find('a').hover(function() { $(this).parents('tr').unbind('click'); }, function() { $(this).parents('tr').click( function() { window.location = $(this).attr('data-href'); }); }); }); </script> </body> </html>home.blade.phpを以下のように編集する。
resources/views/home.blade.php@extends('layouts.app') @section('css') <style> .table-row { background-color: #fff; } .table-row:hover { background-color: #ddd; } .card { margin-top: 40px; } .circle-button { position: fixed; display: inline-block; text-decoration: none; color: rgba(152, 152, 152, 0.43); width: 80px; height: 80px; line-height: 80px; font-size: 40px; border-radius: 50%; text-align: center; overflow: hidden; font-weight: bold; background-image: linear-gradient(#e8e8e8 0%, #d6d6d6 100%); text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.66); box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.5), 0 2px 2px rgba(0, 0, 0, 0.19); border-bottom: solid 2px #b5b5b5; } .circle-button i { line-height: 80px; } .circle-button:active { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 2px 2px rgba(0, 0, 0, 0.19); border-bottom: none; } @media screen and (min-width:0px) { .circle-button { bottom: 20px; right: 30px; } } @media screen and (min-width:768px) and (max-width:1024px) { .circle-button { bottom: 20px; right: 50px; } } @media screen and (min-width:1024px) { .circle-button { bottom: 30px; right: 150px; } } .delete-button { display: inline-block; text-decoration: none; width: 20px; height: 20px; line-height: 20px; font-size: 7px; border-radius: 50%; text-align: center; overflow: hidden; background-color: #cd5c5c; margin-right: 10px; margin-top: 5px; } .delete-button i { line-height: 20px; color: #fff; } .td-left { width: 10px; } </style> @endsection @section('content') <div class="card" style="width: 100%;"> <div class="card-header"> メモ一覧 </div> <table class="table"> <tbody> @foreach ($memos as $memo) <tr data-href="{{route('submit', ['id' => $memo->id])}}" class="table-row"> <td class="td-left"> <a class="delete-button" type="button" data-toggle="modal" data-target="#modal{{$memo->id}}"> <i class="fa fa-minus"></i> </a> </td> <td class="td-right"> {{$memo->title}} </td> </tr> <!-- Modal --> <div class="modal fade" id="modal{{$memo->id}}" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">以下のメモを本当に削除しますか?</h5> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> {{$memo->title}} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <button type="button" class="btn btn-primary" onclick="location.href='{{ route('delete', ['id' => $memo->id])}}'"> 削除 </button> </div> </div> </div> </div> @endforeach </tbody> </table> </div> <a href="{{route('submit')}}" class="circle-button"> <i class="fa fa-plus"></i> </a> @endsectionsubmit.blade.phpのcss部分を消しておく。inputもついでに文字数制限をかける。
resources/views/submit.blade.php@extends('layouts.app') @section('content') <form method="POST" action="{{ route('submit', ['id' => $memo->id])}}"> @csrf <div class="form-group"> <label for="title">タイトル</label> <input type="text" class="form-control" id="title" name="title" value="{{$memo->title}}" maxlength="24" required> </div> <div class="form-group"> <label for="content">内容</label> <input type="text" class="form-control" id="content" name="content" value="{{$memo->content}}" maxlength="60" required> </div> <a href="{{ route('home')}}" class="btn btn-primary">戻る</a> @if($memo->id == 0) <button type="submit" class="btn btn-success">追加</button> @else <button type="submit" class="btn btn-success">適用</button> @endif </form> @endsection今回はここまで。次回は、ログイン機能を実装します。
- 投稿日:2020-01-04T14:17:06+09:00
Laravelで簡単なメモアプリを作る(3)~データの編集と削除~
はじめに
Laravelとは、Webに特化したPHPフレームワーク。セキュリティ対策(CSRF対策など)もこれでできます。MVCアーキテクチャを採用しています。今回は、データの編集と削除をしていきたいと思います。
Laravelで簡単なメモアプリを作る(1)~Viewの作成と表示~
Laravelで簡単なメモアプリを作る(2)~データの取得と保存~今回の目標物
データの編集
編集するデータの取得
id=1のデータを取得する時は、このように書く。
$memo = Memo::find(1);whereメソッドでも、取得することができる。ただ、whereメソッドは、findメソッドと異なって、条件に合うものを全て取ってくるため、たとえ条件に合うものが一つであったとしても配列として取得するので注意。なのでfirstメソッドで、要素として取得する必要がある。
$memo = Memo::where('id', 1)->first();データの編集
updateメソッドで、データベース上のデータを書き換えることができる。
$memo->update([ 'title' => $title, 'content' => $content ]);MemoControllerを以下のように編集する。
app/Http/Controllers/MemoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Memo; class MemoController extends Controller { public function showHome() { $memos = Memo::get(); return view("home", ['memos' => $memos]); } public function showSubmit($id = 0) { if ($id != 0) { $memo = Memo::where('id', $id)->get()->first(); } else { $memo = (object) ["id" => 0, "title" => "", "content" => ""]; } return view("submit", ['memo' => $memo]); } public function postSubmit(Request $request, $id = 0) { $title = $request->input('title'); $content = $request->input('content'); if ($id == 0) { Memo::create([ 'title' => $title, 'content' => $content ]); } else { $memo = Memo::find($id); $memo->update([ 'title' => $title, 'content' => $content ]); } return redirect()->route('home'); } }データの削除
メモを削除する関数の作成
destroyメソッドを使うと、引数に指定したidのデータだけが削除される。下のコードをMemoControllerに追加する。
public function deleteMemo($id) { Memo::destroy($id); return redirect()->route('home'); }web.phpにdeleteMemoメソッドの登録
/delete/{id}
というURLを受け取ったら、メモが削除されるようにする。下のコードをweb.phpに追加する。Route::get('/delete/{id}', 'MemoController@deleteMemo')->name('delete');削除ボタンを押したら削除されるようにする
resources/views/home.blade.phpの削除ボタンを以下のように修正する。
<td><a href="{{ route('delete', ['id' => $memo->id])}}">削除</a></td>おまけ
モーダルを使って、削除の確認をする
bootstrap4のモーダルを使用する。
https://getbootstrap.com/docs/4.3/components/modal/resources/views/home.blade.php@extends('layouts.app') @section('css') <style> header { height: 50px; background-color: #000; color: white; padding-left: 20px; font-size: large; color: #ddd; } .title { position: absolute; top: 10px; } .card { margin-top: 40px; } .left { width: 70%; } .submit { position: absolute; top: 10px; right: 20px; } </style> @endsection @section('content') <div class="card" style="width: 100%;"> <div class="card-header"> メモ一覧 <a href="{{ route('submit')}}" class="submit">メモを追加</a> </div> <table class="table"> <tbody> @foreach ($memos as $memo) <tr> <td class="left">{{$memo->title}}</td> <td><a href="{{ route('submit', ['id' => $memo->id])}}">編集</a></td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr> <!-- Modal --> <div class="modal fade" id="modal{{$memo->id}}" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">以下のメモを本当に削除しますか?</h5> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> {{$memo->title}} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <button type="button" class="btn btn-primary" onclick="location.href='{{ route('delete', ['id' => $memo->id])}}'"> 削除 </button> </div> </div> </div> </div> @endforeach </tbody> </table> </div> @endsectionsubmitページのボタンを新規作成の時と編集の時で変える
新規作成の時は
$memo->id == 0
で、編集の時は$memo->id != 0
なので、if文で条件分岐する。
resources/views/submit.blade.phpの追加ボタンを以下のように書き換える。@if($memo->id == 0) <button type="submit" class="btn btn-success">追加</button> @else <button type="submit" class="btn btn-success">適用</button> @endifテーブルの列をクリックしたら、詳細画面に飛べるようにする
下のコードを、app.blade.phpのbodyタグの一番下に追加する。
resources/views/layouts/app.blade.php<script> jQuery(function($) { $('tbody tr[data-href]').addClass('clickable').click(function() { window.location = $(this).attr('data-href'); }).find('a').hover(function() { $(this).parents('tr').unbind('click'); }, function() { $(this).parents('tr').click( function() { window.location = $(this).attr('data-href'); }); }); }); </script>home.blade.phpの編集ボタンを消して、trタグにdata-href属性を追加する。
resources/views/home.blade.php<tr data-href="{{route('submit', ['id' => $memo->id])}}"> <td class="left">{{$memo->title}}</td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr>テーブルの列をホバーすると色が変わるようにする
trタグに
class="table-row"
を追加する。resources/views/home.blade.php<tr data-href="{{route('submit', ['id' => $memo->id])}}" class="table-row"> <td class="left">{{$memo->title}}</td> <td><a type="button" data-toggle="modal" data-target="#modal{{$memo->id}}">削除</a></td> </tr>resources/views/home.blade.php.table-row { background-color: #fff; } .table-row:hover { background-color: #ddd; }追加ボタンをいい感じにする。ヘッダーも固定する。
app.blade.phpでFont Awesomeを読み込む。
resources/views/layouts/app.blade.php<link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">app.blade.phpを以下のように編集する。
resources/views/layouts/app.blade.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>MemoApp</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.0/css/bootstrap.min.css" integrity="sha384-SI27wrMjH3ZZ89r4o+fGIJtnzkAnFs3E4qz9DIYioCQ5l9Rd/7UAa8DHcaL8jkWt" crossorigin="anonymous"> <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"> </script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"> </script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.0/js/bootstrap.min.js" integrity="sha384-3qaqj0lc6sV/qpzrc1N5DC6i1VRn/HyX4qdPaiEFbn54VjQBEU341pvjz7Dv3n6P" crossorigin="anonymous"> </script> </head> <style> header { background-color: #000; background-position: 0px 50px; padding-left: 20px; font-size: large; width: 100%; height: 50px; clear: both; position: fixed; top: 0; margin-top: 0px; z-index: 1; } .title { position: absolute; top: 10px; color: #fff; } .container { margin-top: 70px; } </style> @yield('css') <body> <header> <span class="title">MemoApp</span> </header> <div class="container"> @yield('content') </div> <script> jQuery(function($) { $('tbody tr[data-href]').addClass('clickable').click(function() { window.location = $(this).attr('data-href'); }).find('a').hover(function() { $(this).parents('tr').unbind('click'); }, function() { $(this).parents('tr').click( function() { window.location = $(this).attr('data-href'); }); }); }); </script> </body> </html>home.blade.phpを以下のように編集する。
resources/views/home.blade.php@extends('layouts.app') @section('css') <style> .table-row { background-color: #fff; } .table-row:hover { background-color: #ddd; } .card { margin-top: 40px; } .circle-button { position: fixed; display: inline-block; text-decoration: none; color: rgba(152, 152, 152, 0.43); width: 80px; height: 80px; line-height: 80px; font-size: 40px; border-radius: 50%; text-align: center; overflow: hidden; font-weight: bold; background-image: linear-gradient(#e8e8e8 0%, #d6d6d6 100%); text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.66); box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.5), 0 2px 2px rgba(0, 0, 0, 0.19); border-bottom: solid 2px #b5b5b5; } .circle-button i { line-height: 80px; } .circle-button:active { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 2px 2px rgba(0, 0, 0, 0.19); border-bottom: none; } @media screen and (min-width:0px) { .circle-button { bottom: 20px; right: 30px; } } @media screen and (min-width:768px) and (max-width:1024px) { .circle-button { bottom: 20px; right: 50px; } } @media screen and (min-width:1024px) { .circle-button { bottom: 30px; right: 150px; } } .delete-button { display: inline-block; text-decoration: none; width: 20px; height: 20px; line-height: 20px; font-size: 7px; border-radius: 50%; text-align: center; overflow: hidden; background-color: #cd5c5c; margin-right: 10px; margin-top: 5px; } .delete-button i { line-height: 20px; color: #fff; } .td-left { width: 10px; } </style> @endsection @section('content') <div class="card" style="width: 100%;"> <div class="card-header"> メモ一覧 </div> <table class="table"> <tbody> @foreach ($memos as $memo) <tr data-href="{{route('submit', ['id' => $memo->id])}}" class="table-row"> <td class="td-left"> <a class="delete-button" type="button" data-toggle="modal" data-target="#modal{{$memo->id}}"> <i class="fa fa-minus"></i> </a> </td> <td class="td-right"> {{$memo->title}} </td> </tr> <!-- Modal --> <div class="modal fade" id="modal{{$memo->id}}" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">以下のメモを本当に削除しますか?</h5> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> {{$memo->title}} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <button type="button" class="btn btn-primary" onclick="location.href='{{ route('delete', ['id' => $memo->id])}}'"> 削除 </button> </div> </div> </div> </div> @endforeach </tbody> </table> </div> <a href="{{route('submit')}}" class="circle-button"> <i class="fa fa-plus"></i> </a> @endsectionsubmit.blade.phpのcss部分を消しておく。
resources/views/submit.blade.php@extends('layouts.app') @section('content') <form method="POST" action="{{ route('submit', ['id' => $memo->id])}}"> @csrf <div class="form-group"> <label for="title">タイトル</label> <input type="text" class="form-control" id="title" name="title" value="{{$memo->title}}"> </div> <div class="form-group"> <label for="content">内容</label> <input type="text" class="form-control" id="content" name="content" value="{{$memo->content}}"> </div> <a href="{{ route('home')}}" class="btn btn-primary">戻る</a> @if($memo->id == 0) <button type="submit" class="btn btn-success">追加</button> @else <button type="submit" class="btn btn-success">適用</button> @endif </form> @endsection今回はここまで。次回は、ログイン機能を実装します。
- 投稿日:2020-01-04T10:28:53+09:00
PHPでは未定義の定数は文字列として処理される
要約
- PHP では 未定義の定数 は 文字列 として処理続行する
- Warning を無視するべからず
- ついに PHP 8 からは動かなくなる
- みんな PHPStan を使おう!
問題
突然ですが、PHP でこう書いたとき、どのように動くでしょうか?
echo date(Ymd);これを書いた人は、おそらく
date("Ymd")
と書きたかったのでしょう。うん、 気持ちは 解る。
一見すると、ダブルクォーテーションが前後にないので、エラー終了しそうです。
ところが、実際には 警告は出力されるが処理続行します。
PHP Warning: Use of undefined constant Ymd - assumed 'Ymd' (this will throw an Error in a future version of PHP) in test.php on line 3 20191231理由
未定義の定数?
処理結果を見てみると、
Use of undefined constant Ymd
(未定義の 定数 Ymd の使用)と書いてあります。どうやら、PHP は
Ymd
を 定数 として認識しているようです。定数というと「全て大文字のアルファベット」という慣習に親しみすぎて、それ以外は定数でないかのように思ってしまいますが、定数名の定義は以下のとおりです( PHP マニュアル )。
有効な定数の名前は、 文字またはアンダースコアで始まり、任意の数の文字、数字、 アンダースコアが後に続きます。
なるほど。たしかに
Ymd
は定数ですね。なんで処理続行したの?
しかし、先程のコードは、定数
Ymd
が未定義であるにもかかわらず、処理続行しています。試しに、こんな風に直接
var_dump()
してみましょうか。var_dump(Ymd); // --> string(3) "Ymd"未定義の定数なのに 文字列 として処理されている!
こんな仕様あるの?
はい。ちゃんと マニュアル にも書いてありました。
未定義の定数を使用した場合、ちょうどstringとして コールしたかのように(CONSTANT vs "CONSTANT")、 PHPはその定数自体の名前を使用したと仮定します。 このような振る舞いは、PHP 7.2.0 以降では非推奨となり、E_WARNING が発生するようになりました。 (それより前のバージョンでは、 E_NOTICE が発生していました)
ここで重要なのは、
E_NOTICE
だろうがE_WARNING
だろうが 処理は続行する ということです。問題2
先程は、たまたま期待どおり動いていました。試しに、こう書いてみたら、どうでしょうか? ログファイルに現在時刻を書き込んでいくだけの簡単なプログラムです。
define("ERROR_LOG_FILE", "error.log"); // 定数名間違えちゃった! $fp = fopen(EROR_LOG_FILE, "a"); fwrite($fp, date("Ymd H:i:s") . PHP_EOL); fclose($fp);これを実行すると、ディレクトリの中に
EROR_LOG_FILE
というファイルが作られます。だいたい、こういうのは単体テストで見つかるのですが、1回、そのままリリースしてしまったことがあってですね・・・1
テスト担当者は なんかたくさん Warning が出てるなー ぐらいにしか思わなかったらしく・・・
たかが Warning、されど Warning 。Warning を無視するべからず。
PHPStan
最近の IDE は優秀なので、未定義の定数を書くと、未定義だよと教えてくれます。
VSCode だと、定数の下に赤い波線を出してくれたり。
でも、それはまだまだ人力に頼ってしまっていると思います。もっと機械的にできないものでしょうか。そこで、 静的解析の出番です!!!(ドンドン)
例えば、冒頭のコードを PHPStan で静的解析させてみましょう。
echo date(Ymd);静的解析すると、以下のようにエラーが表示されます(実際の解析結果は こちら )。
+--------------------------------+ | Line | test.php | +--------------------------------+ | 3 | Constant Ymd not found. | +--------------------------------+こうして地上に平和がもたらされたわけです。IN TERRA PAX!
今後
PHP 7.2 から、Warning に
this will throw an Error in a future version of PHP
と表示されるようになりました。
そして、ついに、PHP 8 では、もはや処理続行を許さず、 Error として例外が送出されるようになります。ちなみに、この件を扱っている RFC (PHP RFC: Deprecate and Remove Bareword (Unquoted) Strings)を見ると、以下のようなコードが載っています。
$foo = flase; // typo! // ... if ( $foo ) { var_dump($foo); // string(5) "flase" }
$foo
がfalse
ならば、if
文の中のコードは実行されないはずです。なのにflase
とタイプミスしたせいで真逆の結果になるという・・・。おそろしい・・・。RFC によると、この奇妙な動作は、1998 年にリリースされた PHP 3.0 で導入されたそうです。どうして PHP 3.0 で導入されたのか?
The old source code also includes the documentation with which PHP 3 shipped, which seem to have no mention of this behaviour, and no examples which take advantage of it.
古いソースコードには、そのときのドキュメントも含まれている。だけど、この動作については何も言及されていないし、それを使った例も記載されていないようだ。
何も言えねえ・・・
こうして、約 20 年間、数多の開発者が陥ってきた落とし穴は、全会一致(41 vs 0)で廃止されることになりました。南無〜
余談
PHPStan で以下のコードを静的解析してみました(実際の解析結果は こちら )。
define("ERROR_LOG_FILE", "error.log"); // 定数名間違えちゃった! $fp = fopen(EROR_LOG_FILE, "a"); fwrite($fp, date("Ymd H:i:s") . PHP_EOL); fclose($fp);すると、このように出力されたのですが、6行目と7行目がどうしてエラーになるのかが分からないです。
$fp
は明らかにresource
型だと思うんですけどねぇ・・・+------------------------------------------------------------------------------------+ | Line | test.php | +------------------------------------------------------------------------------------+ | 5 | Constant EROR_LOG_FILE not found. | | 6 | Parameter #1 $fp of function fwrite expects resource, resource|false given. | | 7 | Parameter #1 $fp of function fclose expects resource, resource|false given. | +------------------------------------------------------------------------------------+
「テストコード書いてないの?」とツッコミがありそうなので、先に書いておきます。令和のこの時代にPHP5.3を使っているような現場で、テストコードを書くという基本的人権が保証されているわけないじゃないですか・・・ ↩
- 投稿日:2020-01-04T05:27:21+09:00
JavaScriptでfor文(foreach)内でのgetElementsByClassNameの使用法
はじめに
趣味で作っているツイッタークローンにおいて、ツイート一覧にマスオーバーさせると背景色が変化するjsの処理を書きたかった。
JSをあてるPHPファイル
3行目のtweetcardが対象のclass名です。
注)実際は生のphpファイルではなくテンプレートエンジンのbladeを使用しています。home.php@if ($posts) @foreach($posts as $post) <div class="card tweetcard"> <div class="card-body alert tweet"> {{ $post->name }} {{ $post->created_at }} <br> {{ $post->content }} </div> </div>間違った書きかた
samle.jsconst tweetcard = document.getElementsByClassName('tweetcard'); //クリックイベントで背景色を変える tweetcard.addEventListener('mouseenter', () => { tweetcard.style.backgroundColor = "#e6ecf0"; }, false); } //クリックイベントで背景色を戻す tweetcard.addEventListener('mouseleave', () => { tweetcard.style.backgroundColor = "white"; }, false); }エラーが出ました。
リファレンスを確認してみます。下記抜粋。getElementsByClassName メソッドは、指定されたクラス名をすべて持つすべての子要素の配列風オブジェクトを返します。 引用元:MDN web docs
getElementsByClassNameで取得した要素は配列だったのですね!
だったらfor分で回してあげればいいですね。
正しい書き方
samle.jsconst tweetcard = document.getElementsByClassName('tweetcard'); //mouseenter for(let i = 0; i < tweetcard.length; i++){ //クリックイベントで背景色を変える tweetcard[i].addEventListener('mouseenter', () => { tweetcard[i].style.backgroundColor = "#e6ecf0"; }, false); } //mouseenter for(let i = 0; i < tweetcard.length; i++){ //クリックイベントで背景色を戻す tweetcard[i].addEventListener('mouseleave', () => { tweetcard[i].style.backgroundColor = "white"; }, false); }これでマウスをかざした領域の背景色が変更され、マウスを外せば背景色が元に戻るようになりました。
参考
- 投稿日:2020-01-04T01:40:08+09:00
composerのライブラリ達にいつもあてたいパッチがあるならワンシェルにしておこう
LaravelでRFCに軽く違反してるメアドにメール送ろうとしたら、Swift_RfcComplianceExceptionが発生して送れなかったんですよ。
原因は下記だったんですが、vendor配下って普通git管理しないじゃないですか。
https://stackoverflow.com/questions/1490042/swift-mailer-error-swift-rfccomplianceexception-on-an-email-that-actually-work
だから、リリースする時はcomposer install
したらパッチをあててやんなきゃいけなくて面倒ですよね。
そういう時は、patchファイルを用意して、patchコマンドを実行ってのもいいですが、なんかサクッと実行したいので、1つのshにするのがおすすめ!$PROJECT_ROOT/vendor.patch.sh#!/bin/bash patch app/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php <<'PATCH' 355,357c355,357 < throw new Swift_RfcComplianceException( < 'Address in mailbox given ['.$address.'] does not comply with RFC 2822, 3.6.2.' < ); --- > // throw new Swift_RfcComplianceException( > // 'Address in mailbox given ['.$address.'] does not comply with RFC 2822, 3.6.2.' > // ); PATCH # 他にpatchが増えたら追記しよう邪魔にならなく控え目でいいでしょ?