PHP code example of padosoft / laravel-pii-redactor

1. Go to this page and download the library: Download padosoft/laravel-pii-redactor library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

padosoft / laravel-pii-redactor example snippets


use Padosoft\PiiRedactor\Facades\Pii;

$clean = Pii::redact('Codice fiscale RSSMRA85T10A562S, IBAN IT60X0542811101000000123456, mail: [email protected].');
// "Codice fiscale [REDACTED], IBAN [REDACTED], mail: [REDACTED]."

$report = Pii::scan('Telefono +39 333 1234567 e P.IVA 12345678903.');
// $report->countsByDetector() === ['phone_it' => 1, 'p_iva' => 1]

   $set = (new YamlCustomRuleLoader())->load(storage_path('app/pii-rules/it-albo.yaml'));
   Pii::extend('custom_it_albo', new CustomRuleDetector('custom_it_albo', $set));
   

// config/pii-redactor.php
'packs' => [
    \Padosoft\PiiRedactor\Packs\Italy\ItalyPack::class,
    // \Padosoft\PiiRedactor\Packs\Germany\GermanyPack::class, // shipped v1.1 — opt-in
    // \Padosoft\PiiRedactor\Packs\Spain\SpainPack::class,     // shipped v1.1 — opt-in
],

'packs' => [
    // ItalyPack removed — codice fiscale / P.IVA / Italian phone / Italian address detectors NOT registered
],

// src/Packs/Iceland/Detectors/KennitalaDetector.php
namespace Padosoft\PiiRedactor\Packs\Iceland\Detectors;

use Padosoft\PiiRedactor\Detectors\Detection;
use Padosoft\PiiRedactor\Detectors\Detector;

final class KennitalaDetector implements Detector
{
    public function name(): string
    {
        return 'kennitala';
    }

    public function detect(string $text): array
    {
        // 10 digits with mod-11 checksum on the first 9.
        if (preg_match_all('/\b(\d{6}-?\d{4})\b/u', $text, $matches, PREG_OFFSET_CAPTURE) === false) {
            return [];
        }
        $hits = [];
        foreach ($matches[1] as $m) {
            $value = preg_replace('/-/', '', (string) $m[0]);
            if (! $this->validChecksum($value)) {
                continue;
            }
            $hits[] = new Detection('kennitala', (string) $m[0], (int) $m[1], strlen((string) $m[0]));
        }
        return $hits;
    }

    private function validChecksum(string $kt): bool
    {
        // Weights: 3, 2, 7, 6, 5, 4, 3, 2 over the first 8 digits;
        // ninth digit is the check digit; mod-11 with 11 - r mapping.
        // ... real implementation here ...
        return true;
    }
}

// src/Packs/Iceland/IcelandPack.php
namespace Padosoft\PiiRedactor\Packs\Iceland;

use Padosoft\PiiRedactor\Packs\PackContract;
use Padosoft\PiiRedactor\Packs\Iceland\Detectors\KennitalaDetector;

final class IcelandPack implements PackContract
{
    public function name(): string        { return 'iceland'; }
    public function countryCode(): string { return 'IS'; }
    public function description(): string { return 'Icelandic kennitala (mod-11) + (future) phone / address detectors.'; }

    public function detectors(): array
    {
        return [
            new KennitalaDetector(),
        ];
    }
}

// config/pii-redactor.php
'packs' => [
    \Padosoft\PiiRedactor\Packs\Italy\ItalyPack::class,
    \Padosoft\PiiRedactor\Packs\Iceland\IcelandPack::class,  // your new pack
],

use Padosoft\PiiRedactor\Facades\Pii;

// Default mask strategy.
$clean = Pii::redact('Codice fiscale RSSMRA85T10A562S e P.IVA 12345678903.');
// "Codice fiscale [REDACTED] e P.IVA [REDACTED]."

// Audit a payload before redacting.
$report = Pii::scan('Email [email protected] IBAN IT60X0542811101000000123456.');
$report->countsByDetector(); // ['email' => 1, 'iban' => 1]

// One-off strategy override (without changing config).
use Padosoft\PiiRedactor\Strategies\HashStrategy;
$hashed = Pii::redact('[email protected]', new HashStrategy(salt: env('PII_REDACTOR_SALT')));
// "[hash:f72a1b09abc12345]"  (16 hex chars — 64-bit namespace)

use Padosoft\PiiRedactor\Strategies\TokeniseStrategy;

$strategy = new TokeniseStrategy(salt: env('PII_REDACTOR_SALT'));

// Tokenise — same input always produces the same token under a fixed salt.
$redacted = Pii::redact($chatLog, $strategy);

// ... ship $redacted to a downstream system that does NOT need the originals ...

// Later, on the secure side, rehydrate when an auditor requests it.
$auditPayload = $strategy->detokeniseString($redacted);

