【Python】htmlファイルの改行とタブを削除するやつ

概要

htmlビルダーを利用して仕事をしているのだが複雑になってくると最終的に出力されるhtmlと、デザイナーが作ったこういう風に作ってください、というモックのhtmlとちゃんとあっているかを目で確認しないといけないのは非常に面倒である。Diffツールを使おうにも改行とかタブの数が若干違う、ということで差分として出されてしまうし、どうしようかと思った。 (もしかしてまともなエンジニアは何かツールを使っていたりする…?) でもまぁ、自分でミニマムに使えるので十分、というかんじなのでツールを探さずに話を進める。

出力されているhtmlの比較にはDiffツールを使うのだが、改行とかタブの数とかをいちいち直すのも手間なので、いっそhtmlタグごとに改行してタグの前方に着いているタブ(2スペや4スペ)を削除してから、Diffツールにかければ差分もまだ少なくなるかな、というのでpythonで作った。

<div>
  <ul>
    <li><span class="aaa">aaa</span></li>
    <li><span class="bbb">bbb</span></li>
  </ul>
</div>
<div>
<ul>
<li>
<span class="aaa">
aaa
</span>
</li>
<li>
<span class="bbb">
bbb
</span>
</li>
</ul>
</div>

以下がコードである。flatten.pyという名前ででも保存して、以下のように実行すると同じフォルダにテキストファイルが出力(ファイル名はもとのtxtファイルの先頭に_(アンダースコア)が付与された名前)されるので、htmlビルダーで出力した用とデザイナーが作った用で2回実行して出力された2ファイルをDiffにかければ使える。

python3 flatten.py hogehoge.txt
python3 flatten.py fugafuga.txt
import sys
import re

argvs = sys.argv

f = open(argvs[1])
file_contents = f.read()
f.close()

replaced_val = file_contents.replace("    ", "")
replaced_val = re.sub(r'\t', "", replaced_val)

replaced_val = re.sub(r'(<!--.*?>)', '', replaced_val)
replaced_val = re.sub(r'(<.*?>)', r'\1\n', replaced_val) # \1はマッチした文字の再利用(※1)
replaced_val = re.sub(r'(\n){2,10}', '\n', replaced_val) # 10である必要はないけど

file = open('_' + argvs[1] + '.txt', 'w')
file.write(replaced_val)

感想

すごく雑だけど、チェックしやすくなったー!

参考

※1 正規表現で一致した文字列を取得し、置換のときに再利用する on Python3

【Laravel】Eager-Loadingでlimitを使ってはいけない

概要

いけないというか、Eager-Loadingでlimitを利用する際は挙動をちゃんと理解していないと思わぬ動作をする。

本題

以下のようなテーブルがあった際にBookに紐づくChapterをEager-Loadingでそれぞれの本の最後の章(といっても2章までしかデータがないけど)を取得したい場合を考える。

Books
--------------
| id |  name |
|----|-------|
|  1 | book1 |
|  2 | book2 |
|  3 | book3 |
-------------

Chapters
------------------------------
| id | book_id | chapter_num |
|----|---------|-------------|
|  1 |       1 |           1 |
|  2 |       1 |           2 |
|  3 |       2 |           1 |
|  4 |       2 |           2 |
|  5 |       3 |           1 |
------------------------------

そのためにまず、Book-Chapterのテーブル結合を行う。

public function lastChapter()
{
  return $this->hasMany(Chapter::class)
  ->orderBy('chapter_num', 'desc');
}
$books = Book::with(['lastChapter',])->get()

ここで、私は最後の章だけを取得したいが、現在のlastChapterではchapter_num順に並び替えられているだけなので、 limit(1)を付与すれば、各Bookの最終章を取れるのではないか、と考えたのでlimit(1)を付与した。

public function lastChapter()
{
  return $this->hasMany(Chapter::class)
  ->orderBy('chapter_num', 'desc')
  ->limit(1);
}

しかしながら、limit(1)を付与すると3つのBook中2つのBookのlastChapterが空っぽだった。 ここで、Eager-Loadingの挙動について調べてみると、

Eager-Loadingによりlimit(1)を付与する前のlastChapterは以下のように変換される。

SELECT 
* 
FROM `chapters` 
WHERE `chapters`.`book_id` IN ('1', '2', '3'

)
 ORDER BY `id` DESC

