LaravelのTaskを上書きする

# <i class="fa fa-cubes"></i> 概要

<kbd>php artisan (task)</kbd>
で呼び出されるタスク処理を、IoCを使い、バンドルに作成したTaskに置き換える。
呼び出し側の記述を変えることなく処理を上書きしてカスタマイズできる。

ここでは一番簡単な引数なしの場合の、デフォルトバンドルのTaskを上書きする。

# <i class="fa fa-cube"></i> 前提

Taskは`tasks`ディレクトリ内に`_Task`をつけた、`run`メソッドを持つクラスを作れば自動的に動作していた。


# <i class="fa fa-cube"></i> Taskの仕組みを追う

Task内でバックトレースを表示したり、コアファイルからそれらしきファイルを追ったりする。

## <i class="fa fa-cube"></i> artisan.php

```php:laravel\cli\artisan.php

<?php namespace Laravel\CLI; defined('DS') or die('No direct script access.');

use Laravel\Bundle;
use Laravel\Config;
use Laravel\Request;

/**
 * Fire up the default bundle. This will ensure any dependencies that
 * need to be registered in the IoC container are registered and that
 * the auto-loader mappings are registered.
 */
Bundle::start(DEFAULT_BUNDLE);

/**
 * The default database connection may be set by specifying a value
 * for the "database" CLI option. This allows migrations to be run
 * conveniently for a test or staging database.
 */

if ( ! is_null($database = get_cli_option('db')))
{
	Config::set('database.default', $database);
}

/**
 * We will register all of the Laravel provided tasks inside the IoC
 * container so they can be resolved by the task class. This allows
 * us to seamlessly add tasks to the CLI so that the Task class
 * doesn't have to worry about how to resolve core tasks.
 */
require path('sys').'cli/dependencies'.EXT;

/**
 * We will wrap the command execution in a try / catch block and
 * simply write out any exception messages we receive to the CLI
 * for the developer. Note that this only writes out messages
 * for the CLI exceptions. All others will not be caught
 * and will be totally dumped out to the CLI.
 */
try
{
	Command::run(array_slice($arguments, 1));
}
catch (\Exception $e)
{
	echo $e->getMessage();
}

echo PHP_EOL;

```

`artisan`コマンドの場合、最初はデフォルトバンドルしか起動せず、`Command::run`が実行される。


## <i class="fa fa-cube"></i> command.php

```php:laravel\cli\command.php
	public static function run($arguments = array())
	{
		static::validate($arguments);

		list($bundle, $task, $method) = static::parse($arguments[0]);

		// If the task exists within a bundle, we will start the bundle so that any
		// dependencies can be registered in the application IoC container. If the
		// task is registered in the container,  we'll resolve it.
		if (Bundle::exists($bundle))
		{
			Bundle::start($bundle);
		}

		$task = static::resolve($bundle, $task);

		// Once the bundle has been resolved, we'll make sure we could actually
		// find that task, and then verify that the method exists on the task
		// so we can successfully call it without a problem.
		if (is_null($task))
		{
			throw new \Exception("Sorry, I can't find that task.");
		}

		if (is_callable(array($task, $method)))
		{
			$task->$method(array_slice($arguments, 1));
		}
		else
		{
			throw new \Exception("Sorry, I can't find that method!");
		}
	}
```

ここで、バンドルのタスクならば、そのバンドルのみを有効化している。
より詳細は`$task = static::resolve($bundle, $task);`だろう。

```php

	public static function resolve($bundle, $task)
	{
		$identifier = Bundle::identifier($bundle, $task);

		// First we'll check to see if the task has been registered in the
		// application IoC container. This allows all dependencies to be
		// injected into tasks for more flexible testability.
		if (IoC::registered("task: {$identifier}"))
		{
			return IoC::resolve("task: {$identifier}");
		}

		// If the task file exists, we'll format the bundle and task name
		// into a task class name and resolve an instance of the class so that
		// the requested method may be executed.
		if (file_exists($path = Bundle::path($bundle).'tasks/'.$task.EXT))
		{
			require_once $path;

			$task = static::format($bundle, $task);

			return new $task;
		}
	}

```