use Padosoft\PiiRedactor\Detectors\Detection;
use Padosoft\PiiRedactor\Detectors\Detector;
use Padosoft\PiiRedactor\Facades\Pii;

class CodiceIscrizioneAlboDetector implements Detector
{
    public function name(): string { return 'custom_albo'; }

    public function detect(string $text): array
    {
        if (preg_match_all('/ISCR-\d{6,}/', $text, $matches, PREG_OFFSET_CAPTURE) === false) {
            return [];
        }
        $hits = [];
        foreach ($matches[0] as $m) {
            $hits[] = new Detection('custom_albo', (string) $m[0], (int) $m[1], strlen((string) $m[0]));
        }
        return $hits;
    }
}

Pii::extend('custom_albo', new CodiceIscrizioneAlboDetector);



declare(strict_types=1);

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Padosoft\PiiRedactor\RedactorEngine;
use Symfony\Component\HttpFoundation\Response;

final class RedactChatPii
{
    public function __construct(
        private readonly RedactorEngine $engine,
    ) {}

    public function handle(Request $request, Closure $next): Response
    {
        // Gate 1 — the package's OWN master switch
        // (`PII_REDACTOR_ENABLED` env / `pii-redactor.enabled` config).
        // When the package is disabled at the env level, every call
        // path skips redaction.
        if (! (bool) config('pii-redactor.enabled', false)) {
            return $next($request);
        }

        // Gate 2 — your host-app integration knob.
        // Substitute `myapp.pii.middleware_active` with the config key
        // your project actually uses.
        if (! (bool) config('myapp.pii.middleware_active', false)) {
            return $next($request);
        }

        $content = $request->input('content');
        if (! is_string($content) || $content === '') {
            return $next($request);
        }

        $request->merge([
            'content' => $this->engine->redact($content),
        ]);

        return $next($request);
    }
}

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'redact-chat-pii' => \App\Http\Middleware\RedactChatPii::class,
    ]);
})

// routes/web.php or routes/api.php
Route::post('/chat/messages', [ChatController::class, 'store'])
    ->middleware('redact-chat-pii');

Route::post('/chat/messages/stream', [ChatStreamController::class, 'store'])
    ->middleware(['auth.sse', 'redact-chat-pii']);

// tests/Architecture/PiiMiddlewareScopeTest.php
final class PiiMiddlewareScopeTest extends TestCase
{
    /**
     * Substrings — NOT prefixes. A bare `admin` URI does not start
     * with `admin/`, so a prefix match would let the binding reach
     * the root admin endpoints unnoticed.
     */
    private const FORBIDDEN_SUBSTRINGS = ['admin', 'ingest'];

    public function test_redact_chat_pii_is_not_bound_to_admin_or_ingest_routes(): void
    {
        $router = $this->app->make(\Illuminate\Routing\Router::class);
        foreach ($router->getRoutes() as $route) {
            $bag = array_merge((array) $route->middleware(), (array) $route->gatherMiddleware());
            if (! in_array('redact-chat-pii', $bag, true) && ! in_array(\App\Http\Middleware\RedactChatPii::class, $bag, true)) {
                continue;
            }
            foreach (self::FORBIDDEN_SUBSTRINGS as $forbidden) {
                $this->assertStringNotContainsString($forbidden, $route->uri());
            }
        }
    }
}

use Padosoft\PiiRedactor\RedactorEngine;
use Padosoft\PiiRedactor\Strategies\MaskStrategy;

final class EmbeddingCacheService
{
    public function __construct(
        private readonly EmbeddingProvider $provider,
        private readonly RedactorEngine $engine,
    ) {}

    /** @param  list<string>  $texts */
    public function generate(array $texts): EmbeddingsResponse
    {
        if (config('pii-redactor.enabled') && config('myapp.pii.redact_before_embeddings')) {
            // Construct mask explicitly from the package's mask_token
            // so the host's `PII_REDACTOR_MASK_TOKEN` override is honoured.
            // Autowiring `app(MaskStrategy::class)` would create a fresh
            // instance with the hard-coded `[REDACTED]` default and skip
            // the configured token entirely.
            $mask = new MaskStrategy(
                (string) config('pii-redactor.mask_token', '[REDACTED]'),
            );

            $texts = array_map(
                fn (string $t): string => $this->engine->redact($t, $mask),
                $texts,
            );
        }

        // Hash for cache key + send to provider — both now see the masked text.
        // ...
    }
}

final class IngestExternalChatJob implements ShouldQueue
{
    public function handle(RedactorEngine $engine): void
    {
        $body = $this->payload['message'] ?? null;
        if (! is_string($body) || $body === '') {
            ChatLog::create(['body' => $body, /* ... */]);
            return;
        }

        if (config('pii-redactor.enabled') && config('myapp.pii.redact_jobs')) {
            $body = $engine->redact($body);
        }
        ChatLog::create(['body' => $body, /* ... */]);
    }
}
bash
php artisan vendor:publish --tag=pii-redactor-config