ログ日記

作業ログと日記とメモ

テンプレートエンジンを使うのをやめたい

BladeOneをアップデートしたら、include時の変数割り当てのフローが変わったのか、既存変数が上書きされてエラーが出るようになってしまった。
本家Bladeではこういったことは起きない?
仕方がないのでバージョン固定した。

ソースを追っても何となく不毛なことになりそうなので、少しずつ外していくことを目指したい。
PHPの型が強化されてきたことだし、そろそろデータの受け渡しに配列や動的変数を使うことをやめたい。かなり減らしたけど、まだ@param mixedと書いている部分も多い。


素のPHPを使って、かつnamespaceを使って、型チェックもして、ビューからロジックはなるべく呼ばないようにして、などという設計に関する蓄積がない。
テンプレートエンジン無しっていうのは手を抜くためとか余計なものを入れないとかの理由が多かったように思う。


と思っていたのだけど
teratail.com
同じような人も居るには居るのか。

開発マシンの Debian stretch を buster にアップグレードする

GUIで使っていてOfficeやらGHCやら色々入っている。PHP5も入っている。
Dockerもsystemd-nspawnコンテナも入っている。
かなり躊躇していたが、そろそろアップグレードする。
サーバーで使っている Debian を buster にアップグレードしたログ - ログ日記
CUIなら問題なかったんだけど、どうなることやら。

まず、元に戻せるようにスナップショットを作成。
ホストからsshで入って作業する。

sources.list を busterに変更。独自に追加したリポジトリコメントアウト

apt update
apt-get upgrade

ファイル変更はNOで。

apt full-upgrade

削除されるパッケージのメモ
emacs25 php5-curl php7.0-curl php7.2-curl php7.3-curl php7.4-curl ruby-compass uim-qt uim-utils
libboost-*-1.62-dev default-java-plugin

アップグレード: 1072 個、新規インストール: 744 個、削除: 80 個、保留: 0 個。

あー、ここで /etc/apt/sources.list.d を見るのを忘れたことに気付いた。
結構 stretch 用の記述があるが…。自分で書いてないのでstretchとstableが混在しているな。
jessieもあるし、これどうしよう。

幸い(?)何故か403エラーで止まったので、手動で書き換えてから実行する。
sources.list.d のファイルをbusterに書き換えてエラーになったらコメントアウトする方針で。
buster-updates も取りあえずコメントアウト
それから今jpがダメっぽいので deb.debian.org に書き換えて再実行。
終わればclean。

apt autoremove

reboot

fc-cacheがエラーになったので再生成。

