CodeIgniter4のFactoriesとは?

この記事は CodeIgniter Advent Calendar 2022 - Qiita の13日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

CodeIgniter4の基本的な機能であるFactoriesについての解説記事がなかったので、書くことにします。

Factoriesとは?

CodeIgniter4のFactoriesとは、クラスをロードする仕組みです。 CodeIgniter3の $this->load に似ています。

Factoriesは要求されたクラスのオブジェクトを生成し返します。

また、Factoriesは生成したオブジェクトを共有します。

オブジェクトの取得方法

Factoriesは CodeIgniter\Config\Factories クラスとして実装されています。

メソッド名としてクラスのあるサブ名前空間を指定し、第一引数にロードするクラス名を指定します。

use CodeIgniter\Config\Factories;

$users = Factories::models('UserModel');

上記のコードは、App\Models にある UserModel クラスをインスタンス化して返します。

共有オブジェクトの取得

デフォルトでは、一度インスタンス化されたオブジェクトは共有されます。

同じコードを実行した場合は、全く同じインスタンスが返ります。

$users = Factories::models('UserModel');

config() と model() 関数

関連する関数として、config()model() があります。

それぞれ、\CodeIgniter\Config\Factories::config()\CodeIgniter\Config\Factories::models() のラッパーです。

つまり、設定クラスとモデルクラスをロードします。

Servicesとの違い

Servicesについては、「CodeIgniter4のServicesとは?」を参照してください。

Factoriesはインスタンス化するために具体的なクラス名を必要とし、インスタンスを作成するためのコードを保持できません。

そのため、依存オブジェクトを必要とするようなインスタンスを組み立てることはできません。 しかし、FactoriesはServicesとは異なり、設定なしでクラスをインスタンス化できます。

一方、Servicesにはインスタンスを作成するコードの記述が必要なため、他のサービスまたはオブジェクトを必要とするインスタンスを組み立てることもできます。

また、サービスの取得は、クラス名ではなくサービス名で行われるため、クライアントコードを全く変更せずに、返されるインスタンスを変更できます。

まとめ

  • Factoriesとはクラスをロードする仕組みです。
  • Factoriesは指定されたクラスのインスタンスを生成し、また、共有します。
  • Servicesとは異なり設定なしで使えますが、1つのクラスをインスタンス化して共有するだけで、依存オブジェクトを必要とするようなインスタンスを組み立てることはできません。

この記事は CodeIgniter Advent Calendar 2022 - Qiita の13日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

関連

参考

Tags: codeigniter, codeigniter4

CodeIgniter4のServicesとは?

この記事は CodeIgniter Advent Calendar 2022 - Qiita の12日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

CodeIgniter4の基本的な機能であるServicesについての解説記事がなかったので、書くことにします。

Servicesとは?

CodeIgniter4のServicesとは、フレームワークが提供するサービスロケーターです。 CodeIgniter4が提供するクラスのほとんどは、このServicesから取得できます。

Servicesは要求されたサービスのオブジェクトを生成し返します。

また、ユーザーが自分で作成したクラスをServicesに追加することや、CodeIgniter4のコアクラスを置き換えることもできます。

サービスの取得方法

Servicesは Config\Services クラスとして実装されています。各サービスを取得するメソッドが用意されているので、そのメソッドを呼び出せば、そのサービスのオブジェクトが取得できます。

$options = [
    'baseURI' => 'http://example.com/api/v1/',
    'timeout' => 3,
];
$client = \Config\Services::curlrequest($options);

上記のようにオプションで引数を渡せるサービスもあります。

共有オブジェクトの取得

ほとんどのサービスはデフォルトでは共有オブジェクトを返します。

最初にそのサービスのメソッドを呼んだときにインスタンスが生成され、その後、 何度同じメソッドを呼んでも、全く同じインスタンスが返るということです。

インスタンス化する際に渡される引数は、二度目以降の呼び出しでは無視されることに注意してください。

例えば、

$options1 = [
    'baseURI' => 'http://example.com/api/v1/',
    'timeout' => 5,
];
$client1 = \Config\Services::curlrequest($options1);

$options2 = [
    'baseURI' => 'http://www.example.jp/api/v2/',
    'timeout' => 3,
];
$client2 = \Config\Services::curlrequest($options2);

上のコードでは、$client1$client2 は全く同じインスタンスです。 二度目の呼び出し時には、すでにあるインスタンスを返すだけで新しいインスタンスは生成されないため、引数 $options2 は結果として無視されます。

新しいオブジェクトの取得

共有オブジェクトではなく、別の新しいインスタンスが必要な場合は、メソッドの引数 $getSharedfalse を渡します。

$options = [
    'baseURI' => 'http://example.com/api/v1/',
    'timeout' => 3,
];
$client = \Config\Services::curlrequest($options, null, null, false);

引数 $getShared は全てのサービスで定義されており、 Services::encrypter() 以外のデフォルト値は true です。

なお、実際のサービスは CodeIgniter\Config\Services クラスに定義されています。

Servicesを使う範囲

Servicesは静的メソッドであり、どこからでも呼び出せます。

しかし、Servicesを使うと、呼び出し元のクラスがServicesに依存することになります。 この依存は本来不必要なものです。

ですから、Servicesはコントローラ内でのみ使うことが推奨されています。 それ以外のモデルやライブラリでは、Servicesを使うのではなく、必要なオブジェクトを外部から渡す(注入する)ようにしましょう。

サービスの追加方法

自分で作成したクラスをサービスとして追加できます。

