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


# 前提の話
こんなモデルがあったとする

```php:Post.php

<?php
class Post extends Eloquent
{
}
```

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

```php
<?php
Post::all();
```

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

## ここで疑問

**Modelに扱うテーブルを明示しなくてもテーブルを読んできてくれるのはナゼ**
→モデル名を複数形にしたテーブルを勝手に読んできてくれるのはナゼ？

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

# Laravelの中身を追っかける

## テーブルの取得処理

### クラス
EloquentはModelクラス使ってる
https://github.com/laravel/framework/blob/5.1/src/Illuminate/Database/Eloquent/Model.php

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


```php:
<?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クラスを見てみると

```php: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というフレームワーク自体の中で使われる
処理のサポートするクラスの１つで
単語の複数形への変換を行っている

#### 実処理

```php
<?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
<?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がよしなにテーブルを読み込んできてくれるのは、「ベタな単語の照合表」や「正規表現」によって実現されていたことが分かった**
確かに、それ以外に方法なんてあるのかという話で、
完全自動化とか考えると、言語処理のもはや研究の域に達するので、
まぁ予想はしてたけど、ベターな結果となった。

もし、オリジナルの単語を作ってしまい（やばそう）、
上手く変換できないときは、
ここが上手く対応できてないと思って良いのではないだろうか。