Rozbudowujemy DirectiveParser tak, aby można było przekazywać funkcje jako replacement. Uczymy się preg_replace_callback. Do dzieła!

Zacznijmy od klasy Spoofer, która będzie mieć jedną metodę i tym razem zwracać tekst, nie wyświetlać:

class Spoofer {
    public static function method($method){
        return '<input type="hidden" id="_method" name="_method" value="' . $method . '"/>';
    }
}

Teraz wypróbujmy preg_replace_callback:

$txt = "@METHOD('PUT')";
$out = preg_replace_callback(
    "/@METHOD\('*(.*?)'*\)/i",
    function($m)  {
        print_r($m);
        return '';
    },
    $txt);
// Array ( [0] => @METHOD('PUT') [1] => PUT )

Jak widać w $m (od matches) pod 0 mamy całość, zaś pod 1 zawartość naszej capture group. Nic więcej nie potrzebujemy:

$txt = "@METHOD('PUT')";
$out = preg_replace_callback(
    "/@METHOD\('*(.*?)'*\)/i",
    function($m)  {
        return Spoofer::method($m[1]);
    },
    $txt);

echo htmlspecialchars($out);
//<input type="hidden" id="_method" name="_method" value="PUT"/>

Caputre group 1 jest przekazywane do metody method spoofera i zwraca nam replacement. Spróbujmy to ulepszyć:

$action = fn($met) => Spoofer::method($met); 
$txt = "@METHOD('PUT')";
$out = preg_replace_callback(
    "/@METHOD\('*(.*?)'*\)/i",
    function($m) use($action)  {
        return $action($m[1]);
    },
    $txt);

echo htmlspecialchars($out);
//<input type="hidden" id="_method" name="_method" value="PUT"/>

To teraz nic prostszego niż dodać to do naszej klasy:

<?php
class Spoofer {
    public static function method($method){
        return '<input type="hidden" id="_method" name="_method" value="' . $method . '"/>';
    }
}

class DirectiveParser {
    protected static $patterns = [
            [
                "pattern" => "/@METHOD\('*(.*?)'*\)/i",
                "replace" =>  array('Spoofer', 'method')
            ]
            
        ];

    public static function searchAndReplace($source){
        foreach(self::$patterns as $pattern ){
            $action = $pattern['replace'];
            $source = preg_replace_callback(
                $pattern['pattern'],
                function($m) use($action)  {
                    return $action($m[1]);
                },
                $source);
        }
        return $source;
    }
}

$txt = "@METHOD('PUT')";
echo htmlspecialchars(DirectiveParser::searchAndReplace($txt));
//<input type="hidden" id="_method" name="_method" value="PUT"/>

Możemy pozbyć się spoofera i metodę method dołączyć do klasy DirectiveParser:

class DirectiveParser {
    protected static $patterns = [
            [
                "pattern" => "/@METHOD\('*(.*?)'*\)/i",
                "replace" =>  array(__CLASS__, 'method')
            ]
            
        ];

    public static function method($method){
            return '<input type="hidden" id="_method" name="_method" value="' . $method . '"/>';
        }

    public static function searchAndReplace($source){
        foreach(self::$patterns as $pattern ){
            $action = $pattern['replace'];
            $source = preg_replace_callback(
                $pattern['pattern'],
                function($m) use($action)  {
                    return $action($m[1]);
                },
                $source);
        }
        return $source;
    }
}

$txt = "@METHOD('PUT')";
echo htmlspecialchars(DirectiveParser::searchAndReplace($txt));
//<input type="hidden" id="_method" name="_method" value="PUT"/>