ありがたいことにIoCに登録されているかを確認するところがある。
通常、こちらは使われない。
普通のタスクはIoCに登録されるのではなく、タスク実行時にファイルを探して`require`する、意外と親近感がわく作りだった。

とかく、IoCの割り込みポイントがあるので、こちらで好きなクラスを登録しておけば問題ないだろう。

# <i class="fa fa-cube"></i> 上書きTaskを登録しよう

Taskの作り自体は普通なので省略。イチから作るなり継承するなりで、バンドル内に作ろう。極論、tasksディレクトリ内でなくても良い。

さて、普通のモデルクラスのIoC上書きならば、バンドルの`start.php`や`routes.php`に書けばいいが、Taskの場合はこれでは上書きできない。

前述の通り、`artisan`による呼び出しは、`bundles.php`に書かれたバンドルを事前にすべて起動しておくものではなく、`artisan`の形式がバンドル(バンドル:タスク)の場合、そのバンドルだけを明示的に起動するからだ。

したがって、上書き対象のバンドルの起動は、デフォルトバンドルかTaskのバンドル内で行わなければならない。

しかし、cli以外でも常に関係ないバンドルを起動するのは心地が悪い。
Task呼び出しのときのみ、上書きバンドルを上書きされるバンドルで起動したい。
ではどこか。
割り込めるのはやっぱり
`IoC::registered("task: {$identifier}")`


```php:カスタマイズバンドルの起動

IoC::register("task: タスク名", function() {
    Bundle::start('上書きTaskがあるバンドル');
});

```

よーしこれでカスタマイズバンドル内で記述した`IoC::registered("task: {$identifier}")`への`IoC::register`が有効になるぞー…って、そこもう通過しているから、IoC解決中に登録を上書きしても、もうそれは呼び出されることは無い…
しかたがないので、バンドル起動したノリでそのままインスタンスを返す。


```php:カスタマイズTaskをreturn
IoC::register("task: Task名", function() {
    Bundle::start('上書きTaskがあるバンドル');
    return new 上書きTask();
});
```

が、普通、Taskは(手動でしなくても動くので)オートロード設定をしていないと思うの。よってまだクラスを読み込んでおらず、エラーになる。

ここは通常のTask呼び出しを真似て、`require`する。

```php:カスタマイズTaskの読み込み
IoC::register("task: Task名", function() {
    Bundle::start('上書きTaskがあるバンドル');
    /* @see \Laravel\CLI\Command::resolve */
    require_once(Bundle::path('上書きバンドル') .'tasks/' . '上書きTask.php');
    return new 上書きTask();
});
```

このとき、上書きTaskは元のTaskを継承していたが、オートロードされていると思っていたので、継承元ファイルの`require`をしていなかった。
前述の通り、IoCが非登録の場合に、Taskを`require`するので、この時点で継承元のクラスはロードされていない。
クラスファイルに書くか迷ったが、まあ元の解決方法でもしているということで、IoCの中でやってしまった。


```php:継承元Taskの読み込み
IoC::register("task: Task名", function() {
    Bundle::start('上書きTaskがあるバンドル');
    /* @see \Laravel\CLI\Command::resolve */
    require_once(Bundle::path(DEFAULT_BUNDLE) .'tasks/' . 'もともと呼び出されるはずのTask.php'); // 継承のため
    require_once(Bundle::path('上書きバンドル') .'tasks/' . '上書きTask.php');
    return new 上書きTask();
});
```

バンドル内で閉じないことはすっきりしないが、元Taskに手を加えずにすむのでよし。


# <i class="fa fa-book"></i> その他上書き

* タスク : 当記事
* モデル : 普通の`IoC::register`上書き
* ルート : 普通の`Route::get`など上書き
* ビュー : [Laravel3 Viewを上書きする - Qiita]

[Laravel3 Viewを上書きする - Qiita]:http://qiita.com/khsk/items/341bea783b9d40785cbd