なんと、内部的にはjoinしているのではなく、Chaptersのテーブルから含まれるBookのidをIN句で取得していた。 なるほど、ということはBooksとChaptersをjoinするのではなく、BooksとChaptersを別々に取得して 各Collectionオブジェクトを結合しているらしい。

そのため、limit(1)を付与すると以下のクエリが発行されるが、Chaptersのデータが1行しか取れずに、 Booksの結果CollectionとChaptersの結果Collectionで結合できるのが1行のデータ分しか結合できないため、 3つ中2つのBookのlastChapterが空っぽになるというわけだ。

SELECT 
* 
FROM `chapters` 
WHERE `chapters`.`book_id` IN ('1', '2', '3'

)
 ORDER BY `id` DESC limit 1

そのため、最終章を取得するためにはEager-Loadingは利用せずにクエリビルダーを使って最適なクエリを自前で記述するか、 Eager-Loadingを利用して最終章以外のデータも取得されてしまうが、それを受け入れて以下のように処理するのが良いだろう。

foreach($books as $book){
  $book->lastChapter->first()
}

【Laravel】TokenMismatchExceptionのエラーが出た

概要

LaravelをAPIサーバーとして利用したが、POST時にTokenMismatchExceptionと出てきたのでメモ。

対応方法

CSRFのチェックを外すためにapp\http\Middleware\VerifyCsrfToken.phpの中に以下を記載。

protected $except = [
  'api/*' 
];

しかしこれはセキュアではないためしっかりとCSRFの対策をしなければいけない。 このWeb APIってCSRF対策出来てますか?って質問にこたえよう を読むとどういったAPIサーバにおけるCSRFの対策の仕方がわかる。

参考

https://stackoverflow.com/questions/37383165/tokenmismatchexception-for-api-in-laravel-5-2-31 https://qiita.com/rana_kualu/items/3f9d0d6b9a363fd2108e https://qiita.com/maruloop/items/e14d02299bd136f4b1fc

【Laravel】ファイルキャッシュが動くかテスト

概要

ファイルキャッシュを使って、1分経過したらキャッシュから消えるようにした。

方法

実際に、Laravelのドキュメントの通りに実装すると

/storage/framework/cacheの場所にキャッシュができていた。

$value = \Cache::store('file')->remember('key', 1, function() {
    return DB::table('test')->get();
});

1分後にキャッシュが消えていることを確認すべく/storage/framework/cacheに作成された キャッシュデータを確認したが、いくら待てども消えなかった。 なぜ消えないかはさておき、別の方法としてhasメソッドを使ってキャッシュの存在を確認してみた。

if (\Cache::store('file')->has("key")) {
  // 存在する場合
}else{
  // 存在しない場合
}

これを実行すると、1分後にキャッシュが存在しない場合のほうに処理が入ったので、とりあえずキャッシュは消えていそうである。

追記

不要なキャッシュを削除するためにはどうやらLaravelはキャッシュクリアコマンド(artisan)を利用する必要がありそう。

qiita.com

Laravelのアーキテクチャ

概要

自分的なLaravelの運用しやすいアーキテクチャ案ができたのでメモ。 細かい部分はプロジェクトによって異なると思うので、大枠的なアーキテクチャを述べる。

自分なりの答え

LaravelではMVCとしての開発の準備が充実しているため、MVCをベースとしたアーキテクチャーについて考える。

MVCオブジェクト指向プログラミングではよく聞くWebアプリケーションの作り方であるが、Modelを効果的にデザインすることで見通しの立ちやすいコードを書くことができる。 Webアプリケーションは開発してそのまま、ということはなく機能拡張を行ったりパフォーマンスチューニングを行ったり、メンテナンスされていくものであるため、機能拡張や変更に対応していくことのできるアーキテクチャにすることが理想的である。 そのため、見通しの良いコードを書くことは機能拡張の際にも柔軟に対応することができるようになり理想に近づくことができる。

ところで、LaravelではMVCの開発の準備がされているものの、Laravel側としてはどういうMVCを構築してほしいのかを考えてみる。LaravelではControllerとViewを格納するためのフォルダは用意されている。しかしながら、Model用のフォルダは用意されていない。理由はMVCのMがどういうことをするか、どういう構成にするかは人によって異なるからである。人によっては論理操作やDB操作を単一のModelに全て押し込める人もいるだろうし、そうでない人もいるが私はLaravel/PHPの特徴を利用してModelを細かく分割するデザインをしていく。 一般的に、Modelで行ってほしいことは以下である。