Config\Services にそのオブジェクトを生成して返すメソッドを追加するだけです。

<?php

namespace Config;

use App\Library\Twig;
use CodeIgniter\Config\BaseService;

class Services extends BaseService
{
    // ...

    public static function twig(bool $getShared = true): Twig
    {
        if ($getShared) {
            return static::getSharedInstance('twig');
        }

        return new Twig();
    }
}

これで、\Config\Services::twig() を呼び出すと共有オブジェクトが返り、\Config\Services::twig(false) を呼び出すと新しいオブジェクトが返ります。

コアクラスの置き換え

CodeIgniter4のコアクラスを置き換えたい場合は、 CodeIgniter\Config\Services クラスで定義されているコアクラスのサービスのメソッドを Config\Services にコピーして変更します。

<?php

namespace Config;

use App\Router\MyRouter;
use CodeIgniter\Config\BaseService;
use CodeIgniter\Router\RouteCollection;

class Services extends BaseService
{
    // ...

    public static function routes(bool $getShared = true): RouteCollection
    {
        if ($getShared) {
            return static::getSharedInstance('routes');
        }

        return new MyRouter(static::locator(), config('Modules'));
    }
}

service() 関数

関連する関数として、service()single_service() が提供されています。 single_service() は新しいインスタンスを返すものです。

これらの関数は Config\Services のメソッドへのラッパーです。

しかし、Config\Services のメソッドを直接呼び出せば、静的解析で返り値がわかるのに対し、 これらの関数を使うと返り値はわかりません。

ということでデメリットはあってもメリットがあまりなさそうなので、これらの関数の利用はお薦めしません。

まとめ

  • Servicesは要求されたサービスのオブジェクトを返します。
  • 自分の作成したクラスをServicesに追加することができます。
  • CodeIgniter4のコアクラスを自分の作成したクラスに置き換えることもできます。
  • Servicesはコントローラ内でのみ使いましょう。

この記事は CodeIgniter Advent Calendar 2022 - Qiita の12日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

関連

参考

Tags: codeigniter, codeigniter4

CodeIgniter4のルーティングでのプレイスホルダー (:any) について

この記事は CodeIgniter Advent Calendar 2022 - Qiita の11日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

動作確認環境

  • CodeIgniter 4.3 ブランチ (8e795334c1d3adc5646e2bb726b28fab9376b3c4)

プレイスホルダーとは?

ルーティングでのプレイスホルダーとは、例えば、以下のルーティング定義での (:any) のことです。

$routes->get('product/(:any)', 'Product::find/$1');

プレイスホルダーは正規表現を表す記号です。内部的に正規表現に変換されます。

CodeIgniter4では以下があります。

  • (:any): 複数のURIセグメントにマッチする
  • (:segment): 1つのURIセグメントにマッチする
  • (:num): 1つの数字のみのURIセグメントにマッチする
  • (:alpha): 1つのアルファベットのみのURIセグメントにマッチする
  • (:alphanum): 1つのアルファベットと数字のみのURIセグメントにマッチする
  • (:hash): (:segment) と同じ

ちなみに、CodeIgniter3では (:any)(:num) がありました。

CI3の (:any) はCI4の (:any) ではない

同じ名前なので、CodeIgniter3の (:any) とCodeIgniter4の (:any) が同じだと思っていたのですが、違いました。

CodeIgniter3の (:any) はCodeIgniter4では (:segment) です。

CodeIgniter4の (:any) は新しく追加された機能でした。

CI4の (:any) の動作

例えば、以下のルートを定義します。

$routes->get('product/(:any)', 'Product::find/$1');

ここで、

http://localhost:8080/product/category/filter1/filter2

にアクセスすると、(:any) は複数のURIセグメントにマッチするので、 product/category/filter1/filter2product/(:any) にマッチします。

よって、このルートにマッチするので、Product::find() メソッドが呼び出されます。

では、ここで引数はどうなるでしょうか?

(:any) に対応するURIは category/filter1/filter2 ですので、第1引数に category/filter1/filter2 が渡されるかと思っていたのですが、違いました。

Productコントローラを作成して確認してみましょう。

<?php

namespace App\Controllers;

use App\Controllers\BaseController;

class Product extends BaseController
{
    public function find(...$params)
    {
        dd($params);
    }
}

結果は以下のようになりました。

$params array (3)
    ⇄0 => string (8) "category"
    ⇄1 => string (7) "filter1"
    ⇄2 => string (7) "filter2"
⧉ Called from .../app/Controllers/Product.php:11 [dd()]

つまり、引数はURIセグメントの数だけ渡されます。

結果として、(:any) を使った場合は、引数の数がアクセスされるURIに応じて変わります。

つまり、以下のような定義をすると、(:num) に対応する引数が何番目の引数になるかはわかりません。

$routes->get('product/(:any)/(:num)', 'Product::find/$1/$2');

このような使い方はややこしいだけなので、すべきではありません。

まとめ

  • CodeIgniter4のルーティングでの (:any) は複数のURIセグメントにマッチします。
  • CodeIgniter4の (:any) はCodeIgniter3の (:any) とは動作が異なります。
  • (:any) が複数のURIセグメントにマッチした場合でも、コントローラのメソッドに渡される引数はURIセグメントごとになります。
  • (:any) の後にさらにプレースホルダーを記載することはやめましょう。

この記事は CodeIgniter Advent Calendar 2022 - Qiita の11日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

関連

参考

Tags: codeigniter, codeigniter4