20200409のPHPに関する記事は13件です。

Docker で立てた WordPress を検証した後からブラウザで特定ポートへのアクセスができなくなる

問題

  • ローカルで WordPress を Docker で構築した
    • 使ったイメージは ここ のDocker Official
    • ページに記載されている通り -p 8080:80 のポート割り当てで起動
  • この時点で初回の検証をする場合は特に問題は起きなかった
  • 改めて起動して http://localhost:8080 にアクセスすると、 http://localhost にリダイレクトされてアクセスできない
  • WordPressコンテナを落として次の日、WordPress 以外のアプリケーション開発で http://localhost:8080 にアクセスすると http://localhost の表示となり、8080 にアクセスできていないように見える
    • ただし、/ 以外 ( http://localhost:8080/login など ) にアクセスはできる
  • curl などでは普通に http://localhost:8080 へはアクセスできる

この問題の原因を探る。

調査結果

  • WordPress の home および siteurl はコンテナ内部のポートに依存した初期設定が行われるので、 http://localhost で初期データが作成される
    • どうも WordPress ではアクセスしてきたドメインと wp_options.home に登録されているドメインが違う場合、 home のドメインに 301リダイレクトが発生する……らしい。
      • なんでこんな機能があるんだろう…、リバプロとかすごく挟みづらそう…
  • そのため、この状態のWordPressが動作している状態で http://localhost:8080 にアクセスすると、 http://localhost に 301 リダイレクトされる
  • そして ブラウザでは 301 リダイレクトもキャッシュする
  • ので、キャッシュが残っている間は http://localhost:8080 へのアクセスを行うと、一瞬でリダイレクトされるので http://localhost:8080 に対してアクセスができないように見える

解決策

  • ブラウザのキャッシュをクリアする
  • WordPressが利用するDataBase の home および siteurlhttp://localhost:8080 に書き換える
    • ただし、WordPress 内部から home および siteurl を参照して自分自身にアクセスするような場合、http://localhost:8080 はコンテナ内部で閉じているので動かない
    • サンプルには -p 8080:80 と書いてあるが、もう複雑すぎるので -p 80:80 以外で動かさない方がいいんじゃないかな?
    • もしくは、そもそもローカルで構築せずにグローバルなFQDNを割り当ててそれで外中両方のアクセスを適切に解決できるようにすればよい

参考

というか同様のトラブルにハマった方の記事。

余談

WordPress、システムを管理する側から見るとものすごくハマりどころが多い印象でつらい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP学習記 #4日目

sprintf関数

指定したフォーマットで文字列を生成する。

オブジェクト指向プログラミング

プロパティとは

クラスの中で定義された変数のことである。メンバ変数とも呼ばれる。プロパティとして変数を定義する場合、アクセス修飾子を頭につける必要がある。

コンストラクタ

インスタンス化のタイミングで実行される特別なメソッドである。何らかの戻り値を返す必要がない。

デストラクタとは

コンストラクタとは逆に、オブジェクトが破棄されるタイミングで実行される。何らかの戻り値を返す必要がない。

静的メソッド

静的メソッドとは、インスタンスを生成しなくてもクラスから直接に呼び出せることメソッドのことである。静的メソッドを呼び出すのには、「::」演算子を利用する。

クラス定数とは

classブロックの中で定義された定数のことである。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020年版 VSCodeの良さげな拡張機能紹介

日々VSCodeをより気持ちよく使用するために拡張機能の探求は欠かせません。
今回は私の独断と偏見で良さげな拡張機能を紹介します。

見た目編

Peacock

https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock
image.png

VSCodeのウィンドウをおしゃれにできます。
私は複数の環境(プロジェクトやプログラミング言語)を使い分けるので、ウィンドウごと色を変えることで気持ちも一緒に切り替えています。

ウィンドウ
公式より拝借

Ubuntu VSCodeTheme

https://marketplace.visualstudio.com/items?itemName=ThiagoLcioBittencourt.ubuntuvscode
image.png

見た目をUbuntuっぽくできます。ただの気分です。
私はMacの壁紙をUbuntuにしています(Ubuntu使えというツッコミは受け付けません)

Ubuntu
公式より拝借

Rainbow CSV

https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv
image.png

CSVを開いたときに色分けしてくれて見やすくなります。

csv
公式より拝借

indent-rainbow

https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow
image.png

インデントがカラフルになってちょっと見易くなります。

共通機能系

Settings Sync

https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync
image.png

複数のPCでVSCodeを使用する場合、各PCでVSCodeの設定を行う必要がありますが、こちらの拡張機能を使えば設定を共有できます!

Vim

https://marketplace.visualstudio.com/items?itemName=vscodevim.vim
image.png

VSCodeをVimっぽく使いたい方におすすめ。ただし、コテッコテのVimmerは痒いところに手が届かない可能性あり。
私は普段VimとVSCodeのハイブリットなので重宝しています。

GitLens

https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens
image.png

Git使うならまず入れた方が良いです。
Git操作をせずにいつ誰が該当のソースを修正したのかがパッと分かります。

ex
ex
公式より拝借

言語別

ここからは言語別の拡張機能紹介です。
デバッガーやLanguage Supportは書くまでも無いと思うので割愛します。

PHP

PHP Intelephense

https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client
image.png

Language Supportは書かないって言ったくせに早速書きました。
PHPは他のLanguage Supportもあるのですが、こちらの拡張機能の方が頭が良い気がしたので紹介します。

PHP DocBlocker

https://marketplace.visualstudio.com/items?itemName=neilbrayfield.php-docblocker
image.png

PHPDocを自動で作ってくれます。intって書いてもintegerと書かれるお茶目さがあります。
(多分自分で設定すれば修正できますが)

PHP Namespace Resolver

https://marketplace.visualstudio.com/items?itemName=MehediDracula.php-namespace-resolver
image.png

Classのインポートを自動でやってくれます。(IDEから乗り換えた人はとりあえず入れて見てください。ストレスが減ります)
一括インポートもできるので結構良いです。

Ruby

endwise

https://marketplace.visualstudio.com/items?itemName=kaiwood.endwise
image.png

自分でendって書くの面倒ですよね?
こちらの拡張機能は自動で付与してくれます。

ruby-rubocop

https://marketplace.visualstudio.com/items?itemName=misogi.ruby-rubocop
image.png

別途Gemの追加が必要ですが、静的チェックとフォーマットをやってくれるので入れておきましょう。
自分の悪いところも容赦無く教えてくれたり、修正してくれるので勉強にもなります。

ifじゃなくてunless使いな〜とか、ネスト減らせるよ〜とか、
eachじゃなくてmap使いなーとか言ってくれるのでレビュワーの負担も減ると思います。

JavaScript

Prettier

https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
image.png

JS触っている人なら当たり前だと思いますが、自動でコード整形してくれるので便利です。
ただ、保存時に整形する設定を入れると差分がめっちゃ出るリポジトリもあったりすると思うので程々に。

Debugger for Chrome

https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome
image.png

デバッガーも書かないと言ったくせに書いてしまいました。
JSのデバッグをVSCode上で行えるので便利です。もうconsole.logデバッグとはおさらば。

その他

Remote-SSH

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh
image.png

SSH先のPC上のソースを編集することができます。
私は自宅のUbuntuや、Vagrantに対してSSHして開発する際に使用しています。
ただソースを編集できるだけでなく、ポートフォワードも行えるのでリモート先の8080ポートをローカルの8080にすることもできたりします。
とても便利なのでSSHで開発する方はぜひ使ってみてください。

Remote-Container

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
image.png

Dockerコンテナの中のソースをVSCodeから修正できちゃいます。
もちろんコンテナなのでVolumeマウントしないと修正内容が飛んでしまいますが、開発環境も含めてコンテナ化しちゃうような時は良さげ。

昔別記事書いてます。
https://qiita.com/MasanoriIwakura/items/fe1bb1fade98c0aa65d2
https://qiita.com/MasanoriIwakura/items/e7a2045b2de28c76ccd9

REST Client

https://marketplace.visualstudio.com/items?itemName=humao.rest-client
image.png

なんと、VSCode上からREST APIを試すことができます!
デバッグや外部APIの確認に便利。


まだまだ拡張機能は沢山あるので、気が向いたタイミングで更新していきます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザでPHPの処理をすぐ確認したいとき

どういうときに使うか

ブラウザでPHPからの出力(HTMLなど)を確認したいとき

どのようにするか

  • 以下をターミナルから打つことで簡易的なウェブサーバを立てることができる。
php -S localhost:8080
  • このコマンドの後に出てくる"Listening on"以降がURLとなる。
  • 何も指定しなければドキュメントルートはカレントディレクトリとなる。
  • -tオプションを使えば明示的にドキュメントルートを指定できる。
php -S localhost:8080 -t /var/www/test

参考

https://qiita.com/drafts/7c8d8fad5815f98fc143/edit
https://xn--web-oi9du9bc8tgu2a.com/php-web-server/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPのビルトインサーバ

どういうときに使うか

ブラウザでPHPからの出力(HTMLなど)を確認したいとき

どのようにするか

  • 以下をターミナルから打つことで簡易的なウェブサーバを立てることができる。
php -S localhost:8080
  • このコマンドの後に出てくる"Listening on"以降がURLとなる。
  • 何も指定しなければドキュメントルートはカレントディレクトリとなる。
  • -tオプションを使えば明示的にドキュメントルートを指定できる。
php -S localhost:8080 -t /var/www/test

参考

https://qiita.com/drafts/7c8d8fad5815f98fc143/edit
https://xn--web-oi9du9bc8tgu2a.com/php-web-server/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

不正アクセスを防ぐためのパーミッション設定 | EC-CUBEユーザーグループ合同勉強会

今回は文字が多いので Qiita スライドモードでお送りします


自己紹介

  • 名前: 大河内健太郎(@nanasess) 年齢: 43才
  • 出身: 愛知県西尾市一色町
  • 在住: 大阪市(四天王寺の近く)
  • 前職:寿司屋の板前
  • 資格: 調理師・ふぐ処理師
  • EC-CUBE コミッター・公式エバンジェリスト
  • 最近のマイブーム: #おうち寿司

EC-CUBE セキュリティチェックリスト のパーミッション設定を、もう少し深堀りします


EC-CUBEにおける不正アクセスの原因の多く

  • 見えちゃいけないファイルが見えていた
  • 他の CMS や CGI の脆弱性攻撃
  • OSコマンドインジェクション
  • SQLインジェクション

当社調べ


見えちゃいけないファイルからの攻撃方法

  1. 見えちゃいけないファイルから、セッション奪取
  2. 管理画面からテンプレート改竄
  3. 購入確認画面に攻撃用 JavaScript を混入
  4. 偽の決済画面 からカード番号奪取

あくまでも推測です


他の CMS や フリーCGI の脆弱性攻撃

  1. 他の CMS や フリーCGIの脆弱性を利用して、 書き込み可能なディレクトリに 攻撃用ファイルを書き込む
  2. 攻撃用ファイルからテンプレート改竄
  3. 購入確認画面に攻撃用 JavaScript を混入
  4. 偽の決済画面 からカード番号奪取

あくまでも推測です


OSコマンドインジェクション

  1. OSコマンドインジェクションを利用して、 書き込み可能なディレクトリに 攻撃用ファイルを書き込む
  2. 攻撃用ファイルからテンプレート改竄
  3. 購入確認画面に攻撃用 JavaScript を混入
  4. 偽の決済画面 からカード番号奪取

あくまでも推測です


SQLインジェクション

  1. SQLインジェクションを利用して、データベースに保存されている情報を盗む

数年前から多く悪用されており、カスタマイズ時には注意が必要。判例あり

phpMyAdmin や phpPgAdmin を同居させないのも重要


ファイル改竄の直接原因

  • Webサーバーのユーザー権限でファイルを書き込まれた
  • テンプレートを改竄された

ファイル改竄を防ぐには

  • Webサーバーのユーザー権限で、テンプレートを変更させないこと
  • Webサーバーのユーザー権限で、書き込める範囲を最小限にすること

Webサーバーのユーザー権限で、テンプレートを変更させないために

  • デザイン管理画面は閉じる
  • テンプレートの変更は、 FTP/SSH ユーザー権限で行う
  • テンプレートのパーミッションは 604(環境によって変わります)

多くの共有レンタルサーバーでは FTP/SSH ユーザー権限で Webサーバーが動作しているため、対策できない


Webサーバーのユーザー権限で、書き込める範囲を最小限にするには

Webサーバーの権限で書き込める箇所は、以下に限定する

  • 画像フォルダ(html/upload/save_image)
  • キャッシュフォルダ(4系: var, 3系: app/cache, 2系: data/cache, data/Smarty/templates_c)

プラグインのインストール等、管理画面機能の一部は動作しなくなるが、安全性を高めるためには利便性を削るしかない

CMS 機能は、 WordPress や Concrete5 などの他の優秀な CMS に任せた方が安全


他に注意すべきこと

  • PHPはデフォルトの設定でファイルやディレクトリを生成した時点では、すべてのユーザーが書き込める状態なので注意(ファイル:666, ディレクトリ:777 になってしまう)
  • 定期的にパーミッションをチェックし、変更してあげましょう
## 666 のファイルを検索
find . -type f -perm 666

## 604 に変更
find . -type f -perm 666 -print0 | xargs -0 chmod 604

## 777 のディレクトリを検索
find . -type d -perm 777

## 705 に変更
find . -type d -perm 777 -print0 | xargs -0 chmod 705

そのほか

  • .htaccess を改竄されるケースもある
  • 画像ファイルに偽装したバックドアを設置されるケースもある
  • .env も見られないように注意
  • Nginx など .htaccess の効かない環境は注意
  • WordPress の安全性を高める記事は、とても参考になります

まとめ

  • 見えちゃいけないファイルは確実に隠そう
  • Webサーバーの書き込み権限は最小限に
  • 利便性と安全性はトレードオフ。管理画面の機能を限定することも選択肢に

ご静聴ありがとうございました!


2020-04-10追記

管理画面からのデザイン管理を外す方法について

ちなみに、 WordPress は FTP 権限でファイルを更新可能な CMS なので、EC-CUBEより堅牢な実装になっています。
EC-CUBE も、 FTP 権限でデザイン管理画面を更新できるようになれば、もっと安心して使えるようになります。

2系

  • html/<管理画面ディレクトリ>/design 以下を削除する
  • data/Smarty/templates/admin/main_frame.tpl から デザイン管理 のメニューを削除する

3系

  • src/Eccube/ControllerProvider/AdminControllerProvider.php の137行目付近から149行目付近までをコメントアウト
diff --git a/src/Eccube/ControllerProvider/AdminControllerProvider.php b/src/Eccube/ControllerProvider/AdminControllerProvider.php
index b706b6be8..a77adff0a 100644
--- a/src/Eccube/ControllerProvider/AdminControllerProvider.php
+++ b/src/Eccube/ControllerProvider/AdminControllerProvider.php
@@ -134,19 +134,19 @@ class AdminControllerProvider implements ControllerProviderInterface
         $c->match('/content/file_download', '\Eccube\Controller\Admin\Content\FileController::download')->bind('admin_content_file_download');
         $c->delete('/content/file_delete', '\Eccube\Controller\Admin\Content\FileController::delete')->bind('admin_content_file_delete');

-        $c->match('/content/layout', '\Eccube\Controller\Admin\Content\LayoutController::index')->bind('admin_content_layout');
-        $c->match('/content/layout/{id}/edit', '\Eccube\Controller\Admin\Content\LayoutController::index')->assert('id', '\d+')->bind('admin_content_layout_edit');
-        $c->match('/content/layout/{id}/preview', '\Eccube\Controller\Admin\Content\LayoutController::preview')->assert('id', '\d+')->bind('admin_content_layout_preview');
-
-        $c->match('/content/block', '\Eccube\Controller\Admin\Content\BlockController::index')->bind('admin_content_block');
-        $c->match('/content/block/new', '\Eccube\Controller\Admin\Content\BlockController::edit')->bind('admin_content_block_new');
-        $c->match('/content/block/{id}/edit', '\Eccube\Controller\Admin\Content\BlockController::edit')->assert('id', '\d+')->bind('admin_content_block_edit');
-        $c->delete('/content/block/{id}/delete', '\Eccube\Controller\Admin\Content\BlockController::delete')->assert('id', '\d+')->bind('admin_content_block_delete');
-
-        $c->match('/content/page', '\Eccube\Controller\Admin\Content\PageController::index')->bind('admin_content_page');
-        $c->match('/content/page/new', '\Eccube\Controller\Admin\Content\PageController::edit')->bind('admin_content_page_new');
-        $c->match('/content/page/{id}/edit', '\Eccube\Controller\Admin\Content\PageController::edit')->assert('id', '\d+')->bind('admin_content_page_edit');
-        $c->delete('/content/page/{id}/delete', '\Eccube\Controller\Admin\Content\PageController::delete')->assert('id', '\d+')->bind('admin_content_page_delete');
+        // $c->match('/content/layout', '\Eccube\Controller\Admin\Content\LayoutController::index')->bind('admin_content_layout');
+        // $c->match('/content/layout/{id}/edit', '\Eccube\Controller\Admin\Content\LayoutController::index')->assert('id', '\d+')->bind('admin_content_layout_edit');
+        // $c->match('/content/layout/{id}/preview', '\Eccube\Controller\Admin\Content\LayoutController::preview')->assert('id', '\d+')->bind('admin_content_layout_preview');
+
+        // $c->match('/content/block', '\Eccube\Controller\Admin\Content\BlockController::index')->bind('admin_content_block');
+        // $c->match('/content/block/new', '\Eccube\Controller\Admin\Content\BlockController::edit')->bind('admin_content_block_new');
+        // $c->match('/content/block/{id}/edit', '\Eccube\Controller\Admin\Content\BlockController::edit')->assert('id', '\d+')->bind('admin_content_block_edit');
+        // $c->delete('/content/block/{id}/delete', '\Eccube\Controller\Admin\Content\BlockController::delete')->assert('id', '\d+')->bind('admin_content_block_delete');
+
+        // $c->match('/content/page', '\Eccube\Controller\Admin\Content\PageController::index')->bind('admin_content_page');
+        // $c->match('/content/page/new', '\Eccube\Controller\Admin\Content\PageController::edit')->bind('admin_content_page_new');
+        // $c->match('/content/page/{id}/edit', '\Eccube\Controller\Admin\Content\PageController::edit')->assert('id', '\d+')->bind('admin_content_page_edit');
+        // $c->delete('/content/page/{id}/delete', '\Eccube\Controller\Admin\Content\PageController::delete')->assert('id', '\d+')->bind('admin_content_page_delete');

         $c->match('/content/cache', '\Eccube\Controller\Admin\Content\CacheController::index')->bind('admin_content_cache');
  • src/Eccube/Resource/config/nav.yml.dist の58行目付近から63行目付近までをコメントアウト
    • app/config/eccube/nav.yml が存在する方は、 同様に58行目付近から63行目付近までをコメントアウト
diff --git a/src/Eccube/Resource/config/nav.yml.dist b/src/Eccube/Resource/config/nav.yml.dist
index 32662a2a1..a1b5b29ba 100644
--- a/src/Eccube/Resource/config/nav.yml.dist
+++ b/src/Eccube/Resource/config/nav.yml.dist
@@ -55,12 +55,12 @@
       - id: file
         name: ファイル管理
         url: admin_content_file
-      - id: page
-        name: ページ管理
-        url: admin_content_page
-      - id: block
-        name: ブロック管理
-        url: admin_content_block
+      # - id: page
+      #   name: ページ管理
+      #   url: admin_content_page
+      # - id: block
+      #   name: ブロック管理
+      #   url: admin_content_block
       - id: cache
         name: キャッシュ管理
         url: admin_content_cache

4系

  • app/config/eccube/packages/eccube_nav.yaml の61行目付近から66行目付近と、73行目付近から75行目付近をコメントアウト
diff --git a/app/config/eccube/packages/eccube_nav.yaml b/app/config/eccube/packages/eccube_nav.yaml
index 5abb0b706f..d99603c01a 100644
--- a/app/config/eccube/packages/eccube_nav.yaml
+++ b/app/config/eccube/packages/eccube_nav.yaml
@@ -58,21 +58,21 @@ parameters:
                 file:
                     name: admin.content.file_management
                     url: admin_content_file
-                layout:
-                    name: admin.content.layout_management
-                    url: admin_content_layout
-                page:
-                    name: admin.content.page_management
-                    url: admin_content_page
+                # layout:
+                #     name: admin.content.layout_management
+                #     url: admin_content_layout
+                # page:
+                #     name: admin.content.page_management
+                #     url: admin_content_page
                 css:
                     name: admin.content.css_management
                     url: admin_content_css
                 js:
                     name: admin.content.js_management
                     url: admin_content_js
-                block:
-                    name: admin.content.block_management
-                    url: admin_content_block
+                # block:
+                #     name: admin.content.block_management
+                #     url: admin_content_block
                 cache:
                     name: admin.content.cache_management
                     url: admin_content_cache
  • 以下の Controller の @Route//@Route に変更
    • src/Eccube/Controller/Admin/Content/BlockController.php
    • src/Eccube/Controller/Admin/Content/LayoutController.php

    - src/Eccube/Controller/Admin/Content/PageController.php

diff --git a/src/Eccube/Controller/Admin/Content/BlockController.php b/src/Eccube/Controller/Admin/Content/BlockController.php
index ce78ae3a65..d5a0501536 100644
--- a/src/Eccube/Controller/Admin/Content/BlockController.php
+++ b/src/Eccube/Controller/Admin/Content/BlockController.php
@@ -51,7 +51,7 @@ class BlockController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/block", name="admin_content_block")
+     * //@Route("/%eccube_admin_route%/content/block", name="admin_content_block")
      * @Template("@admin/Content/block.twig")
      */
     public function index(Request $request)
@@ -77,8 +77,8 @@ class BlockController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/block/new", name="admin_content_block_new")
-     * @Route("/%eccube_admin_route%/content/block/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_block_edit")
+     * //@Route("/%eccube_admin_route%/content/block/new", name="admin_content_block_new")
+     * //@Route("/%eccube_admin_route%/content/block/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_block_edit")
      * @Template("@admin/Content/block_edit.twig")
      */
     public function edit(Request $request, $id = null, Environment $twig, FileSystem $fs, CacheUtil $cacheUtil)
@@ -180,7 +180,7 @@ class BlockController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/block/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_block_delete", methods={"DELETE"})
+     * //@Route("/%eccube_admin_route%/content/block/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_block_delete", methods={"DELETE"})
      */
     public function delete(Request $request, Block $Block, Filesystem $fs, CacheUtil $cacheUtil)
     {
diff --git a/src/Eccube/Controller/Admin/Content/LayoutController.php b/src/Eccube/Controller/Admin/Content/LayoutController.php
index eddbb86f12..5b6c764096 100644
--- a/src/Eccube/Controller/Admin/Content/LayoutController.php
+++ b/src/Eccube/Controller/Admin/Content/LayoutController.php
@@ -100,7 +100,7 @@ class LayoutController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/layout", name="admin_content_layout")
+     * //@Route("/%eccube_admin_route%/content/layout", name="admin_content_layout")
      * @Template("@admin/Content/layout_list.twig")
      */
     public function index()
@@ -119,7 +119,7 @@ class LayoutController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/layout/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_layout_delete", methods={"DELETE"})
+     * //@Route("/%eccube_admin_route%/content/layout/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_layout_delete", methods={"DELETE"})
      *
      * @param Layout $Layout
      *
@@ -148,8 +148,8 @@ class LayoutController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/layout/new", name="admin_content_layout_new")
-     * @Route("/%eccube_admin_route%/content/layout/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_layout_edit")
+     * //@Route("/%eccube_admin_route%/content/layout/new", name="admin_content_layout_new")
+     * //@Route("/%eccube_admin_route%/content/layout/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_layout_edit")
      * @Template("@admin/Content/layout.twig")
      */
     public function edit(Request $request, $id = null, $previewPageId = null, CacheUtil $cacheUtil)
@@ -236,7 +236,7 @@ class LayoutController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/layout/view_block", name="admin_content_layout_view_block", methods={"GET"})
+     * //@Route("/%eccube_admin_route%/content/layout/view_block", name="admin_content_layout_view_block", methods={"GET"})
      *
      * @param Request $request
      * @param Twig $twig
@@ -272,7 +272,7 @@ class LayoutController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/layout/{id}/preview", requirements={"id" = "\d+"}, name="admin_content_layout_preview")
+     * //@Route("/%eccube_admin_route%/content/layout/{id}/preview", requirements={"id" = "\d+"}, name="admin_content_layout_preview")
      */
     public function preview(Request $request, $id, CacheUtil $cacheUtil)
     {
diff --git a/src/Eccube/Controller/Admin/Content/PageController.php b/src/Eccube/Controller/Admin/Content/PageController.php
index 13bf8cfe0f..2d8d0e8a07 100644
--- a/src/Eccube/Controller/Admin/Content/PageController.php
+++ b/src/Eccube/Controller/Admin/Content/PageController.php
@@ -65,7 +65,7 @@ class PageController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/page", name="admin_content_page")
+     * //@Route("/%eccube_admin_route%/content/page", name="admin_content_page")
      * @Template("@admin/Content/page.twig")
      */
     public function index(Request $request)
@@ -86,8 +86,8 @@ class PageController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/page/new", name="admin_content_page_new")
-     * @Route("/%eccube_admin_route%/content/page/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_page_edit")
+     * //@Route("/%eccube_admin_route%/content/page/new", name="admin_content_page_new")
+     * //@Route("/%eccube_admin_route%/content/page/{id}/edit", requirements={"id" = "\d+"}, name="admin_content_page_edit")
      * @Template("@admin/Content/page_edit.twig")
      */
     public function edit(Request $request, $id = null, Environment $twig, RouterInterface $router, CacheUtil $cacheUtil)
@@ -250,7 +250,7 @@ class PageController extends AbstractController
     }

     /**
-     * @Route("/%eccube_admin_route%/content/page/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_page_delete", methods={"DELETE"})
+     * //@Route("/%eccube_admin_route%/content/page/{id}/delete", requirements={"id" = "\d+"}, name="admin_content_page_delete", methods={"DELETE"})
      */
     public function delete(Request $request, $id = null, CacheUtil $cacheUtil)
     {
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP組み込みのDateTimeクラスの定数を使えば日時フォーマットを頑張って書かなくて良い

PHPの場合はDateTimeクラスに定数が定義されているので、それを使うとdateフォーマット表現が簡単でした。
Laravel の Request Validation にも使えます。

ref: https://www.php.net/manual/ja/class.datetime.php

例えば
2020-04-08T00:00:00+09:00
みたいなフォーマットを表現したいとき、
DateTime::RFC3339 で表現できます。
この定数の中には Y-m-d\TH:i:sPが入っています。

ここで最後のPが何を意味するかという情報はPHPのdate関数の項目にまとまっています。
ref: https://www.php.net/manual/ja/function.date.php

一部抜粋。

O グリニッジ標準時 (GMT) との時差。時間と分の間にコロンは入りません。 例: +0200
P グリニッジ標準時 (GMT) との時差。時間と分をコロンで区切った形式 (PHP 5.1.3 で追加)。 例: +02:00
T タイムゾーンの略称 例: EST, MDT ...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

array_spliceで多次元配列に配列を追加する

array_spliceで多次元配列の中に配列を追加したい

親配列
$hoge = [
    ['id' => 1, 'name' => 'はげ'],
    ['id' => 2, 'name' => 'ひげ']
];

追加配列
$add = ['id' => 3, 'name' => 'ほげ'];

直で追加配列を指定してみる。

array_splice($hoge, 1, 0, $add);

結果:
[
    ['id' => 1, 'name' => 'はげ'],
    'id' => 3,
    'name' => 'ほげ',
    ['id' => 2, 'name' => 'ひげ']
]

ぶっ壊れた(・ω・`

なので一度配列で入れ子にしてあげる。

array_splice($hoge, 1, 0, [$add]);

結果:
[
    ['id' => 1, 'name' => 'はげ'],
    ['id' => 3, 'name' => 'ほげ'],
    ['id' => 2, 'name' => 'ひげ']
]

おっお(^ω^

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 検索・置換系の引数の順番まとめ

\$needleと\$haystackどっちが先だったっけ?
といつも記憶喪失悩まされるのでメモ

関数 第1引数 第2引数 第3引数 第4引数 第5引数
strpos $haystack $needle $offset
substr_count $haystack $needle $offset $length
str_replace $search $replace $subject $count
preg_match $pattern $subject $matches $flags $offset
preg_replace $pattern $replace $subject $limit $count
preg_replace_callback $pattern $callback $subject $limit $count
array_search $needle $haystack $strict
array_filter $array $callback $flag
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel触って1年経ったので過去の戒め(ソースコード)を改善してみた

Laravelのリファクタ記事にしようと思ったが、ただただ間違ったコードを直すだけの記事でした。

久々に過去練習用として作成したサイトのソースコードを覗いたら無駄な処理書いてたり、
Laravelの機能活かせれてなかったりそもそもPHP使えてなかったりと散々だったので、
いい反面教師コードだと思って改善記事(改善というか修正レベル)書いてみました。

目に付いた部分しか改善しないので、一貫性はありませんし、
他の方が絶対やらないような事も多々ありますが、戒めとしてどうか生温かい目で見守ってください。

環境

Laravel5.7
PHP7.1

注意

改善とはいえ、これがベストとは思ってません。
あくまで出来る時間で簡単な改善(修正)を行っただけです。
その辺り、ご容赦くださいm(._.)m

関係ないところは一部省略します。
例外処理等も省略します。

フォームリクエストの謎の使い方

FormRequest便利ですよね。
Controllerにバリデーションを書かずに済むので良く使っています。
(他にもControllerで受け取る前の前加工とか)

さて過去のソースコードを覗いてみましょう

過去コード

CreateController.php
use App\Http\Requests\Validation;

public function store(Validation $request)
{
    //Validation Requestからルールを取得
    $rules = $request->rules();
    //Validation Requestからエラーメッセージを取得
    $messages = $request->messages();

    $data = Validator::make($request->all(), $rules, $messages);
}

フォームリクエストから受け取ってる時点でバリデーション処理は済んでるはずなので、色々と不要ですね。
それとValidationというクラス名だと分かりづらいので、クラス名も変更します。

改善コード

CreateController.php
use App\Http\Requests\ProjectRequest;

public function store(ProjectRequest $request)
{
    $data = $request->all();
}

ProjectRequestと命名した理由は案件登録で使用するのが一つと作成と更新で使用するのであえてCreateProjectRequestとしませんでした。

改善ポイント

  • 無駄な記述を削除して本来の使い方を実装
  • クラス名の役割が抽象的すぎたので、少し具体的にして明確化出来た?

fillable便利だよ

fillableもありがたいですよね〜
デフォルトで存在するUser.php見ると最初から存在するコイツですね

User.php
protected $fillable = [
    'name', 'email', 'password',
];

何が便利なのか、まずは悪い方の過去のコードを見てみましょう。

過去コード

CreateController.php
use App\Http\Requests\ProjectRequest;

public function store(ProjectRequest $request)
{
    $project = new Project;
    $project->name = $request->name;
    $project->responsible_id = $request->responsible;
    $project->category_id = $request->category;
    $project->description = $request->description;
    $project->url = $request->url;
    $project->release_year_at = $request->release_year;
    $project->release_month_at = $request->release_month;
    $project->image_id = $image_id;
    $project->start_year_at = $request->start_year;
    $project->start_month_at = $request->start_month;
    $project->end_year_at = $request->end_year;
    $project->end_month_at = $request->end_month;
    $project->save();
}

うーん行が無駄に多いのと保存処理のたびにこれを書くのはめんどくさいですね。

では改善コード

fillableも勿論活用しますが、そもそも保存処理をControllerでやるのはMVCとしてよろしくないのでModelに責務を分けましょう。

改善コード

ちなみに引数にモデル(というかクラス名)をタイプヒントするとLaravelがインスタンスを用意してくれます。

CreateController.php
use App\Http\Requests\ProjectRequest;
use App\Project;

public function store(ProjectRequest $request, Project $project)
{
    $results = $project->storeProject($request->all());
}

ではModel

Project.php
protected $fillable = [
    'responsible_id',
    'category_id',
    'image_id',
    'name',
    'url',
    'description',
    'release_year_at',
    'release_month_at',
    'start_year_at',
    'start_month_at',
    'end_year_at',
    'end_month_at',
];

public function storeProject(array $data)
{
   return $this->create($data);
}

保存の部分が1行で済みました!
fillableというのは保存するときのホワイトリストなんですね〜
勿論更新の時にも適用されます!

逆にブラックリストのguardedもあります
[Laravel 5.7] Eloquent Modelのfillableとguardedの違い

Project.php
public function storeProject(array $data, int $id)
{
   return $this->where('id', $id)->update($data);
}

改善ポイント

  • 入力内容が増えるたびに書き足す必要がなくなった
  • MVCで責務をちゃんと分けた
  • コード行短縮

resourceの恩恵を得ていない

Controllerを作成する時に良くみますね〜
説明は別記事に託します。

Laravelのリソースコントローラを理解して使ってみよう!

ではルーティングをみてみましょう。

web.php
Route::get('project/', 'ProjectsController@index');

Route::get('project/create', 'CreatesController@index');

Route::post('project/create', 'CreatesController@store');

Route::get('project/edit', 'CreatesController@edit');

Route::post('vupdate', 'CreatesController@update');

Route::get('project/delete', 'CreatesController@delete');

突っ込みどころは多いですけど一旦スルー

改善コード

web.php
Route::prefix('project')->name('project.')->group(function () {
    Route::resource('/', ProjectsController, ['except' => 'show']);
});

改善ポイント

  • Projectに関連した物しかなく、restfullで完結できそうだったので、ProjectsControllerファイルにまとめました(これはたまたまなので、無理矢理真似なくてOK)
  • groupにまとめて無駄な記述が減らせたのと関連したルーティングが見易くなった。
  • Routeメソッドをresourceに変える事でメソッド毎に定義しなくて済んだ(showは使わないのでexceptで除外していますが本来はonly派)

ちょこっと番外編(LaravelというかPHP)

検索機能を付けていたのだが、過去のコードを見てみましょう。

過去コード

ProjectsController
public function index(Request $request)
{

    $query = Project::query();

    if (isset($request))
    {
        $category_id = $request->get('category_id');

        $responsible_id = $request->get('responsible_id');

        $tag_ids = $request->get('tag_id');

        $release_year = $request->get('release_year');

        $release_month = $request->get('release_month');

        $start_year = $request->get('start_year');

        $start_month = $request->get('start_month');

        $end_year = $request->get('end_year');

        $end_month = $request->get('end_month');

        //カテゴリ
        if (!empty($category_id))
        {
            $query->where('category_id', 'like', '%' .$category_id .'%');
        }

        //ユーザー
        if (!empty($responsible_id))
        {
            $query->where('responsible_id', 'like', '%' .$responsible_id .'%');
        }

        //リリース年
        if (!empty($release_year))
        {
            $query->where('release_year_at', 'like', '%' .$release_year .'%');
        }

        //リリース月
        if (!empty($release_month))
        {
            $query->where('release_month_at', 'like', '%' .$release_month .'%');
        }

        //開発開始年
        if (!empty($start_year))
        {
            $query->where('start_year_at', 'like', '%' .$start_year .'%');
        }

        //開発開始月
        if (!empty($start_month))
        {
            $query->where('start_month_at', 'like', '%' .$start_month .'%');
        }

        //開発終了年
        if (!empty($end_year))
        {
            $query->where('end_year_at', 'like', '%' .$end_year .'%');
        }

        //開発終了月
        if (!empty($end_month))
        {
            $query->where('end_month_at', 'like', '%' .$end_month .'%');
        }
    }
}

なげーーーー

しかも検索条件増えるたびに書き足さないといけない、、
という事で今回は複雑な検索条件は無かったので短縮化

ProjectsController
public function index(Request $request, Project $project)
{
    $query = $project->searchProject($request);
}

Modelに記述

Project.php
    public function searchProject(Request $request)
    {
        $query = $this->query();

        if ($request->query() !== '') {
            foreach ($request->all() as $key => $val) {
                !empty($val) ? $query->where($key, 'LIKE', '%' .$val .'%') : '';
            }
        }

        return $query;
    }

これで検索条件が増えても安心ですね^^(簡単な条件に限るけど)

改善ポイント

  • コード行短縮
  • 条件が増えるたびに書き足す必要がない

Bladeのoldヘルパー使えてる?

良く編集画面とかで動的に変わる選択ボックスとか見かけますよね??(この辺割と記事ない印象)
そういう時入力値を優先させて、old()がなければDBの値を反映して一致した時にselectedとかif文でゴニョゴニョしまくってませんか?

勿論過去の僕はしてます。
しかも型を厳密にチェックしてません。
悪い子です。

過去コード

edit.blade.php
<select name="category_id" class="form-control">
    @foreach ($categories as $category)
        <option value="{{ $category->id }}"
                @if(!empty(old('category')))
                    @if(old('category') == $category->id)
                        selected
                    @endif
                @elseif($project->category_id == $category->id)
                    selected
                @endif
            >{{ $category->name }}</option>
    @endforeach
</select>

上のコードもold()の第二引数を使用すればここまでスッキリかけます!
(ちょっと横に長いけど、改行してもよし)

改善コード

edit.blade.php
<select name="category_id" class="form-control">
    @foreach ($categories as $category)
        <option value="{{ $category->id }}" @if ($category->id === (int) old('category_id', $project->category_id)) selected @endif>{{ $category->name }}</option>
    @endforeach
</select>

ちなみにold()はstringで返ってくるのでintに変換してます。

改善ポイント

  • 無駄なif文を省いて見易くなった

終わり

誰もこんなクソコードを量産して欲しく無かったので戒めとして書きました。
皆さんも昔のソースコード久々に覗いてみては?
乾いた笑いが出ると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?【CakePHP2】js.mapファイルが存在しないエラーの解消法

環境

PHP 7.2.21
CakePHP 2.10.18

やりたいこと

pnotifyを追加したところ、下記の様なJsControllerが無いエラーが多数吐かれていたが動作に問題はなかった
が、このまま放置は良くないので出ないように修正したい

error.log
2020-03-04 10:10:58 Error: [MissingControllerException] Controller class JsController could not be found.
Exception Attributes: array (
  'class' => 'JsController',
  'plugin' => NULL,
)
Request URL: /js/vendor/jquery-pnotify/pnotify.js.map
Stack Trace:
#0 /***/***/***/webroot/index.php(100): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#1 {main}

やったこと

pnotify.custom.min.js
/*
PNotify 3.2.0 sciactive.com/pnotify/
(C) 2015 Hunter Perrin; Google, Inc.
license Apache-2.0
*/
!function(t,i) *******省略******* {"feturn s(i)});
//# sourceMappingURL=pnotify.js.map ←これを削除もしくはmapファイルを追加で対応可能

ここからmapファイルをダウンロードし同階層に格納
https://cdnjs.com/libraries/pnotify

もしくは最下部にあるmapファイルのリンクを削除

pnotify.custom.min.js
//# sourceMappingURL=pnotify.js.map
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fzf.vimでtagsを検索しながら色付きのpreviewを表示する

PHPでせめて定義元ぐらいは辿りたいんだけど、Language Serverが使えない、phpcdも使えない、けれど絶対Vim使うってときに、ctagsを使うことにした。他のプラグインとかで何とかなるかもしれないけど、何とかなるって情報が見つけられなかった。

仕方なくctagsでタグを作ってfzf.vimでpreviewを表示させながら選びたい。

どういうことか

こういうファイルがあって、test2()を辿りたい。

caller.php
<?php

require_once './functions.php';
test2();
functions.php
<?php

function test1()
{
    echo __FUNCTION__;
}


function test2()
{
    echo __FUNCTION__;
}

function test3()
{
    echo __FUNCTION__;
}

どうするか

ひとまずctagsでtagsファイルを作っておく。

$ ls
caller.php  functions.php
$ ctags -R # 作る
$ ls
caller.php  functions.php  tags # tagsができあがる
$ cat tags # tagsの中身
!_TAG_FILE_FORMAT   2   /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED   1   /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR    Darren Hiebert  /dhiebert@users.sourceforge.net/
!_TAG_PROGRAM_NAME  Exuberant Ctags //
!_TAG_PROGRAM_URL   http://ctags.sourceforge.net    /official site/
!_TAG_PROGRAM_VERSION   5.8 //
test1   functions.php   /^function test1()$/;"  f
test2   functions.php   /^function test2()$/;"  f
test3   functions.php   /^function test3()$/;"  f

設定

fzf.vimはpreviewを外部から受け取れる。今回はshellScriptを一つ作って、previewに渡す。

/path/to/ctags_preview.sh
#!/usr/bin/env zsh

NAME=$1
FILE=$2

 #タブに囲まれた3つめの検索パターンの抽出
 #/^ any $/;" を ^ any $ に置換
PATTERN=$(echo $3 | cut -f 3 | sed "s/^\///" | sed -E "s/\/;\".*$//" | sed "s/\"/\\\\\"/g")

# 前5行、後5行を表示、-48でgrepしてるのは検索文字列に色をつけるため
grep -B 5 -A 42 --color=never "${PATTERN}" $FILE | grep -48 --color=always $NAME

で、~/.vimrcに以下を書き込む。

~/.vimrcか他のconfig
command! -bang Tags
  \ call fzf#vim#tags(expand('<cword>'), {
  \     'options': '+i
  \                 --exact
  \                 --with-nth 1
  \                 --reverse
  \                 --no-unicode
  \                 --preview "/path/to/ctags_preview.sh {1} {2} {}"
  \                     '
  \ })

nnoremap <silent> <Leader>ft :<C-u>Tags<CR>

こうなる

caller.phpのtest2の上で、ftを押すと、下記のようなプレビューが表示される。(floating windowにしてある)

スクリーンショット 2020-04-09 1.10.38.png

まぁ……全然完全ではないんだけど……。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel の Policy ってどんな場合に使うの?

特定のリソースに対するユーザーアクションを認可する仕組みだそうです。
例えば、ブログの記事削除は本人にしかできないようにすることが可能になる仕組みのことです。

Policyを使わないで書くと以下のようになり、

public function delete(Article $article)
{
    if (request()->article()->isNot($article->user)) {
        return response([], 403);
    }

    $article->delete();
}

ArticlePolicyを作成すると、

ArticlePlicy.php
public function delete(User $user, Article $article)
{
    return $user->id == $article->user_id;
}

以下のように書ける。

public function delete(Article $article)
{
    $this->authorize('delete', $article);

    $article->delete();
}

また、ミドルウェアによる認可もできる。以下のように書ける。

api.php
Route::delete('/articles/{article}', 'ArticlesController@destroy')->middleware('can:delete,article');

今の所、アカウントの種類によって認可するアクションを切り分けたいときに使う認識でいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む