なるべくControllerには無用に命令的なコードを書かずに、論理で追っていくことのできるスタイルが理想であると私は考えている。以降で「論理」という言葉が多用されるが、その意味は「処理を意味的なものにする」くらいに思っておいてほしいが、多分説明になっていないので下記のコードで説明する。足し算をする、というコードがあったとする。その際には、以下のように書くのが一般的である。

$a = 1;
$b = 2;
$result = a + b;

しかしながら、addというメソッドを用意することで以下のように書き換えることができる。

$a = 1;
$b = 2;
$result = add(a, b); // addメソッドはどこかで宣言されている

例のスケールが小さすぎるので何が嬉しいかはわかりにくいが、後者はaddメソッドによりaとbを足すんだな、と一目見ただけでわかる。このaddメソッドのように命令的な処理を関数としてラップした、意味単位でのまとまりを「論理」と呼ぶことにする。

話をModelに戻す。Cotrollerではなるべく論理を利用してコードを書くために、命令的な部分(ビジネスロジックやDB操作)はModelとして水面下に隠されるべきである。

そのために、まずビジネスロジックはどのように水面下に隠すべきであるか。ビジネスロジックとはどういった存在であるかを考えると、単なる論理であるべきで、それ自身がオブジェクトになったりするものではない。そうしたことを踏まえるとphpにはtraitが使えるのでこれを使用する。traitのような論理を格納する場所はいまのところないので「Traits」や「Services」のような名前のディレクトリを作って、そこへ入れてあげれば良い。 これにより、オブジェクト指向でありながら関数型プログラミングのような論理によるコードを書くことができ、コードの見通しが立ちやすくなる。

.

├── Cotrollers
│   └── UserCotroller.php
│
├── Services(Traitsでも良いと思う)
│   └── UserService.php(UserTraits.phpでも良いと思う)

これでビジネスロジックはControllerから剥がすことができたので、次はDB処理の仕方を考える。 Laravelを利用しているのであればぜひEloquentを利用するべきである。 Eloquentでは以下のような記法でDB処理を行う(joinの例)。 我々がDBに対してjoin句を書く部分はhasOneメソッドにより論理的な操作ができるようになっている。

// 連携情報の宣言(論理の宣言)
public function foo()
{
    return $this->hasOne('連携先テーブル名');
}

// 連携情報の宣言(論理の宣言)
public function foo2()
{
    return $this-where('id' > 0);
}

// 上記の宣言を利用するためには次のようにする
$this->foo()->foo2()->get();

上記の例のようにテーブル連結の宣言やwhereによる条件指定をラップしたメソッドを宣言したが、このようなものをたくさん宣言していくことになる。そして、このたくさん宣言された論理をレゴブロックのように組み合わせることでDBのデータを取得できる。

これがEloquentの強みであるが、上記の一連のDB操作は大きく分けて「論理の宣言」と「利用」部分で分割することができる。 そのため、DB操作をさらに「論理」と「利用」に分けることで、DB操作の部分がごちゃごちゃしないスマートなアーキテクチャにすることができる。

これを実現するのにDBアクセスの処理をビジネスロジックから切り離す手法であるRepositroyパターンを使用する。 調べてみるとRepositoryパターンをLaravelで導入するQiita記事(これ)があるためこれを参考にして話を進める。

上記の記事では以下のような構成にしているので同じ構成にする。

.

├── Models
│   ├── User.php
│   
├── Repositories
    └── User
        ├── UserRepository.php
        └── UserRepositoryInterface.php

さて、ModelsとRepositoriesの2つのディレクトリを作成することになるが、Modelsは論理を書く場所、RepositoriesはModelsで宣言されたレゴブロックを組み立てる場所として使う。 私の中でのルールはControllerからDB処理を呼び出すのはRepositoriesであり、原則Modelを直接呼び出さない。 中には、Repositoriesなんか必要なくてControllerに書けばよい、という人もいると思う。 確かにディレクトリが増えるし、概念も増えてしまっている。しかし、残念ながら全てのDB操作がEloquentで済まない場合があったり、DB操作をチューニングして高速にしたい場合、どのようにするか。もしRepositoriesがない場合はControllerかModelsにDB処理を書くことになる。 そうすると、ControllerもしくはModelsの役割がごちゃごちゃしてしまったり、Controllerが太ってしまったり残念なことになってしまう。