rm .cache/fontconfig/*
fc-cache -fv

emacs のフォントを再設定。*1

外観がちょっとおかしかったので外観の設定で選択し直す。
MATEのUIがわずかにリッチになって、ちょっともっさりしている気がする。
Alt+Tabの切り替えの表示が変わって分かりにくくなった。どこかに設定がある?
ターミナルの色がちょっと変になった。これもフォントやテーマがリセットされたからかな。



他に、特に大きな問題は無い模様。
emacsは25から26になってもフォントの設定を直せばそのまま使えた。
Eclipseもそのまま使える。
古いPHPや古いMySQLも使える。MySQLPostgreSQLはそれぞれ本家のリポジトリを追加しているから特に変更なし。PHPも独自リポジトリ。古いバージョンもそのまま。
Chrome Beta は枠が変な感じになった。MATEテーマのせい?
VS Code は使ってないけど起動はできた。
docker, kubernetes(使ってない), systemd-nspawn も異常なし。
systemdコンテナの中のOSも問題なく動いている。systemdのネットワーク設定を結構いじったけど、特に問題なし。
日本語入力もそのまま。
パッケージ指定がjessieになっていてコメントアウトしたslackも使えた。まあこれは普段はブラウザで使うけども。
パネルに追加したシステムモニターの負荷グラフの描写がちょっと変わったような。負荷が高く見える。

使えなくなったら困るアプリ gnucash も自動でデータ置き場が更新された模様。
LibreOfficeの起動画面がやたらカラフルになった。


動作確認はこれぐらいかな?
MATEの設定の細かい変更方法が分からん…。
「外観」メニュー以外に設定は無いんだろうか?
ググラビリティ低すぎるだろう…。

Alt + Tab でスイッチする操作中のウィンドウにマーカーが出て欲しいんだが。

もっさりしているのは「ウィンドウ」の「コンポジットマネージャー」をオフにすれば元に戻った。
あと、openboxの設定メニューがあるんだけど動いてないっぽい。何をどうやってインストールしたか忘れた。

ウィンドウの切り替えやテーマ、動きはLXDEが良いんだけど、パネルはMATEがいいんだよね。どうしよう。


※ 9/7 ひとまずMATEで使ってみてる。あとめっちゃtypoってた。

PHPStanのextensionでnamespace・class名の依存関係チェックする

前に書いた記事
DDDのさわりをやろうとした - ログ日記
ここで作ろうと思っていた拡張を作った。


github.com

サンプル。
https://github.com/nishimura/phpstan-namespace-dependency-sample


実際に作ってみると、各レイヤーごとの依存関係の認識が曖昧なことに気が付いた。

DomainはApplicationServceで生成するけど、生成したDomainのオブジェクトをそのまま返却しても良い?
ドメインのオブジェクトをそのまま返却してしまうと、Presentation層などでドメインの複雑な操作をしてしまえるので、Dtoに詰め替えてApplicationServiceResponse的なオブジェクトを返却すべきだった?
Javaなら例外を各層ごとにわざわざcatchして詰め替えてthrowするのが普通に見られるけど、PHPだとそこまではしないよね?
MVCのモデルをビューにそのまま渡して良い?簡略化したMVC2的なものは、ViewがModelに依存してしまっているよね?Modelのメソッドが変わったらViewに変更が必要だよね?
テンプレートで$entity->save() とかできるオブジェクトを扱うべきではないよね?
とか。


まあPHPだしそこまで細かくインターフェースを分離しなくていいかと思ってたことが色々とあぶり出された。

もっとちゃんと設計している人はこの辺をうまく整理して実装しているんだろうね。
自分は、最初は気を付けていてもいつの間にか忘れてメインのロジックが詰まったオブジェクトをテンプレートに渡したりしていた。
ある程度の規模までとかあまり複雑でない処理ならそっちの方が早いから、問題になることもなかった。

そんな省略したCRUDを早く作るための書き方に慣れている人でも、強制的にDDDできるツールになった。(のかどうかはこれから時間ができたら試す)

PHPの開発環境とライブラリと振り返りと近況

昔のコードを触る機会があった。
PHPの自作フレームワーク現状まとめ - ログ日記
この辺のやつ。
もう7年も前か…。

当時はテンプレートエンジンに変数を渡す場合、アクションコントローラーでメソッドを呼び出すのが流行っていた。

<?php
class MyController {
    public function index() {
        $this->set('foo', 1);
    }
}

<?php
class MyController {
    public function index() {
        $this->foo = 1;
    }
}

のような感じ。
どこでも変数を設定できるから作るときは楽なんだけど、コードが膨れてきたり後から見返すとあちこちでテンプレート変数が設定されていたりして大変だった。
それから、自分の作った機能なのに自動設定しすぎで結構忘れていた。

PHPのフレームワークを作った - ログ日記
@var でリクエスト変数のIDからDBアクセスして取得したオブジェクトをインジェクションするのはやりすぎだったかもわからん。
慣れると開発がめっちゃ早いんだけど。

$db->save($obj) で保存するのは楽だし、where(['col' => $value]) でSQL作るのも楽。
多少遅くてもいいならORMも便利だったなあと思い出してきた。

ただし、arrayや動的生成を多用していて型チェックされないのは不安だし、定義にジャンプできない部分が結構あった。
テンプレートエンジンの箇所でバグが出るのも若干不安。


それで忘れていた考え方の振り返り。
ぼんやり振り返り - ログ日記
この頃は、動的にコードを生成したり、流れるようなインターフェースにしようとしていたりしていた。
静的解析は使っていなかったし、コード補完もほとんど使ってなかった。書き方の問題もあって精度が低かったし。


その後、フルスクラッチの案件はあまりやらなくなって、WordPressとかEC-CUBEとかどこかの独自システムとかの修正等をやっていた。



そして最近、再びフルスクラッチでシステムを作った。
今見たらPHP 6万行、HTML 2万5千行、JavaScript 1万行あった。
PHPのテスト 8千行、CSS 1万行 も入れたら、10万行超えるね。
ここまでやったのは初めてかもしれない。

PHPのツールは
・自作フレームワーク
・自作ORM
・自作テンプレートエンジン
jQuery

から

・自作DIコンテナ PHPのDIコンテナを作った話 - Qiita
・FastRoute
・BladeOne
・Closure Library

に変えた。
速度重視の方針なので、基本ベタ書き。
フレームワークを使うと自動生成に頼っちゃうけど、手書きだとクラスを自分で書くことになる。
その代わりPHPStan のレベルMAXでチェックできて、クラスの定義もすぐ見れて安心感がある。
PHPStanに慣れてきてジェネリクスまで使えるとなると、今はBladeOneのテンプレートエンジンを外したくなってるところ。PHP側でめっちゃ細かく型チェックしてるのにテンプレートエンジンがザルだと意味ないじゃんっていう。

JavaScriptは速度重視なのでClosute Compiler、それならClosure Libraryだろうってことで。
PHPフルスタックは嫌だけど、JavaScriptはnpmの細かいライブラリの方が嫌だ。なので依存はClosure Toolsだけにする。
CSSもClosure Stylesheets。最終更新めっちゃ古いけど。

PHPは、FastRouteとDIコンテナとzend-diactoros とBladeOne、セッションライブラリ、Monologやちょっとしたツール系を繋げる薄いフレームワーク的なものを書いた。1000行いってないのでフレームワークというほどでもない。



開発環境は
・etags, gtags
PHPのモデル、コントローラー、HTML、CSSなどで分けてターミナルからEmacsを複数ウィンドウで立ち上げて並べる
・ローカルのApacheにバーチャルドメインで開発環境を設定、DBその他サービスもローカルにインストール
・GitLabから独自hook呼び出し

から

・gtags, ac-php
purpose-mode でウィンドウを複数立ち上げて並べる
systemd-nspawn で開発サーバーを用意
・GitLab CI

という感じに変わった。
あまり大きくは変わってないか。
Ansibleを使い始めたのが大きいかな。全体的に一周遅れてる感があるけど、KubernetesやDockerはオーバースペックなのでしゃーない。
DockerはCIのテストで使ってる。

サーバーは相変わらずさくらクラウドとさくらVPSコスパが良い。
大量アクセスをさばくのではなくて、1回のリクエストの応答を0.2秒以内にしたいとかいう方向の速度を出したいという要求がある。
個人的にも、最近のスマホ版Webは好きじゃない。細かくリクエストが分かれていてローディングアイコンがいつまでもクルクルしているし、もっさりしているし、スクロールが飛ぶし、リッチさにハードスペックが追いついてない感じがしている。
スケールアップで十分な規模のサイトをスケールアウトするのはナンセンスな感じもある。

あと、ネットショップばっかりやってるっていうのもあるかもね。
トランザクション大事。




そんな感じで最近はやっている。
でもさすがにSQLベタ書きは疲れてきたので、型チェック厳しめのORMを作りたいとか思っている。
関数呼び出しとオブジェクト生成って地味に処理時間取られるからあまり複雑なことはやりたくないんだけどね。管理者側の単純なCRUDならORMの方が開発コストが大幅に低くなるので、迷っている。
逆に、表側は長大なSQLを書いているのでORMは無理。

ひとまず次にやることは、テンプレートエンジンを外したり、コードを整理したり、テストを書いたりかなあ。
テストは金額計算と日付計算しかやってない。これで十分といえば十分だけども。

あとはE2Eテストか…。
何個か書いたけど、重いので増やしたくない方向に意識が傾いてしまう。
push時に実行するテストと毎日1回実行するテストを分ける、とかかなあ。


テンプレートエンジンを外すというのは、
型がある時代のPHP、テンプレートエンジンでも型チェックしたい - Qiita
こういうこと。
あと、これだとhtmlspecialcharsを忘れて危険なので
PHPStanで素のPHPをテンプレートとして使うとき、htmlspecialcharsをチェックする - Qiita
これもセットで。
少しずつやってる。

Debian Buster に Cacti をインストールする

ログをElasticSearch+Kibanaに集約したり、Prometheus を使ってみたりしたけれども、普通にサーバーのログファイル+Cactiに戻ってきた。

最近の流行は大量のサーバーとか大量のコンテナとかがメインターゲットなんだね。
普通に数台〜十数台のコンテナではないサーバーなら、ログファイルとCactiが一番使いやすそうということになった。

実際に長期間KibanaとPrometheusを試したわけではないから分からないけれども。

そういうわけでDebian BusterにCactiをインストールしたログ。

apt install cacti cacti-spine
chown www-data:www-data /var/log/cacti/*

インストール時にログの所有者がwww-dataになっていないので手動変更する。
自動でapache2とmod-phpがインストールされる。
普段はnginxとphp-fpmを使っているけれど、自動インストールに任せたかったのでapacheのままにした。

/etc/apache2/conf-available/cacti.conf に設定があり、これだけで /cacti で既に利用できるようになっているけれども、sslとか独自サブドメインとかの設定をするなら普通にsites-availableを自分で用意する。


それから、SNMPは使わないので
Installing Percona Monitoring Plugins for Cacti
PerconaのCactiプラグインを入れる。このテンプレートで、SNMPの代わりにSSHで繋げる。
(NginxでもSSHしてからwgetするので、直接繋ぎたい場合は後でコマンドに --use-ssh 0 を付ける)

busterが無くstretchしか無いけれども、特に依存があるわけでもないのでstretch用パッケージで問題ない。

PostgreSQL
PostgreSQL Host Template - Cacti

PHP-FPM
GitHub - glensc/cacti-template-php-fpm: Cacti PHP-FPM template


どれもあまりメンテされてなさそうだけれども、使える。
cactiの過去グラフが平均化されないようにする方法 - Qiita
この辺も設定する。


tholdプラグインは、gitで取ってきて 2ea956d6 をインストール。
バージョンが合わない問題、バージョンタグが細かく打たれていない問題があるので、互換性がある最新のハッシュを探す必要があった。
v1.2のようにタグを決め打ちで取得すると、バグがかなりある。
2ea956d6 でも若干怪しい部分があるが、これ以上新しくすると互換性がなくなる。


それから
MAILER WARNING: Mail failed from... - Cacti
このようなエラーに対応する。

Debian公式パッケージになっているのに、バグがあるとはね…。
人気の無いパッケージのつらさよ。


まあでも一度動き始めれば使い続けられるという安心感があるし、PHPerなので多少のバグは自分で直せる。
色々試して、RRDTool だとディスク容量が増えない安心感があるということも実感した。
ログを3年保存とかにしても、設定直後にディスクが確保されるから、その時点で足りていれば大丈夫。
サーバー数が倍になる予定なら、あとから2倍の容量になるだろうという予測も簡単。


実際の設定反映はAnsibleにしている。
各ノードにはsshのログが大量に出るので、/etc/rsyslog.d/cacti.conf に

if \
  ($msg contains 'Received disconnect from 192.168.0.10') or \
  ($msg contains 'Disconnected from user cacti 192.168.0.10') or \
  ($msg contains 'Accepted publickey for cacti from 192.168.0.10' ) then \
  /var/log/cacti.log
& ~

を追加して、/etc/logrotate.d/rsyslog に /var/log/cacti.log を追加したりといったことも自動化した。

プログラムの分け方とディレクトリ構造

最近、プログラムのディレクトリレイアウトというかファイルを置く場所を変えようと試みている。



旧来のMVC的な構造だと、ControllerやModelのディレクトリを分ける、レイヤーごとにまとめる、という感じになると思う。
商品情報表示画面、その管理画面、CSVダウンロード機能があるとすると、

old1
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       └── product.blade.php
    └── product.blade.php

こんな感じになると思う。
ModelはDomainとかServiceとかInfraとか、そういうのでも良い。

ここに、期間限定キャンペーン用商品に関する機能を追加するとしよう。
特殊な画面と特殊な処理なので、DBに数項目追加するといった対応はできないとする。
そうすると

old2
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── CampaignProductCsvController.php
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   ├── CampaignProductController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   ├── CampaignProductDto.php
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CampaignProductCsvModel.php
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       ├── CampaignProductBehavior.php
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       ├── campaign_product.blade.php
    │       └── product.blade.php
    ├── campaign_product.blade.php
    └── product.blade.php

こんな感じのディレクトリレイアウト、クラス名、テンプレート名で機能追加するだろうか。

フレームワークなんかでは専用コマンドが用意されたりしていて、scaffoldと呼ばれたりもする。
これで一気にController、Model、Viewのファイルを生成したりするわけだけれども、そもそもの構造が複雑だからこそ専用コマンドが複雑になるとも言える。

自分はこの自動生成は好きではない。AbstractControllerやAbstractModelも好きではない。できるだけシンプルにしたい。
最近その辺りの構造を大きく変えようと思っている。


基本的には層でディレクトリを分けるのではなくて、画面単位で分ける。

new1
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        └── ProductBehavior.php

こんな感じにする。
ControllerはMVC的なコントロールはしないので単にPageという名前する。
テンプレートエンジンはやめて、Html.php に昔ながらのやり方で書く。とは言ってもネームスペースとクラスは使う。
Page.php から ModelやViewModelを使って何らかの処理をした後に、Dtoに詰め込んでHtml.php に渡す。これを同じディレクトリに置く。
2箇所以上で使う処理は旧来のModelやService等に分ける。1箇所からしか使われない処理はPageクラスの近くに置く。先のことは考えない。他のPageでの処理を使いたくなったその時に共通化して移動する。

キャンペーン機能を追加した後はこんな感じ。

new2
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   ├── CampaignInterface.php
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── Campaign
    │   │       │   ├── CsvModel.php
    │   │       │   ├── CsvPage.php
    │   │       │   └── Html.php
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Campaign
    │       │   ├── Dto.php
    │       │   ├── Html.php
    │       │   └── Page.php
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        ├── CampaignProductBehavior.php
        └── ProductBehavior.php

ここでも各レイヤーをまとめて同じディレクトリに追加する。この方が見やすく、依存関係も分かりやすい。
リファクタリングも手軽にできる。


前提として、DBで

select * from product

のようなことはしない、という要因もあるかもしれない。

select product_id, name, description from product

のように、必要な項目だけ取得する。
なおかつ、型指定ありのDtoに詰め替える。
逆に言うと、型指定ありのDtoで必要な項目だけ詰めて渡すようなことをやり始めると、あまり共通化ができない、とも言える。

Html.php もPHPStanで静的解析に含むので、かなり安心感がある。

もし共通化してDBの返却値も共通化したりすると、富豪的なDBアクセスとオブジェクト生成で遅くなる。
各画面でDBの同じテーブルにアクセスしたとしても、必要な項目まで同じということはあまり起こらない。それでも共通化するとしたら、各画面で必要な項目をどんどん追加していくことになってDB処理が膨れていく。
機能を削除したとしても、迂闊に項目を消したりできない。

そういった理由で、今は徐々にテンプレートエンジンを使うのをやめて各ページの処理とHTMLを同じ場所に移動したりして、徐々に変更して試している。

Debian stretch で MySQL 5.6 のアップグレード

本家のリポジトリを使ったMySQLの5.6.47 から 5.6.48 のアップグレードが失敗する。


/usr/bin/mysqld_safe のスクリプトが間違っているっぽい。
データディレクトリが /usr/data になってしまう。
OSもMySQLも古いバージョンだから、メンテされていないのかもしれない。

ファイルを書き換えたり色々試したけど、インストールの前後の処理で上書きされたり、post install が止まったりして不安定だった。

ln -s /var/lib/mysql /usr/data

取り急ぎリンクを張ってやり過ごした。


apt でエラーが出たときのコマンドメモ。

dpkg --audit
dpkg --configure --pending
apt --fix-broken install

色々案内されたけど、インストールスクリプトが間違ってたらどうしようもないよね。


apt install --reinstall mysql-server mysql-community-server ではうまくいかず、一旦removeしてinstallした。
パッケージを消してもデータはそのまま残ってるので後でinstallすれば使える。