Posted at

何故EloquentModelはクラス名を複数形にしたテーブルを自動で見に行けるのか

More than 1 year has passed since last update.


前提の話

こんなモデルがあったとする


Post.php


<?php
class Post extends Eloquent
{
}

そこで全件取得の処理を書く

<?php

Post::all();

このモデルから呼ばれるテーブルは Posts テーブルとなる


ここで疑問

Modelに扱うテーブルを明示しなくてもテーブルを読んできてくれるのはナゼ

→モデル名を複数形にしたテーブルを勝手に読んできてくれるのはナゼ?

※ここ以降は完全に雑学です。途中を読むのがめんどくさい人は「まとめ」を読んでしまうことをオススメします。


Laravelの中身を追っかける


テーブルの取得処理


クラス

EloquentはModelクラス使ってる

https://github.com/laravel/framework/blob/5.1/src/Illuminate/Database/Eloquent/Model.php


テーブル名取得

その中を追うと、テーブル名の取得にはgetTable()を使ってることが分かる。

<?php

abstract class Model
{

// 省略
/**
* Get the table associated with the model.
*
* @return string
*/

public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}
}

明示的に書かれていれば、そのテーブル名のテーブルを見るが

そうじゃない場合、

str_replace('\\', '', Str::snake(Str::plural(class_basename($this))))

ということをしてる

コレは何だというと

Str::snake()MyPostsという文字列をmy_postsってな感じに変換してくれる

Str::plural()は英単語の複数形を取得する

class_basename()Foo\Bar\Bazっていうクラスの呼ばれ方をしていたらBazだけ返すヘルパー

つまり、クラス名を取得して、その名前の複数形をスネーク表記にして返すというもの

(テーブル名はスネーク表記だから)


参考

Strについて

https://laravel.com/api/5.1/Illuminate/Support/Str.html#method_plural

class_basename()について

https://laravel.com/docs/5.1/helpers#method-class-basename

そして今回注目したいのは

英単語の複数形を取得するStr::plural()


複数形を習得する


Str::plural()は何をしている

Strクラスを見てみると


Str.php

<?php

class Str
{
/**
* Get the plural form of an English word.
*
* @param string $value
* @param int $count
* @return string
*/

public static function plural($value, $count = 2)
{
return Pluralizer::plural($value, $count);
}
}

https://github.com/laravel/framework/blob/5.1/src/Illuminate/Support/Str.php#L209

Pluralizerというのを使っているらしい


Pluralizerとは

Laravelというフレームワーク自体の中で使われる

処理のサポートするクラスの1つで

単語の複数形への変換を行っている


実処理

<?php

class Pluralizer
{
/**
* Get the plural form of an English word.
*
* @param string $value
* @param int $count
* @return string
*/

public static function plural($value, $count = 2)
{
if ((int) $count === 1 || static::uncountable($value)) {
// 単数形を指定していたり、変換が不要の単語の場合はそのまま値を返す
return $value;
}
// 複数形に変換できる場合はInflectorクラスのInflector()を呼び出す
$plural = Inflector::pluralize($value);
                // 大文字、小文字を入力値に揃える
return static::matchCase($plural, $value);
}
}

https://github.com/laravel/framework/blob/5.1/src/Illuminate/Support/Pluralizer.php#L50


Inflectorとは

単語の単数/複数形で文字列操作を行うことができるライブラリ

中枢部分(゚∀゚)キタコレ!!

<?php

class Inflector
{
/**
* Returns a word in plural form.
*
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*/

public static function pluralize($word)
{
if (isset(self::$cache['pluralize'][$word])) {
return self::$cache['pluralize'][$word];
}
if (!isset(self::$plural['merged']['irregular'])) {
self::$plural['merged']['irregular'] = self::$plural['irregular'];
}
if (!isset(self::$plural['merged']['uninflected'])) {
self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
}
if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);

return self::$cache['pluralize'][$word];
}
if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $word;
return $word;
}
foreach (self::$plural['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['pluralize'][$word];
}
}
}
}

なんか、たくさんif文が並んでるな−って感じですが

やってることとしては

不規則な複数形への変換があれば、変数に配列で書かれたマッチングで変換して

複数形と単数形の変化がないものは、そのままにしたり

典型的例に当てはまる語尾のものは、典型例に沿って複数形に変換したり(正規表現)

でした

https://github.com/doctrine/inflector/blob/master/lib/Doctrine/Common/Inflector/Inflector.php#L386


まとめ

Modelがよしなにテーブルを読み込んできてくれるのは、「ベタな単語の照合表」や「正規表現」によって実現されていたことが分かった

確かに、それ以外に方法なんてあるのかという話で、

完全自動化とか考えると、言語処理のもはや研究の域に達するので、

まぁ予想はしてたけど、ベターな結果となった。

もし、オリジナルの単語を作ってしまい(やばそう)、

上手く変換できないときは、

ここが上手く対応できてないと思って良いのではないだろうか。