PHPackages                             andydefer/laravel-jsonl - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. andydefer/laravel-jsonl

ActiveLibrary[File &amp; Storage](/categories/file-storage)

andydefer/laravel-jsonl
=======================

JSONL storage service for Laravel - write, read, query, and clean JSON Lines files with custom path strategies

0.3.0(today)03↑2900%MITPHPPHP ^8.2

Since Jun 13Pushed todayCompare

[ Source](https://github.com/andydefer/laravel-jsonl)[ Packagist](https://packagist.org/packages/andydefer/laravel-jsonl)[ RSS](/packages/andydefer-laravel-jsonl/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (6)Versions (4)Used By (0)

Laravel JSONL - Documentation complète
======================================

[](#laravel-jsonl---documentation-complète)

[![PHP Version](https://camo.githubusercontent.com/7535257ca228724c93658bd52583d4e47a9bab02c356abf6e54c1d575f2151e6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d626c75652e737667)](https://php.net)[![License](https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

Table des matières
------------------

[](#table-des-matières)

1. [Introduction](#introduction)
2. [Installation](#installation)
3. [Architecture et concepts](#architecture-et-concepts)
4. [Les stratégies de chemin](#les-strat%C3%A9gies-de-chemin)
5. [Utilisation de base](#utilisation-de-base)
6. [Opérations avancées](#op%C3%A9rations-avanc%C3%A9es)
7. [Buffer d'écriture](#buffer-d%C3%A9criture)
8. [Verrouillage concurrentiel](#verrouillage-concurrentiel)
9. [Nettoyage des données](#nettoyage-des-donn%C3%A9es)
10. [Configuration](#configuration)
11. [Bonnes pratiques](#bonnes-pratiques)
12. [Exemples complets](#exemples-complets)
13. [API Reference](#api-reference)
14. [Dépannage](#d%C3%A9pannage)

---

Introduction
------------

[](#introduction)

**Laravel JSONL** est un package de stockage de données au format [JSON Lines](https://jsonlines.org/) (JSONL) pour PHP 8.1+. Chaque ligne est un JSON valide, ce qui le rend idéal pour :

- **Logs structurés** : Journalisation d'événements avec recherche par date/heure
- **Cache persistant** : Stockage de données avec expiration
- **Métriques** : Collecte de données temps réel
- **Audit trails** : Traçabilité immuable des actions

### Pourquoi JSONL plutôt que JSON classique ?

[](#pourquoi-jsonl-plutôt-que-json-classique-)

CritèreJSON classiqueJSONLAjout de donnéesDoit réécrire tout le fichierSimple appendStreamingImpossible (doit tout charger)Possible ligne par ligneRechercheDoit parser tout le fichierStreaming + filtrageCorruptionFichier complet inutilisableUne ligne corrompue n'affecte pas les autresParallélismeDifficile (lock sur tout le fichier)Possible par ligne---

Installation
------------

[](#installation)

```
composer require andydefer/laravel-jsonl
```

**Prérequis :**

- PHP 8.1 ou supérieur
- Composer
- (Optionnel) Laravel 10, 11 ou 12 pour l'intégration automatique

### Sans Laravel (PHP pur)

[](#sans-laravel-php-pur)

```
use AndyDefer\LaravelJsonl\JsonlService;
use AndyDefer\LaravelJsonl\Contexts\JsonlContext;
use AndyDefer\LaravelJsonl\Strategies\TemporalPathStrategy;
use AndyDefer\PhpServices\Services\FileSystemService;

$strategy = new TemporalPathStrategy('/var/logs');
$fileSystem = new FileSystemService();
$context = new JsonlContext();
$service = new JsonlService($strategy, $fileSystem, $context);
```

### Avec Laravel

[](#avec-laravel)

Le package s'enregistre automatiquement via le Service Provider. Publiez la configuration :

```
php artisan vendor:publish --tag=jsonl-config
```

```
# .env
JSONL_BASE_PATH=storage/logs/structured
JSONL_BUFFER_SIZE=100
JSONL_DIRECTORY_PERMISSION=755
```

---

Architecture et concepts
------------------------

[](#architecture-et-concepts)

### Le pattern Stateless Service

[](#le-pattern-stateless-service)

Le `JsonlService` est conçu comme un service **stateless**. Tout l'état (verrous actifs, buffer d'écriture) est déporté dans un `JsonlContext` injecté. Cette architecture offre plusieurs avantages :

- **Testabilité** : On peut isoler et tester chaque composant indépendamment
- **Prévisibilité** : Pas d'effets de bord cachés entre appels
- **Concurrence** : Chaque contexte peut être isolé par thread/requête

```
┌─────────────────────────────────────────────────────────────────┐
│                        JsonlService                              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ - pathStrategy: JsonlPathStrategyInterface              │   │
│  │ - fileSystem: FileSystemInterface                       │   │
│  │ - context: JsonlContext ◄─── État externalisé           │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                         JsonlContext                            │
│  ┌─────────────────────┐  ┌─────────────────────────────────┐  │
│  │ Lock State          │  │ Buffer State                    │  │
│  │ - locks: array      │  │ - buffer: array                 │  │
│  │                     │  │ - bufferSize: int               │  │
│  │                     │  │ - onFlushCallback: callable     │  │
│  └─────────────────────┘  └─────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

```

### Records

[](#records)

Les **Records** sont des objets immutables qui représentent les données à stocker. Ils ne contiennent **aucune logique métier** - uniquement des propriétés.

#### LogJsonlRecord - Pour la journalisation

[](#logjsonlrecord---pour-la-journalisation)

```
use AndyDefer\LaravelJsonl\Records\LogJsonlRecord;
use AndyDefer\PhpVo\ValueObjects\DateTimeVO;
use AndyDefer\DomainStructures\Structures\StrictDataObject;

$log = new LogJsonlRecord(
    time: new DateTimeVO(),                    // Date/heure de l'événement
    level: 'info',                             // Niveau (info, warning, error, debug)
    type: 'user_login',                        // Type d'événement
    payload: new StrictDataObject([            // Données métier
        'user_id' => 123,
        'ip' => '192.168.1.100',
    ]),
);
```

#### CacheJsonlRecord - Pour le cache

[](#cachejsonlrecord---pour-le-cache)

```
use AndyDefer\LaravelJsonl\Records\CacheJsonlRecord;

$cache = new CacheJsonlRecord(
    key: 'user_profile_123',                  // Clé unique
    value: json_encode(['name' => 'John']),   // Valeur (JSON string)
    expires_at: new DateTimeVO('+1 hour'),    // Expiration (optionnel)
);
```

### Stratégies de chemin

[](#stratégies-de-chemin)

Les **stratégies** déterminent **où** les fichiers sont stockés. Le service ne sait pas où ranger les données - il délègue cette décision à la stratégie.

```
// Le service demande à la stratégie : "Où dois-je mettre ce log ?"
$filePath = $strategy->getFilePath($logRecord);
```

---

Les stratégies de chemin
------------------------

[](#les-stratégies-de-chemin)

### Pourquoi des stratégies ?

[](#pourquoi-des-stratégies-)

L'organisation des fichiers dépend du cas d'usage :

BesoinOrganisation idéaleLogsPar date/heure (recherche temporelle)CachePar hash de clé (accès direct O(1))Le package fournit deux stratégies prêtes à l'emploi, et vous pouvez créer les vôtres.

### TemporalPathStrategy (pour les logs)

[](#temporalpathstrategy-pour-les-logs)

**Structure :** `{basePath}/{YYYY-MM-DD}/{HH}.jsonl`

```
storage/logs/
├── 2026-01-15/
│   ├── 00.jsonl   (00h00 - 00h59)
│   ├── 01.jsonl   (01h00 - 01h59)
│   └── 14.jsonl   (14h00 - 14h59)
├── 2026-01-16/
│   └── ...

```

**Avantages :**

- Recherche par intervalle de temps rapide
- Nettoyage facile par date (suppression de dossiers entiers)
- Streaming par jour/heure
- Pas besoin d'index, la structure EST l'index

**Exemple :**

```
$strategy = new TemporalPathStrategy('/var/logs');

$log = new LogJsonlRecord(
    time: new DateTimeVO('2026-01-15T14:35:00Z'),
    // ...
);

$path = $strategy->getFilePath($log);
// Résultat: /var/logs/2026-01-15/14.jsonl
```

### KeyBasedPathStrategy (pour le cache)

[](#keybasedpathstrategy-pour-le-cache)

**Structure :** `{basePath}/{hash[0]}/{hash[1]}/{sanitized_key}.jsonl`

```
storage/cache/
├── a/
│   ├── b/
│   │   └── user_123.jsonl
│   └── c/
│       └── session_abc.jsonl
├── f/
│   └── 3/
│       └── product_456.jsonl

```

**Comment ça marche :**

1. `md5('user_123')` = `e10adc3949ba59abbe56e057f20f883e`
2. Niveaux de hash : `e` → `1`
3. Chemin final : `/cache/e/1/user_123.jsonl`

**Avantages :**

- Accès direct O(1) - on sait exactement quel fichier lire
- Répartition équilibrée (le hash disperse naturellement)
- Pas de scanning inutile
- Suppression rapide par clé

**Exemple :**

```
$strategy = new KeyBasedPathStrategy('/cache', hashLevels: 2);

$cache = new CacheJsonlRecord(key: 'user_123', ...);

$path = $strategy->getFilePath($cache);
// Résultat: /cache/e/1/user_123.jsonl
```

### Créer sa propre stratégie

[](#créer-sa-propre-stratégie)

Implémentez `JsonlPathStrategyInterface` :

```
use AndyDefer\LaravelJsonl\Contracts\JsonlPathStrategyInterface;

/**
 * Organisation par tenant (multi-entreprise)
 * Structure: /{tenant_id}/{YYYY-MM-DD}/{HH}.jsonl
 */
class TenantBasedPathStrategy implements JsonlPathStrategyInterface
{
    public function __construct(
        private string $basePath,
        private string $tenantId,
    ) {}

    public function getFilePath(AbstractRecord $entity): string
    {
        if (!$entity instanceof LogJsonlRecord) {
            throw new InvalidArgumentException('Expected LogJsonlRecord');
        }

        $date = $entity->time->format('Y-m-d');
        $hour = $entity->time->format('H');

        return implode(DIRECTORY_SEPARATOR, [
            rtrim($this->basePath, DIRECTORY_SEPARATOR),
            $this->tenantId,
            $date,
            $hour . '.jsonl',
        ]);
    }

    public function getFilesToScan(AbstractRecord $query): array
    {
        // Logique pour scanner tous les fichiers du tenant sur la plage demandée
        // ...
    }

    public function getBaseDirectory(): string
    {
        return $this->basePath;
    }
}
```

---

Utilisation de base
-------------------

[](#utilisation-de-base)

### Écrire un log

[](#écrire-un-log)

```
use AndyDefer\LaravelJsonl\JsonlService;
use AndyDefer\LaravelJsonl\Contexts\JsonlContext;
use AndyDefer\LaravelJsonl\Records\LogJsonlRecord;
use AndyDefer\LaravelJsonl\Strategies\TemporalPathStrategy;
use AndyDefer\PhpServices\Services\FileSystemService;
use AndyDefer\PhpVo\ValueObjects\DateTimeVO;

$strategy = new TemporalPathStrategy(storage_path('logs/structured'));
$fs = new FileSystemService();
$context = new JsonlContext();
$service = new JsonlService($strategy, $fs, $context);

$log = new LogJsonlRecord(
    time: new DateTimeVO(),
    level: 'info',
    type: 'user_login',
    payload: new StrictDataObject([
        'user_id' => 123,
        'username' => 'john_doe',
        'ip' => '192.168.1.100',
    ]),
);

$service->write($log);
```

**Résultat dans le fichier :**

```
{"time":"2026-01-15T14:35:00+00:00","level":"info","type":"user_login","payload":{"user_id":123,"username":"john_doe","ip":"192.168.1.100"}}
```

### Écrire une entrée de cache

[](#écrire-une-entrée-de-cache)

```
use AndyDefer\LaravelJsonl\Records\CacheJsonlRecord;
use AndyDefer\LaravelJsonl\Strategies\KeyBasedPathStrategy;

$strategy = new KeyBasedPathStrategy(storage_path('cache'), 2);
$service = new JsonlService($strategy, $fs, $context);

$cache = new CacheJsonlRecord(
    key: 'user_123',
    value: json_encode(['name' => 'John', 'email' => 'john@example.com']),
    expires_at: new DateTimeVO('+1 hour'),
);

$service->write($cache);
```

### Lire un fichier

[](#lire-un-fichier)

```
// Lire toutes les lignes
$lines = $service->readAll('/var/logs/2026-01-15/14.jsonl');
foreach ($lines as $line) {
    echo $line['type'] . "\n";
}

// Lire ligne par ligne (streaming - économique en mémoire)
$service->readLineByLine('/var/logs/2026-01-15/14.jsonl', function ($line) {
    echo $line['time'] . ': ' . $line['level'] . "\n";
});

// Lire uniquement la première ou dernière ligne
$first = $service->getFirstLine('/var/logs/2026-01-15/14.jsonl');
$last = $service->getLastLine('/var/logs/2026-01-15/14.jsonl');
```

### Rechercher dans un fichier

[](#rechercher-dans-un-fichier)

```
// Trouver toutes les erreurs
$errors = $service->search('/var/logs/2026-01-15/14.jsonl', function ($line) {
    return $line['level'] === 'error';
});

// Trouver les logs d'un utilisateur spécifique
$userLogs = $service->search('/var/logs/2026-01-15/14.jsonl', function ($line) {
    return isset($line['payload']['user_id']) && $line['payload']['user_id'] === 123;
});
```

---

Opérations avancées
-------------------

[](#opérations-avancées)

### Écriture par lots (batch)

[](#écriture-par-lots-batch)

Plus efficace que des écritures individuelles pour de gros volumes :

```
$logs = [
    new LogJsonlRecord(/* ... */),
    new LogJsonlRecord(/* ... */),
    // ...
];

$service->writeBatch($logs); // Une seule opération I/O
```

### Écriture avec verrouillage

[](#écriture-avec-verrouillage)

Par défaut, `write()` et `writeBatch()` utilisent un verrouillage exclusif (`flock`). Désactivez-le pour les environnements mono-processus :

```
// Pas de verrou (plus rapide mais pas sûr en concurrence)
$service->write($record, lock: false);
```

### Recherche sur plusieurs fichiers

[](#recherche-sur-plusieurs-fichiers)

```
$files = [
    '/var/logs/2026-01-15/14.jsonl',
    '/var/logs/2026-01-15/15.jsonl',
];

$errors = $service->searchMultiple($files, function ($line) {
    return $line['level'] === 'error';
});
```

### Utilisation du contexte de traitement

[](#utilisation-du-contexte-de-traitement)

Le `JsonlProcessingContext` suit l'état d'une opération :

```
$context = new JsonlProcessingContext();

try {
    $service->write($log, context: $context);

    if ($context->isCompleted()) {
        echo "Files processed: " . $context->getProcessedFiles()->count();
        echo "Lines written: " . $context->getTotalLinesProcessed();
        echo "Duration: " . $context->getDuration() . " seconds";
    }
} catch (JsonlException $e) {
    echo "Failed: " . $context->getLastError();
}
```

---

Buffer d'écriture
-----------------

[](#buffer-décriture)

Le buffer accumule les entités en mémoire avant de les écrire sur le disque, réduisant ainsi les opérations I/O.

### Activer le buffer

[](#activer-le-buffer)

```
// Buffer de 100 lignes avant écriture automatique
$service->enableBuffer(100);
```

### Utilisation

[](#utilisation)

```
$service->enableBuffer(50);

for ($i = 0; $i < 100; $i++) {
    $service->writeBuffered($log);  // Stocké en mémoire

    // Toutes les 50 écritures, flush automatique
}

$service->flushBuffer();  // Flush manuel des derniers
```

### Callback de flush

[](#callback-de-flush)

```
$service->enableBuffer(100);
$service->onFlush(function ($filePath, $count) {
    echo "Flushed {$count} lines to {$filePath}\n";
});
```

### Désactiver le buffer

[](#désactiver-le-buffer)

```
$service->disableBuffer();  // Flush puis désactive
```

---

Verrouillage concurrentiel
--------------------------

[](#verrouillage-concurrentiel)

Le package utilise `flock` pour garantir l'intégrité des données en environnement concurrent.

### Verrouillage automatique

[](#verrouillage-automatique)

Les méthodes `write()` et `writeBatch()` utilisent un verrou par défaut :

```
// Verrou automatique (par défaut)
$service->write($record);  // lock = true

// Sans verrou (dangereux en concurrence)
$service->write($record, lock: false);
```

### Verrouillage manuel

[](#verrouillage-manuel)

```
// Acquérir un verrou
if ($service->acquire('/var/logs/app.jsonl', timeout: 5)) {
    try {
        // Opérations exclusives
        $this->fileSystem->append('/var/logs/app.jsonl', $data);
    } finally {
        $service->release('/var/logs/app.jsonl');
    }
}
```

### Exécution atomique

[](#exécution-atomique)

```
$result = $service->executeWithLock('/var/logs/app.jsonl', function () use ($service) {
    $content = $service->readAll('/var/logs/app.jsonl');
    $content[] = ['time' => date('c'), 'event' => 'processed'];
    return count($content);
});
```

---

Nettoyage des données
---------------------

[](#nettoyage-des-données)

### Nettoyage par âge (fichiers entiers)

[](#nettoyage-par-âge-fichiers-entiers)

```
// Supprimer les fichiers de plus de 30 jours
$deleted = $service->cleanOlderThan(30, '/var/logs');
echo "Deleted {$deleted} old log files";
```

### Nettoyage par pattern

[](#nettoyage-par-pattern)

```
// Supprimer tous les fichiers du 15 janvier
$pattern = '/var/logs/2026-01-15/*.jsonl';
$deleted = $service->cleanByPattern($pattern);
```

### Nettoyage des entrées expirées (cache)

[](#nettoyage-des-entrées-expirées-cache)

```
$deleted = $service->cleanExpired('/cache', function ($line) {
    if (!isset($line['expires_at'])) {
        return false;
    }
    $expiresAt = new DateTimeVO($line['expires_at']);
    return $expiresAt->isBefore(new DateTimeVO());
});
```

### Dry run - Simulation avant suppression

[](#dry-run---simulation-avant-suppression)

```
// Voir ce qui serait supprimé sans rien effacer
$filesToDelete = $service->dryRun('/var/logs', function ($file) {
    return filemtime($file) < strtotime('-30 days');
});

foreach ($filesToDelete as $file) {
    echo "Would delete: {$file}\n";
}

if (count($filesToDelete) > 0) {
    $confirm = readline("Proceed with deletion? (y/n): ");
    if ($confirm === 'y') {
        $service->cleanOlderThan(30, '/var/logs');
    }
}
```

### Vider complètement un répertoire

[](#vider-complètement-un-répertoire)

```
$deleted = $service->clear('/var/app/cache');
echo "Cleared {$deleted} cache files";
```

---

Configuration
-------------

[](#configuration)

### Variables d'environnement

[](#variables-denvironnement)

```
# Chemin de base pour les fichiers JSONL
JSONL_BASE_PATH=/custom/log/path

# Taille du buffer (null = désactivé)
JSONL_BUFFER_SIZE=100

# Permissions des dossiers (755, 775, 750, 700, etc.)
JSONL_DIRECTORY_PERMISSION=755
```

### Fichier de configuration Laravel

[](#fichier-de-configuration-laravel)

```
// config/jsonl.php
return [
    'base_path' => env('JSONL_BASE_PATH', storage_path('jsonl')),
    'buffer_size' => env('JSONL_BUFFER_SIZE', null),
    'directory_permission' => (int) env('JSONL_DIRECTORY_PERMISSION', 755),
];
```

---

Bonnes pratiques
----------------

[](#bonnes-pratiques)

### 1. Utilisez la stratégie adaptée à votre besoin

[](#1-utilisez-la-stratégie-adaptée-à-votre-besoin)

Cas d'usageStratégie recommandéeLogs, événements, audits`TemporalPathStrategy`Cache, sessions`KeyBasedPathStrategy`### 2. Activez le buffer pour les écritures fréquentes

[](#2-activez-le-buffer-pour-les-écritures-fréquentes)

```
$service->enableBuffer(100);
// Écritures...
$service->flushBuffer();
```

### 3. Utilisez les queries typées

[](#3-utilisez-les-queries-typées)

```
// Pour les logs
$query = new TemporalLogQueryRecord(
    from: new DateTimeVO('2026-01-15T00:00:00Z'),
    to: new DateTimeVO('2026-01-15T23:59:59Z'),
    type: 'user_login',      // Optionnel
    level: 'info',           // Optionnel
);

// Pour le cache
$query = new CacheKeyQueryRecord(key: 'user_123');
```

### 4. Structurez vos payloads

[](#4-structurez-vos-payloads)

```
// ✅ BON - Données structurées
$payload = new StrictDataObject([
    'event' => 'user_login',
    'user_id' => 123,
    'ip' => '192.168.1.100',
]);

// ❌ MAUVAIS - Texte non structuré
$payload = new StrictDataObject(['message' => 'User 123 logged in from 192.168.1.100']);
```

### 5. Gérez les exceptions

[](#5-gérez-les-exceptions)

```
try {
    $service->write($record);
} catch (JsonlException $e) {
    // Problème d'écriture
    Log::error('Failed to write JSONL: ' . $e->getMessage());
} catch (JsonlLockException $e) {
    // Timeout de verrou
    Log::warning('Could not acquire lock: ' . $e->getMessage());
}
```

### 6. Nettoyez régulièrement

[](#6-nettoyez-régulièrement)

```
// Cron daily
$service->cleanOlderThan(30, '/var/logs');
$service->cleanExpired('/cache', $isExpiredCallback);
```

---

Exemples complets
-----------------

[](#exemples-complets)

### Application de logging complète

[](#application-de-logging-complète)

```
class UserActivityLogger
{
    private JsonlService $service;

    public function __construct()
    {
        $strategy = new TemporalPathStrategy(storage_path('logs/activities'));
        $fs = new FileSystemService();
        $context = new JsonlContext();
        $this->service = new JsonlService($strategy, $fs, $context);
        $this->service->enableBuffer(50);
    }

    public function logLogin(int $userId, string $ip, bool $success): void
    {
        $log = new LogJsonlRecord(
            time: new DateTimeVO(),
            level: $success ? 'info' : 'warning',
            type: 'user_login',
            payload: new StrictDataObject([
                'user_id' => $userId,
                'ip' => $ip,
                'success' => $success,
                'timestamp' => time(),
            ]),
        );

        $this->service->writeBuffered($log);
    }

    public function getFailedLogins(DateTimeVO $date): array
    {
        $query = new TemporalLogQueryRecord(
            from: $date,
            to: $date,
            type: 'user_login',
            level: 'warning',
        );

        $files = $this->service->getFilesToScan($query);
        $failed = [];

        foreach ($files as $file) {
            $lines = $this->service->search($file, function ($line) {
                return $line['payload']['success'] === false;
            });
            $failed = array_merge($failed, $lines);
        }

        return $failed;
    }

    public function __destruct()
    {
        $this->service->flushBuffer();
    }
}
```

### Service de cache persistant

[](#service-de-cache-persistant)

```
class PersistentCache
{
    private JsonlService $service;
    private int $ttl;

    public function __construct(int $defaultTtlSeconds = 3600)
    {
        $strategy = new KeyBasedPathStrategy(storage_path('cache/persistent'), 2);
        $fs = new FileSystemService();
        $context = new JsonlContext();
        $this->service = new JsonlService($strategy, $fs, $context);
        $this->ttl = $defaultTtlSeconds;
    }

    public function set(string $key, mixed $value, ?int $ttl = null): void
    {
        $expiresAt = $ttl !== null
            ? new DateTimeVO("+{$ttl} seconds")
            : ($this->ttl ? new DateTimeVO("+{$this->ttl} seconds") : null);

        $cache = new CacheJsonlRecord(
            key: $key,
            value: json_encode($value),
            expires_at: $expiresAt,
        );

        $this->service->write($cache);
    }

    public function get(string $key): mixed
    {
        $query = new CacheKeyQueryRecord(key: $key);
        $files = $this->service->getFilesToScan($query);

        if (empty($files) || !$this->service->fileExists($files[0])) {
            return null;
        }

        $data = $this->service->readAll($files[0]);
        if (empty($data)) {
            return null;
        }

        $record = CacheJsonlRecord::fromArray($data[0]);

        if ($this->service->isExpired($record)) {
            unlink($files[0]);
            return null;
        }

        return json_decode($record->value, true);
    }

    public function delete(string $key): void
    {
        $query = new CacheKeyQueryRecord(key: $key);
        $files = $this->service->getFilesToScan($query);

        if (!empty($files) && $this->service->fileExists($files[0])) {
            unlink($files[0]);
        }
    }

    public function clear(): void
    {
        $this->service->clear(storage_path('cache/persistent'));
    }

    public function cleanExpired(): int
    {
        return $this->service->cleanExpired(storage_path('cache/persistent'), function ($line) {
            if (!isset($line['expires_at'])) {
                return false;
            }
            $expiresAt = new DateTimeVO($line['expires_at']);
            return $expiresAt->isBefore(new DateTimeVO());
        });
    }
}
```

### Intégration Laravel

[](#intégration-laravel)

```
// AppServiceProvider.php
use AndyDefer\LaravelJsonl\Contexts\JsonlContext;
use AndyDefer\LaravelJsonl\JsonlService;
use AndyDefer\LaravelJsonl\Strategies\TemporalPathStrategy;
use AndyDefer\LaravelJsonl\Strategies\KeyBasedPathStrategy;
use AndyDefer\PhpServices\Services\FileSystemService;

public function register(): void
{
    // Contexte partagé
    $this->app->singleton(JsonlContext::class);

    // Service pour les logs
    $this->app->singleton('jsonl.logs', function ($app) {
        $strategy = new TemporalPathStrategy(storage_path('logs/structured'));
        $fs = new FileSystemService();
        $context = $app->make(JsonlContext::class);
        return new JsonlService($strategy, $fs, $context);
    });

    // Service pour le cache
    $this->app->singleton('jsonl.cache', function ($app) {
        $strategy = new KeyBasedPathStrategy(storage_path('cache/jsonl'), 2);
        $fs = new FileSystemService();
        $context = $app->make(JsonlContext::class);
        $service = new JsonlService($strategy, $fs, $context);
        $service->enableBuffer(100);
        return $service;
    });
}

// Controller
class AnalyticsController extends Controller
{
    public function track(Request $request)
    {
        $jsonl = app('jsonl.logs');

        $log = new LogJsonlRecord(
            time: new DateTimeVO(),
            level: 'info',
            type: 'page_view',
            payload: new StrictDataObject([
                'page' => $request->path(),
                'user_id' => auth()->id(),
                'user_agent' => $request->userAgent(),
            ]),
        );

        $jsonl->writeBuffered($log);

        return response()->json(['status' => 'tracked']);
    }
}
```

---

API Reference
-------------

[](#api-reference)

### Interfaces

[](#interfaces)

InterfaceMéthodes principales`JsonlWriterInterface``write()`, `writeBatch()`, `writeBuffered()`, `flushBuffer()``JsonlReaderInterface``readAll()`, `readLineByLine()`, `search()`, `searchMultiple()``JsonlCleanerInterface``cleanOlderThan()`, `cleanExpired()`, `cleanByPattern()`, `dryRun()`, `clear()``JsonlLockInterface``acquire()`, `release()`, `executeWithLock()`, `isLocked()``JsonlPathStrategyInterface``getFilePath()`, `getFilesToScan()`, `getBaseDirectory()`### Classes principales

[](#classes-principales)

ClasseRôle`JsonlService`Service principal (stateless)`JsonlContext`État du service (locks, buffer)`LogJsonlRecord`Record pour logs`CacheJsonlRecord`Record pour cache`TemporalPathStrategy`Stratégie temporelle`KeyBasedPathStrategy`Stratégie par clé`JsonlProcessingContext`Contexte de traitement`TemporalLogQueryRecord`Query pour logs temporels`CacheKeyQueryRecord`Query pour cache par clé### Exceptions

[](#exceptions)

ExceptionDescription`JsonlException`Exception de base`JsonlLockException`Problème de verrouillage---

Dépannage
---------

[](#dépannage)

### Erreur : "Unsupported record type"

[](#erreur--unsupported-record-type)

La stratégie attend un type spécifique de Record.

**Solution :** Utilisez le bon Record avec la bonne stratégie :

- `TemporalPathStrategy` → `LogJsonlRecord`
- `KeyBasedPathStrategy` → `CacheJsonlRecord`

### Erreur : "Timeout acquiring lock"

[](#erreur--timeout-acquiring-lock)

Un autre processus maintient le verrou trop longtemps.

**Solution :**

- Augmentez le timeout : `$service->acquire($path, timeout: 10)`
- Vérifiez les processus zombies
- Nettoyez les fichiers `.lock` orphelins

### Les fichiers ne sont pas trouvés par `cleanExpired()`

[](#les-fichiers-ne-sont-pas-trouvés-par-cleanexpired)

Le pattern glob `**` n'est pas supporté sur certains systèmes.

**Solution :** Le package utilise `RecursiveIterator`, compatible tous systèmes.

### Performance lente avec beaucoup de petits fichiers

[](#performance-lente-avec-beaucoup-de-petits-fichiers)

**Solution :**

- Utilisez le buffer : `$service->enableBuffer(100)`
- Regroupez les écritures : `writeBatch()`
- Ajustez le niveau de hash pour `KeyBasedPathStrategy`

---

Licence
-------

[](#licence)

MIT © [Andy Defer](https://github.com/andydefer)
------------------------------------------------

[](#mit--andy-defer)

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2170ec3fbad9eb4b002661ab4f58b1cc374eae4293b92904c6a74bc2818bd570?d=identicon)[andydefer](/maintainers/andydefer)

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/andydefer-laravel-jsonl/health.svg)

```
[![Health](https://phpackages.com/badges/andydefer-laravel-jsonl/health.svg)](https://phpackages.com/packages/andydefer-laravel-jsonl)
```

###  Alternatives

[rahulhaque/laravel-filepond

Use FilePond the Laravel way

264127.9k2](/packages/rahulhaque-laravel-filepond)[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

135212.4k7](/packages/statamic-rad-pack-runway)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21313.7k3](/packages/ecotone-laravel)[slimani/filament-media-manager

A media manager plugin for Filament.

115.1k](/packages/slimani-filament-media-manager)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