結果

MVCのMをService(トレイト)とRepository(レポジトリ)とModel(Eloquentの論理宣言)の3つに分かれたが、これにより各機能が意味的に分割され、更には処理を論理的な操作で可能にすることにより、見通しが立ちやすく、保守しやすいものになった。

(おまけ)Laravelのここが良い

しっかりとしたアーキテクチャを構成するために使いたいLaravelの機能。

Eloquent ORM

Eloquentを利用することで見通しの良いDB操作を行うことができる。 テーブル連携では以下のように記述することができる。 引数を省略すると、自モデルの'連携先テーブル名'_idと連携先テーブルのidが紐づけられる。 第二引数と第三引数を指定することで紐づけるキーを変更することができる。 例では1対1のテーブル連携であるが、メソッドを変えることで1対多や多対多の連携もできる。

Eloquentを利用することで、処理を論理的に記述することができソースコードの見通しが良くなる。

$this.hasOne('連携先テーブル名');

また、ミューテータも非常に便利である。以下のようにgetFooAttributeというメソッドを宣言すると、first_nameへアクセスする際データはucfirstメソッドを通した形で取得することができる。 つまりDBとModelの間に立つmiddlewareのような操作をさせることができる。 今回の例ではあまり実感はないが、nullの値は空白やデフォルト文字にすることができたり 金額は税率によって変わってしまうため、middlewareで税率を積算させることができるなど、様々な箇所で便利に使えるであろう。

public function getFirstNameAttribute($value)
{
    return ucfirst($value);
}

これの逆パターンとして(getではなくset)も行うことができて、これをミューテータを呼ぶ。 例えば全部英子文字の入力を期待するが、大文字が入力された場合もミューテータを通すことにより小文字でDB登録することができる。

public function setFirstNameAttribute($value)
{
    $this->attributes['first_name'] = strtolower($value);
}

更に、Eloquentを使用して終端メソッド(getall)を利用した際はCollectionオブジェクトを取得することができる。 Collectionオブジェクトでは関数型プログラミング(のような手法)でmapやfilter等によりDBの取得結果に対して操作をすることができる。 Collectionオブジェクトを論理により操作することもまたコードの見通しを良くすることができる非常に強力な機能である。

ブラウザバック時にFORM送信された値が保持されない

概要

取り扱っているシステムでブラウザバック時にFORM送信された値が保持されない箇所があったので原因を調査した。

状況

選択肢1と選択肢2が存在し以下の流れでブラウザバックをする。

  1. 選択肢1の値はサーバサイドで作られる
  2. 選択肢1の選択に追従して選択肢2の値をXHR(Ajax等)で取得して変更する(初期表示の際は選択肢1の値を元にXHRでデータ取得をする)
  3. 選択肢2を選択する
  4. ブラウザバック

そうすると選択肢1ではFORMで送信した値が保持されるが、選択肢2では値が保持されない。 気づく人はこの時点でオチがわかるかもしれないが原因は以下。

原因

ブラウザバック時に選択肢1と選択肢2で選択した値が、前画面の選択肢1と選択肢2にセットされる。 選択肢1のデータはサーバサイドで作られるため、view上には選択肢1の収まるデータが存在するためブラウザバックしても値は保持される。 この際、選択肢2のデータリストは選択肢1の値をもとにXHRで取得されるため、選択肢2の初期のデータリストは空っぽである。初期状態の選択肢2のデータリストが空のため、ブラウザバック時に選択肢2が収まる場所がないため、選択肢2の値が消滅してしまう。

対策

  • 選択肢2に入りうる値をサーバサイド側でviewに詰め込む
  • 選択肢2の変更に追従するhiddenタグを用意する。選択肢2の値が変更されたらhiddenの値を利用して初期状態をセットしてあげる

【Python】ImproperlyConfigured: MySQLdb must be installed.がでた

概要

peeweeにおいてconnectやcreate_tableを行う際に、以下のエラーが発生したためメモ。

ImproperlyConfigured: MySQLdb must be installed.

解決法

PythonからMySQLを操作するモジュールをpip経由でinstallすれば解決。

pip3 install PyMySQL

// もしくは

sudo pip3 install PyMySQL

// もしくは

pip install PyMySQL

// もしくは

sudo pip install PyMySQL

// もしくは