PHPackages                             rudak/js-injector - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. rudak/js-injector

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

rudak/js-injector
=================

Injection de constantes PHP dans un JS - TEST

v1.6.6.4(7y ago)121Apache-2.0PHPPHP &gt;=5.6CI passing

Since Jan 1Pushed 1w ago1 watchersCompare

[ Source](https://github.com/rudak/JsInjector)[ Packagist](https://packagist.org/packages/rudak/js-injector)[ RSS](/packages/rudak-js-injector/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (6)DependenciesVersions (6)Used By (0)

JsInjector
==========

[](#jsinjector)

[![CI](https://github.com/rudak/JsInjector/actions/workflows/ci.yml/badge.svg)](https://github.com/rudak/JsInjector/actions/workflows/ci.yml)

Injectez des constantes PHP dans le navigateur, sans template échappé de force et sans appeler votre backend depuis le JavaScript.

`JsInjector` est un **bundle Symfony + une bibliothèque JavaScript** qui fait une seule chose : transformer vos valeurs serveur en variables JavaScript prêtes à l'emploi, de façon **sûre**, **prévisible** et **testable**.

---

Pourquoi utiliser JsInjector ?
------------------------------

[](#pourquoi-utiliser-jsinjector-)

### Le problème

[](#le-problème)

Vous avez des valeurs qui n'existent que côté serveur : URL d'API, identifiants d'environnement, feature flags, locales, timeouts, limites de pagination...

Sans outil, vous vous retrouvez à :

```

  var API_URL = 'https://api.example.com';      // dupliqué à la main
  var DEBUG = true;                              // ...et vite désynchronisé

  {"API_URL": "https://api.example.com", "DEBUG": true}

/* 30 lignes pour lire + injecter ça à la main */

```

Toutes ces approches ont les mêmes défauts : pas de validation, pas de typage, des données dupliquées entre PHP et JS, et des failles XSS dès que la moindre valeur contient `` ou une apostrophe.

### La solution

[](#la-solution)

Avec JsInjector, la chaîne est unique et vérifiée :

```
Service PHP (provider)
        │  implémente HarvesterInterface
        │  taggé `rudak.injector`
        ▼
Collecte (ValuesHarvester) ──▶ validation des noms de variables
        ▼
Commande `rudak:generate:js`
        ▼
injection.js (fichier statique, sûr, versionnable)
        │  var { API_URL, DEBUG } = INJECTED_VALUES;
        ▼
votre code JS : console.log(API_URL)   // prêt à l'emploi

```

**Pourquoi c'est mieux :**

Inline / data-attributesJsInjectorSource de véritédupliquée PHP + HTMLun seul provider PHPValidation des nomsaucuneidentifiants JS + mots réservés (PHP **et** JS)SécuritéXSS possiblelittéral JSON échappé (``)Typageaucuntypes affichés à la générationTestabilitéimpossiblegénérateur 100 % testé (PHPUnit / Vitest)Compat navigateurdépend de la méthodemoderne et propreEt le même moteur existe **côté client** : `inject()` fait la même chose à l'exécution, par exemple pour des valeurs reçues d'une API.

---

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

[](#installation)

### PHP (côté Symfony)

[](#php-côté-symfony)

```
composer require rudak/js-injector
```

Prérequis : PHP 8.2+ et Symfony 6.4 ou 7.x. Le bundle s'enregistre automatiquement (Flex).

### JavaScript (bibliothèque standalone)

[](#javascript-bibliothèque-standalone)

```
npm install js-injector
```

La bibliothèque est disponible en **ESM** (`import`) et en **CommonJS** (`require`), avec les types TypeScript inclus.

---

Utilisation rapide
------------------

[](#utilisation-rapide)

### 1. Créez un provider de valeurs

[](#1-créez-un-provider-de-valeurs)

Un provider est n'importe quel service qui implémente `HarvesterInterface` :

```
// src/Service/FrontendConfigProvider.php
use Rudak\JsInjector\Harvester\HarvesterInterface;

class FrontendConfigProvider implements HarvesterInterface
{
    public function getValues(): array
    {
        return [
            'API_URL' => 'https://api.example.com',
            'DEBUG'   => $_ENV['APP_ENV'] === 'dev',
            'LOCALE'  => 'fr_FR',
        ];
    }
}
```

### 2. Taguez le service

[](#2-taguez-le-service)

```
# config/services.yaml
services:
    App\Service\FrontendConfigProvider:
        tags: ['rudak.injector']
```

Le tag est tout ce qui compte : JsInjector collecte automatiquement **tous** les services taggés, peu importe leur nombre.

> **Pour des valeurs fraîches à chaque rechargement** (données dynamiques), utilisez le tag `rudak.injector.dynamic` — voir [Injection dynamique](#injection-dynamique) ci-dessous.

### 3. Générer le fichier JS

[](#3-générer-le-fichier-js)

```
php bin/console rudak:generate:js
```

Le fichier `public/bundles/rudakInjection/injection.js` est créé. Par défaut, c'est un **module ES** :

```
// generated by rudak:generate:js
export default {"API_URL":"https:\/\/api.example.com","DEBUG":true,"LOCALE":"fr_FR"};
```

La console affiche aussi un tableau récapitulatif des variables et de leurs types :

```
 --------------  -------
  variable name   type
 --------------  -------
  API_URL         string
  DEBUG           boolean
  LOCALE          string
 --------------  -------

```

### 4. Inclure et utiliser

[](#4-inclure-et-utiliser)

**Avec le format `module` (défaut)** — import ES moderne, les valeurs ne polluent pas le scope global :

```

```

```
// app.js
import config from '/bundles/rudakInjection/injection.js';

console.log(config.API_URL); // 'https://api.example.com'
console.log(config.DEBUG);   // true
```

**Avec le format `globals`** — script classique, les valeurs deviennent des variables globales :

```

```

```
// app.js
console.log(API_URL);   // 'https://api.example.com'
console.log(DEBUG);     // true
if (DEBUG) { /* ... */ }
```

Les variables sont de vraies variables globales : pas besoin de `window.` ni de parseur.

---

Configuration du bundle
-----------------------

[](#configuration-du-bundle)

```
# config/packages/js_injector.yaml
js_injector:
    # Chemin du fichier généré, relatif au répertoire du projet.
    output_path: 'public/bundles/rudakInjection/injection.js'

    # Format du fichier généré : 'module' (défaut, import ES) ou 'globals' (script classique).
    format: 'module'

    # Optionnel : regroupe toutes les valeurs sous un seul namespace.
    namespace: 'MyApp'
```

### Format `module` (défaut)

[](#format-module-défaut)

Le fichier est un **module ES** : `export default` quand aucun namespace n'est défini, `export const ` sinon. Il se consomme avec un `import` moderne — c'est la manière recommandée d'injecter des valeurs dans une application utilisant déjà un bundler (Vite, Webpack, Rollup, ...).

```
// generated by rudak:generate:js
export default {"API_URL":"https:\/\/api.example.com","DEBUG":true};
```

```
import config from '/bundles/rudakInjection/injection.js';
console.log(config.API_URL); // 'https://api.example.com'
```

### Format `globals`

[](#format-globals)

Le comportement historique : script classique, les valeurs deviennent des variables globales (ou une seule variable quand `namespace` est défini), sans `import` nécessaire.

```
// generated by rudak:generate:js
var INJECTED_VALUES = {"API_URL":"https:\/\/api.example.com","DEBUG":true};
// creating 2 variable(s)
var { API_URL, DEBUG } = INJECTED_VALUES;
```

### Mode namespace

[](#mode-namespace)

Avec `namespace: 'MyApp'`, les valeurs sont regroupées sous une seule variable (ou un seul export nommé) :

**En `module` :**

```
// generated by rudak:generate:js
export const MyApp = {"API_URL":"https:\/\/api.example.com","DEBUG":true};
```

```
import { MyApp } from '/bundles/rudakInjection/injection.js';
console.log(MyApp.API_URL); // 'https://api.example.com'
```

**En `globals` :**

```
// generated by rudak:generate:js
var MyApp = {"API_URL":"https:\/\/api.example.com","DEBUG":true};
```

```
console.log(MyApp.API_URL); // 'https://api.example.com'
```

Recommandé dès que vous injectez plus de quelques valeurs, ou si vous voulez un contrat clair entre PHP et JS.

---

Injection dynamique
-------------------

[](#injection-dynamique)

La commande `rudak:generate:js` fige les valeurs dans un fichier : parfait pour une configuration stable. Mais pour des **données qui changent à chaque requête** (utilisateur connecté, A/B testing, feature flags, stats en temps réel...), les valeurs doivent être recalculées à **chaque rechargement de page**.

JsInjector propose un second canal, branché au niveau du kernel Symfony, qui réutilise la même interface.

### 1. Taguez le provider comme dynamique

[](#1-taguez-le-provider-comme-dynamique)

```
# config/services.yaml
services:
    App\Service\UserContextProvider:
        tags:
            # valeurs recalculées à chaque requête
            # 'channel' identifie ce jeu de données : il servira à choisir
            # quelles données injecter sur quelle page.
            - { name: 'rudak.injector.dynamic', channel: 'user_context' }

    App\Service\FeatureFlagsProvider:
        tags:
            - { name: 'rudak.injector.dynamic', channel: 'feature_flags' }
```

Le provider implémente la même `HarvesterInterface`, et il est **exclu du fichier statique** généré par la commande. Le tag `channel` est une simple étiquette de données (comme `user_context`, `feature_flags`, `stats`) : vous la réutilisez dans l'attribut pour choisir ce que la page reçoit. Sans `channel`, le provider est référencé par son id de service.

### 2. Activez et choisissez les pages

[](#2-activez-et-choisissez-les-pages)

L'injection dynamique est **opt-in** : un switch global (`dynamic_enabled`, défaut `false`) pour l'activer, puis un **attribut par page** pour dire quelles données la page reçoit.

```
# config/packages/js_injector.yaml
js_injector:
    dynamic_enabled: true   # active le listener kernel.response
```

```
// src/Controller/AccountController.php
use Rudak\JsInjector\Attribute\JsInjectorDynamic;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

final class AccountController extends AbstractController
{
    // Cette action reçoit les données du channel 'user_context'.
    #[JsInjectorDynamic('user_context')]
    public function account(): Response { /* ... */ }

    // Cette action reçoit les données de 'user_context' ET 'feature_flags'.
    #[JsInjectorDynamic(['user_context', 'feature_flags'])]
    public function dashboard(): Response { /* ... */ }

    // Cette page ne reçoit rien de dynamique.
    public function home(): Response { /* ... */ }
}
```

Variantes : `#[JsInjectorDynamic]` **sans valeur** récolte **tous** les providers dynamiques ; posé sur la **classe** du contrôleur, l'attribut s'applique à toutes ses actions (un attribut posé sur l'action a priorité).

### 3. Que se passe-t-il sur une page marquée ?

[](#3-que-se-passe-t-il-sur-une-page-marquée-)

Le `DynamicValuesListener` (sur `kernel.response`) agrège les providers dynamiques **du channel demandé** et injecte leurs valeurs dans la réponse HTML, juste avant `` :

```
{"USER_ID":42,"CURRENT_LOCALE":"fr_FR","FEATURE_FLAGS":{"chat":true}}
```

- données **fraîches à chaque chargement** (recalculées par le kernel) ;
- tag `application/json` **inerte** : le contenu n'est jamais exécuté, pas de risque XSS ni de souci CSP ;
- rien n'est injecté si aucun provider dynamique ne correspond au channel demandé ;
- les pages **sans attribut** ne sont jamais touchées (aucun tag, aucune charge).

### 4. Debug : voir les channels et leurs données

[](#4-debug--voir-les-channels-et-leurs-données)

```
php bin/console rudak:debug:injection
```

Liste les channels dynamiques (avec leurs valeurs) et les providers statiques :

```
INJECTOR PROVIDERS
==================

Dynamic channels (tag: rudak.injector.dynamic)
 ----------------  ---------------------------  ------------------------------------
  channel           provider                    data
 ----------------  ---------------------------  ------------------------------------
  user_context      App\Service\UserContext..   USER_ID: 42, ROLE: admin
  feature_flags     App\Service\FeatureFlags..  CHAT_ENABLED: true
 ----------------  ---------------------------  ------------------------------------

Static providers (tag: rudak.injector, written to the generated file)
 ---------------------------  -------------------------------
  provider                    data
 ---------------------------  -------------------------------
  App\Service\FrontendConfig  API_URL: https://api.example.com
 ---------------------------  -------------------------------

```

### 5. Côté client

[](#5-côté-client)

```
import { injectFromDom } from 'js-injector';

const result = injectFromDom('#js-injector-dynamic');
// => { success: true, injected: ['USER_ID', 'CURRENT_LOCALE', 'FEATURE_FLAGS'], errors: [] }
// => null si le tag n'est pas présent dans la page
```

`injectFromDom(selector, options)` combine `loadFromScriptTag` + `inject` : même API (namespace, freeze, overwrite, target), voir [la référence](#injectfromdomselector-options).

### 6. Injection automatique (optionnel)

[](#6-injection-automatique-optionnel)

Le tag JSON est **inerte** : rien n'est injecté tant que le client ne lit pas le tag. Si vous voulez une injection **100 % automatique** à chaque chargement de page, la façon la plus simple est un petit module de bootstrap :

```
// assets/bootstrap-inject.js
import { injectFromDom } from 'js-injector';

// Le tag  est déjà
// présent dans le DOM quand ce module s'exécute : les modules sont différés
// par défaut, ils tournent après le parsing du document.
injectFromDom('#js-injector-dynamic');
```

```

```

**Pourquoi ça fonctionne sans `DOMContentLoaded` ?** `` est toujours exécuté après le parsing du document. Or le tag JSON est inséré juste avant `` : au moment où votre module tourne, il existe forcément. Appeler `injectFromDom` quand le tag est absent est sans danger (il retourne `null`), ce module peut donc être chargé sur **toutes** les pages, y compris celles sans données dynamiques.

**Sans module (script classique) ?** Utilisez `DOMContentLoaded` — un `` placé dans le template s'exécute pendant le parsing, potentiellement **avant** l'insertion du tag par le listener :

```

  document.addEventListener('DOMContentLoaded', async () => {
    const { injectFromDom } = await import('/bundles/js-injector/index.esm.js');
    injectFromDom('#js-injector-dynamic');
  });

```

Note CSP : un `` inline exige `'unsafe-inline'` (ou un nonce) dans votre politique. Le module de bootstrap externe, lui, est compatible CSP.

**Statique + dynamique en même temps** : combinez le fichier généré par la commande et le tag dynamique dans le même bootstrap :

```
import { inject, injectFromDom } from 'js-injector';
import config from '/bundles/rudakInjection/injection.js'; // valeurs statiques

inject(config);                        // configuration stable
injectFromDom('#js-injector-dynamic'); // valeurs fraîches à chaque page
```

### Configurer l'identifiant du tag

[](#configurer-lidentifiant-du-tag)

```
# config/packages/js_injector.yaml
js_injector:
    dynamic_enabled: true                        # active l'injection dynamique
    dynamic_script_tag_id: 'app-dynamic-values'  # défaut : 'js-injector-dynamic'
```

### Résumé statique vs dynamique

[](#résumé-statique-vs-dynamique)

Statique (`rudak.injector`)Dynamique (`rudak.injector.dynamic`)Quandconfiguration stabledonnées recalculées à chaque requêteMécanismecommande `rudak:generate:js``dynamic_enabled: true` + `#[JsInjectorDynamic]` → `DynamicValuesListener` (`kernel.response`)Périmètretoutes les pages qui importent le fichier**uniquement les pages marquées** (`#[JsInjectorDynamic('channel')]`)Sélection des données—par channel (tag `channel` du provider)Fraîcheurau moment de la commande**à chaque chargement de page**Côté client`` / `import` du fichier`injectFromDom('#js-injector-dynamic')`Debug`rudak:debug:injection` (statique + dynamique)`rudak:debug:injection`---

Comment ça marche, en détail
----------------------------

[](#comment-ça-marche-en-détail)

### Collecte

[](#collecte)

`ValuesHarvester` agrège les services taggés `rudak.injector` (injection de dépendances Symfony, `!tagged_iterator`) pour le fichier statique. `DynamicValuesHarvester` agrège les services taggés `rudak.injector.dynamic`, **indexés par `channel`** et consommés à chaque requête par `DynamicValuesListener` (`kernel.response`). Seuls les channels demandés par `#[JsInjectorDynamic]` sont récoltés pour la page courante.

### Validation

[](#validation)

Chaque clé est validée comme **identifiant JavaScript** : caractères autorisés, pas de mot réservé (`class`, `await`, `return`, ...). Un provider avec une clé invalide est signalé dans la console et **ignoré** — le reste des valeurs est quand même généré. La même liste de mots réservés est partagée côté client (`js-src/utils/validation.js`) pour une cohérence parfaite.

### Génération

[](#génération)

`JsFileGenerator` sérialise les valeurs en un **littéral JSON** :

- insensible aux apostrophes et aux sauts de ligne (plus de `JSON.parse('...')` qui casse) ;
- `` et les caractères Unicode échappés → pas d'injection HTML ;
- priorité : si deux providers déclarent la même clé, le **premier** l'emporte (l'ordre des services dans le tag compte) ;
- deux formats : `module` (défaut, `export default` / `export const `) et `globals` (`var INJECTED_VALUES` + destructuring), choisis via la configuration `format`.

### Côté client

[](#côté-client)

La bibliothèque reproduit exactement ce comportement à l'exécution, ce qui permet de tester le rendu, ou d'injecter des valeurs reçues dynamiquement.

---

API JavaScript
--------------

[](#api-javascript)

```
import {
  // Injection
  inject, injectFromJson, injectFromDom, generateInjectionCode, remove,
  // Chargement
  loadScript, loadJson, loadFromScriptTag,
  // Erreurs
  InjectionError, LoadError, TimeoutError, ValidationError,
} from 'js-injector';
```

### `inject(values, options)`

[](#injectvalues-options)

Injecte des valeurs dans un contexte cible (par défaut `globalThis`).

```
// Variables globales
inject({ API_URL: 'https://api.example.com', DEBUG: true });

// Dans un namespace
inject({ timeout: 5000 }, { namespace: 'MyApp' });
console.log(window.MyApp.timeout); // 5000

// Sur une cible précise (tests, modules, workers)
const target = {};
inject({ foo: 1 }, { target });
```

**Options :**

OptionDéfautRôle`target``globalThis`objet receveur`namespace``null`regroupe les valeurs sous un objet`freeze``false``Object.freeze` les objets injectés (valeurs en lecture seule)`overwrite``true``false` → refuse d'écraser une valeur existante**Retour :** `{ success, injected: string[], errors: Array }`.

```
const result = inject({ foo: 1 }, { overwrite: false });
if (!result.success) {
  console.error(result.errors); // [{ key: 'foo', error: 'Key "foo" already exists...' }]
}
```

### `injectFromJson(jsonString, options)`

[](#injectfromjsonjsonstring-options)

La même chose, depuis une chaîne JSON (utile après un `fetch`).

```
injectFromJson('{"foo": 1, "bar": "hello"}', { namespace: 'App' });
```

### `injectFromDom(selector, options)`

[](#injectfromdomselector-options)

Lit les valeurs d'un `` présent dans la page et les injecte. C'est le pendant client de l'injection dynamique Symfony ([voir plus haut](#injection-dynamique)) : combine `loadFromScriptTag` + `inject`.

```
// Le tag est injecté par le bundle à chaque chargement de page.
const result = injectFromDom('#js-injector-dynamic');

if (result !== null) {
  console.log(result.injected); // ['USER_ID', 'CURRENT_LOCALE', ...]
}
```

Retourne `null` si le tag n'existe pas dans la page.

### `generateInjectionCode(values, options)`

[](#generateinjectioncodevalues-options)

Génère le **code source** que produirait la commande Symfony — utile pour comparer, débugger, ou générer un fichier sans PHP. Mêmes formats que le générateur PHP : `module` (défaut) ou `globals`.

```
// Module (défaut)
generateInjectionCode({ foo: 1 });
// "// generated by rudak:generate:js\nexport default {\"foo\":1};\n"

// Module + namespace → export nommé
generateInjectionCode({ foo: 1 }, { namespace: 'MyApp' });
// "// generated by rudak:generate:js\nexport const MyApp = {\"foo\":1};\n"

// Globals (comportement historique)
generateInjectionCode({ foo: 1 }, { format: 'globals' });
// "// generated by rudak:generate:js\nvar INJECTED_VALUES = {\"foo\":1};\n..."
```

**Options :**

OptionDéfautRôle`namespace``null`regroupe les valeurs sous un namespace (export nommé en `module`)`format``'module'``'module'` ou `'globals'`### `remove(keys, options)`

[](#removekeys-options)

Retire des valeurs précédemment injectées.

```
remove(['API_URL', 'DEBUG']);              // global scope
remove(['timeout'], { namespace: 'MyApp' });
```

### Chargement de ressources

[](#chargement-de-ressources)

```
// Charge un script externe avec timeout
await loadScript('https://cdn.example.com/lib.js', { timeout: 5000, defer: true });

// Charge du JSON avec timeout (AbortController + fetch)
const data = await loadJson('/api/config.json', { timeout: 3000 });

// Lit un  déjà présent dans la page
const config = loadFromScriptTag('#app-config');
```

### Erreurs

[](#erreurs)

Des classes d'erreur dédiées pour un `catch` précis :

```
try {
  await loadJson('/api/config.json');
} catch (error) {
  if (error instanceof TimeoutError)      console.error('trop lent !');
  else if (error instanceof LoadError)    console.error('impossible de charger');
  else                                    throw error;
}
```

---

Exemple complet
---------------

[](#exemple-complet)

Un cas réel : un provider qui expose la configuration de l'appli, versionnée, et un client qui l'utilise.

```
// src/Service/AppConfigProvider.php
use Rudak\JsInjector\Harvester\HarvesterInterface;

class AppConfigProvider implements HarvesterInterface
{
    public function getValues(): array
    {
        return [
            'APP_NAME'     => $this->appName,          // 'Acme'
            'API_URL'      => $this->apiBaseUrl,       // 'https://api.acme.test'
            'APP_ENV'      => $this->environment,      // 'prod'
            'FEATURES'     => ['search' => true, 'chat' => false],
            'MAX_PAGE_SIZE' => 50,
        ];
    }
}
```

```
# config/services.yaml
services:
    App\Service\AppConfigProvider:
        arguments: ['%env(APP_NAME)%', '%env(API_BASE_URL)%', '%kernel.environment%']
        tags: ['rudak.injector']
```

```
# config/packages/js_injector.yaml
js_injector:
    format: 'module'   # import ES moderne (défaut)
    namespace: 'AppConfig'
```

```
php bin/console rudak:generate:js
```

```
// src/app.js
// Le fichier généré est un module ES : import simple, pas de scope global pollué.
import { AppConfig } from '/bundles/rudakInjection/injection.js';

const { API_URL, FEATURES, MAX_PAGE_SIZE } = AppConfig;

if (FEATURES.search) {
  const results = await fetch(`${API_URL}/search?limit=${MAX_PAGE_SIZE}`);
  // ...
}
```

---

Développer sur le projet
------------------------

[](#développer-sur-le-projet)

```
# JavaScript
npm install
npm test              # Vitest (86 tests)
npm run lint          # ESLint (flat config)
npm run build         # Rollup → dist/ (ESM + CJS)
npm run test:coverage # couverture (~96 %)

# PHP
composer install
vendor/bin/phpunit                    # tests (44 tests)
vendor/bin/phpstan analyse            # analyse statique, niveau 6
vendor/bin/php-cs-fixer fix --dry-run # style de code Symfony
```

La CI GitHub Actions exécute tout cela sur Node 18/20 et PHP 8.2/8.3/8.4.

---

Structure du dépôt
------------------

[](#structure-du-dépôt)

```
├── src/                  # Bundle Symfony
│   ├── Command/          #   rudak:generate:js
│   ├── DependencyInjection/  # extension + configuration
│   ├── EventListener/    #   DynamicValuesListener (kernel.response)
│   ├── Generator/        #   JsFileGenerator (le moteur)
│   ├── Harvester/        #   interface + collecteurs tagués (statique/dynamique)
│   ├── Helper/           #   ValuesChecker, VariableTypeHelper
│   ├── Validator/        #   VariableNameValidator (miroir JS)
│   └── Resources/config/ #   services.yml
├── js-src/               # Bibliothèque JavaScript (ESM)
├── __tests__/            # Tests JS (Vitest)
├── tests/                # Tests PHP (PHPUnit)
└── types/                # Définitions TypeScript

```

---

Licence
-------

[](#licence)

Apache License 2.0

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance64

Regular maintenance activity

Popularity8

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 78.3% of commits — single point of failure

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 ~1 days

Total

5

Last Release

2778d ago

Major Versions

v0.1 → v1.02019-01-01

PHP version history (2 changes)v0.1PHP ^5.6

v1.1PHP &gt;=5.6

### Community

Maintainers

![](https://www.gravatar.com/avatar/0c53497f169359bdf8ab67f3690af61bd04257cdf7ad6302bd78534978115e7b?d=identicon)[rudak](/maintainers/rudak)

---

Top Contributors

[![rudak](https://avatars.githubusercontent.com/u/604478?v=4)](https://github.com/rudak "rudak (18 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (5 commits)")

### Embed Badge

![Health badge](/badges/rudak-js-injector/health.svg)

```
[![Health](https://phpackages.com/badges/rudak-js-injector/health.svg)](https://phpackages.com/packages/rudak-js-injector)
```

###  Alternatives

[rairlie/laravel-locking-session

Provide session locking in Laravel

92490.7k1](/packages/rairlie-laravel-locking-session)[fbf/laravel-blog

A Laravel 4 package to add a simple blog to a site

763.0k](/packages/fbf-laravel-blog)

PHPackages © 2026

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