Posted at

Phalcon2のLoggerがPSR-3インタフェースを実装してくれない件への対応

More than 3 years have passed since last update.

内容的には Phalcon\LoggerをPSR-3として扱う - Qiita の続編になります。

Phalcon\Logger は Phalcon 1.3 から PSR-3 Logger Interface に対応しているはずでしたが、Phalcon 2 にはこの機能が搭載されていません。

1.3 にあった phalcon.register_psr3_classes ディレクティブが…。

2.0 には存在しない!?

現状 \Psr\Log\LoggerInterface に依存したクラスが動いているサイトで、何も考えずに Phalcon 1.3.4 → Phalcon 2.0.8 に入れ替えてみたところ、当然こんな感じのエラーが発生しました。

'Argument 1 passed to Acme\Domain\Service\AbstractService::setLogger() must be an instance of Psr\Log\LoggerInterface, instance of Phalcon\Logger\Adapter\File given, called in...

Phalcon2がリリースされてから随分と経ちますが、何も問題視されていないということは…。


PSR-3を実装したクラスを別途用意してエラーを回避する

取り急ぎの対策として、PSR-3インタフェースを実装したクラスを作成し、これに代えることにします。

まず、PSR-3インタフェースが定義されたファイルをComposerでインストールします。


composer.json

{

…中略…

"require": {

…中略…

"psr/log": "~1.0"
}
}


これでインストールすると、vendorディレクトリ以下に Psr\Log 名前空間以下のクラスやインタフェース、トレイトの定義されたファイルが配置されます。


Psr\Log\LoggerInterface.php

<?php

namespace Psr\Log;

/**
* Describes a logger instance
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/

interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return null
*/

public function emergency($message, array $context = array());

/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return null
*/

public function alert($message, array $context = array());

/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return null
*/

public function critical($message, array $context = array());

/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return null
*/

public function error($message, array $context = array());

/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return null
*/

public function warning($message, array $context = array());

/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return null
*/

public function notice($message, array $context = array());

/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return null
*/

public function info($message, array $context = array());

/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return null
*/

public function debug($message, array $context = array());

/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/

public function log($level, $message, array $context = array());
}


こいつを実装したクラスを作成すれば良いんですが、PHP 5.4 以上であれば LoggerTrait を利用するのが手っ取り早いです。


Acme\Phalcon\Logger.php

<?php

/**
* Phalcon フレームワーク用ライブラリ
*
* @copyright k-holy <k.holy74@gmail.com>
* @license The MIT License (MIT)
*/

namespace Acme\Phalcon;

/**
* Logger
*
* @author k.holy74@gmail.com
*/

class Logger implements \Psr\Log\LoggerInterface
{

use \Psr\Log\LoggerTrait;

/**
* @var Phalcon\Logger\AdapterInterface
*/

private $adapter;

public function __construct(\Phalcon\Logger\AdapterInterface $adapter)
{
$this->adapter = $adapter;
}

/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/

public function log($level, $message, array $context = [])
{
$this->adapter->log($level, $message, $context);
}

}


基本はこれでOKですが、このままでは Phalcon\Logger\Adapter\File 独自のメソッドを利用できなくなってしまいますので、その対応も入れます。

PHP 5.4 or 5.5 の場合はこうなりますね…。


Acme\Phalcon\Logger.php

<?php

中略

/**
* __call
*
* @param string
* @param array
*/

public function __call($method, $args)
{
if (method_exists($this->adapter, $method)) {
return call_user_func_array([$this->adapter, $method], $args);
}
throw new \BadMethodCallException(
sprintf('Undefined Method "%s" called.', $method)
);
}


PHP 5.6 以上なら Argument Unpacking / 可変長引数リスト の出番です。


Acme\Phalcon\Logger.php

<?php

中略

/**
* __call
*
* @param string
* @param array
*/

public function __call($method, $args)
{
if (method_exists($this->adapter, $method)) {
return $this->adapter->{$method}(...$args);
}
throw new \BadMethodCallException(
sprintf('Undefined Method "%s" called.', $method)
);
}


あとはDIでの生成部分を書き換えます。


変更前

$di->setShared('logger', function() {

return new \Phalcon\Logger\Adapter\File('/path/to/log/file');
});


変更後

$di->setShared('logger', function() {

return new \Acme\Phalcon\Logger(
new \Phalcon\Logger\Adapter\File('/path/to/log/file')
);
});

これでエラーが消えました。こんな時にコード修正が少なくて済むのもDIの恩恵ですね。