<?php

namespace Acms\Services\Logger\Processor;

use Acms\Services\Logger\Filter;
use Monolog\Processor\ProcessorInterface;

/**
 * Monolog レコードの context / extra から機密情報をスクラブするプロセッサ。
 *
 * WebProcessor が extra.url / extra.referrer に htmlspecialchars 前の生の
 * REQUEST_URI / HTTP_REFERER を注入するため、その後段で実行される必要がある。
 * Why not: Monolog\Logger::pushProcessor() は array_unshift() で先頭に積むため、実行順は
 * 「後に push したものが先に走る」順序になる。つまりこのプロセッサを WebProcessor より後に
 * 実行させたい場合、登録（push）は WebProcessor より先に行う必要がある
 * （登録順の具体は LoggerServiceProvider::register() 側の責務）。
 *
 * Monolog\Logger::addRecord() はプロセッサを一度通した同一レコードを全ハンドラへ配布するため、
 * このプロセッサを一度登録するだけで DatabaseHandler / EmailHandler / RotatingFileHandler の
 * いずれにも効く。REQUEST_URL 等の定数を直接参照する箇所（DatabaseHandler の audit_log_url、
 * EmailHandler の url フィールド）はこのプロセッサの対象外なので、各ハンドラ側で
 * Filter::scrubUrl() を個別に呼ぶ必要がある。
 *
 * @phpstan-import-type Record from \Monolog\Logger
 */
class ScrubProcessor implements ProcessorInterface
{
    /**
     * @var Filter
     */
    private $filter;

    /**
     * @param Filter $filter
     */
    public function __construct(Filter $filter)
    {
        $this->filter = $filter;
    }

    /**
     * @param array $record
     * @return array
     *
     * @phpstan-param Record $record
     * @phpstan-return Record
     */
    public function __invoke(array $record): array
    {
        // Why not: Monolog\Logger::addRecord() は 'extra' => [] を必ず初期化し、'context' も
        // addRecord(array $context = []) 経由で常に配列として渡されるため
        // （Record 型シェイプ参照）、isset/is_array のガードは不要。
        $record['extra'] = $this->filter->scrubLogArray($record['extra']);
        $record['context'] = $this->filter->scrubLogArray($record['context']);
        return $record;
    }
}
