PHPackages                             igor-php/igor-php - 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. [Queues &amp; Workers](/categories/queues)
4. /
5. igor-php/igor-php

ActiveLibrary[Queues &amp; Workers](/categories/queues)

igor-php/igor-php
=================

The faithful assistant for your FrankenPHP Workers.

v0.6.3(2w ago)805.4k↓41.4%1MITGoPHP &gt;=8.1CI passing

Since Apr 13Pushed yesterday1 watchersCompare

[ Source](https://github.com/igor-php/igor-php)[ Packagist](https://packagist.org/packages/igor-php/igor-php)[ RSS](/packages/igor-php-igor-php/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)DependenciesVersions (31)Used By (1)

🧟‍♂️⚡ Igor-Php
==============

[](#‍️-igor-php)

 [![Igor PHP Logo](assets/igor-php.png)](assets/igor-php.png)

**The faithful assistant for your FrankenPHP Workers.**

`igor-php` is an ultra-fast static linter written in **Go** that prepares your **Symfony** application for the persistent memory model of **FrankenPHP**.

Like the legendary assistant, `igor` checks every connection and part of your application to ensure it won't "blow up" when the lightning strikes (Worker Mode).

---

✨ Highlights
------------

[](#-highlights)

- **⚡ Lightning Fast**: Scans hundreds of files in milliseconds using Go's native multi-threading.
- **🔍 Deep Audit**: Automatically detects Symfony projects and audits **every shared service** defined in the container, including those in `vendor/` and external bundles.
- **🎯 Surgical Precision**: Detects complex state mutations (`$this->prop[]`, `static::$prop`, increments) without false positives.
- **🧠 Intelligent**: Verifies not just the presence of `ResetInterface`, but ensures all mutated properties are correctly reset. Automatically ignores **`readonly` properties and classes** (PHP 8.1+) as they are immutable by design.
- **🛡️ Safety First**: Catches dangerous `exit()` or `die()` calls, and warns about **PHP Superglobals** (`$_GET`, `$_POST`, etc.) or **local static variables** that could leak state between requests.
- **🔇 Zero Noise**: Automatically ignores `Symfony\` and `Doctrine\` namespaces, and common data folders (`Entity`, `Dto`, `ApiResource`).
- **📦 Project vs. Vendor**: Clear separation between your code and third-party dependencies, with tailored recommendations for each.
- **🎯 Selective Ignore**: Skip specific lines using the `// @igor-ignore` comment, or target entire classes, methods, and properties using modern **PHP 8 Attributes** (`#[WorkerSafe]`).

---

📋 Prerequisites
---------------

[](#-prerequisites)

- **Go**: Required to compile or install the binary.
- **PHP 8.1+**: Required for the **Deep Audit** mode. Igor uses PHP Reflection to precisely locate service files within your project and `vendor/` directory. Without PHP, Igor will fall back to a standard directory scan.

---

🚀 Installation
--------------

[](#-installation)

### Via Composer (Recommended)

[](#via-composer-recommended)

```
composer require --dev igor-php/igor-php
```

### Enable the Symfony Bundle (Optional but Recommended)

[](#enable-the-symfony-bundle-optional-but-recommended)

To make Igor even more reliable, you can enable the embedded PHP bundle. It generates a precise service map directly from your container, which Igor Go will use to audit your services.

Add the bundle to your `config/bundles.php`:

```
return [
    // ...
    IgorPhp\IgorBundle\IgorPhpBundle::class => ['dev' => true, 'test' => true],
];
```

Or manually in your `Kernel.php`:

```
public function registerBundles(): iterable
{
    // ...
    if ($this->getEnvironment() === 'dev') {
        yield new IgorPhp\IgorBundle\IgorPhpBundle();
    }
}
```

### Via Go

[](#via-go)

```
go install github.com/igor-php/igor-php@latest
```

---

🛠️ Usage
--------

[](#️-usage)

### 🪄 Quick Start

[](#-quick-start)

Igor can automatically detect your project type Symfony and generate a default configuration for you:

```
# Initialize igor.json
igor-php init

# Initialize with a custom name/path
igor-php init -c custom-igor.json
```

### 🔍 Audit your project

[](#-audit-your-project)

Once initialized (or using defaults), let Igor audit your project:

```
# Standard usage
igor-php .

# Generate a baseline to ignore existing errors
igor-php --generate-baseline

# Custom configuration file
igor-php --config custom-igor.json .
# or shorthand
igor-php -c custom-igor.json .

# Custom console path, environment and verbose mode
igor-php --console app/console --env stage --verbose .

# Non-Symfony project or skip Symfony discovery
igor-php --no-agent .
```

### Non-Symfony Projects

[](#non-symfony-projects)

Igor can also audit standard PHP projects that don't use the Symfony framework. In this case, use the `--no-agent` flag to disable automatic container discovery.

When using Igor without Symfony, you should manually define which directories or vendor packages to audit in your `igor.json`:

```
{
  "scan_vendors": ["my-company/internal-library"],
  "exclude": ["tests", "Data", "vendor/symfony"]
}
```

> 💡 **Note**: Without Symfony, Igor performs a recursive scan of your project directory (excluding folders in `exclude`). Using `scan_vendors` allows you to force the audit of specific third-party libraries even without the Symfony service map.

🧪 See it in Action
------------------

[](#-see-it-in-action)

Want to understand why Igor is vital for your Worker environment? Check these real-world scenarios from our **Leak Lab**:

**1. Memory Pressure (The "BOOM" effect)****2. Global State Poisoning**[![Memory Leak Demo](docs/heavy-load.gif)](docs/heavy-load.gif)[![Global State Leak Demo](docs/timezone-poison.gif)](docs/timezone-poison.gif)*Adding data to a shared service without reset will accumulate in RAM until the worker crashes.**Modifying global PHP settings (like timezone) "poisons" the worker thread for all future requests.*### 🛡️ Igor's Verdict: Catching them all in &lt; 1s

[](#️-igors-verdict-catching-them-all-in--1s)

[![Igor Scan Demo](docs/igor-audit.gif)](docs/igor-audit.gif)*Igor identifies all leaks (Static, Stateful, Incomplete Reset) and dangerous global function calls automatically.*

---

### 🧪 Try the Leak Lab yourself!

[](#-try-the-leak-lab-yourself)

We've built an **interactive laboratory** using Symfony and FrankenPHP. You can run it locally with Docker and see the memory leaks with your own eyes.

[**Explore the Igor Leak Lab →**](examples/demo-leak/README.md)
---------------------------------------------------------------

[](#explore-the-igor-leak-lab-)

### Deep Audit Mode (Symfony)

[](#deep-audit-mode-symfony)

When a Symfony project is detected, Igor combines three layers of discovery to ensure maximum reliability:

1. **Level 1: Project Code (Recursive Scan)**: Igor scans all PHP files in your project directory (excluding `vendor`, `var`, `tests`, etc.). This ensures that even if Symfony "inlines" or "hides" a service for optimization, Igor will still find and audit it.
2. **Level 2: Smart Filtering (Composer)**: Igor automatically parses your `composer.json` to identify packages in `require-dev`. It will automatically exclude any service originating from these packages to reduce noise and focus only on production-ready code.
3. **Level 3: Igor Agent (Embedded Bundle)**: By enabling the optional PHP bundle, Igor becomes "infallible". The bundle hooks into the Symfony compilation process to export the exact map of all active shared services.

---

🧠 How it Works
--------------

[](#-how-it-works)

### 1. Smart Filtering

[](#1-smart-filtering)

Igor reads the `require-dev` section of your `composer.json`. When it audits your Symfony container, it checks the physical path of each service. If a service is located inside a `vendor/` directory belonging to a dev package (like `phpunit/phpunit` or `symfony/maker-bundle`), Igor will automatically skip it.

### 2. Igor Agent (The PHP Bundle)

[](#2-igor-agent-the-php-bundle)

The `IgorPhpBundle` includes a `CompilerPass` that runs every time you clear your Symfony cache (`php bin/console cache:clear`).

> ⚠️ **Important**: You must run `php bin/console cache:clear` whenever you add or modify services in your Symfony project to ensure the Igor Agent map is up-to-date.

- **What it does**: It iterates through the `ContainerBuilder`, identifies all **Shared Services**, and extracts their class names and IDs.
- **The Cache**: It writes this information into a small JSON file: `var/cache//igor_service_map.json`.
- **The Benefit**: The Go binary reads this file instead of executing the heavy `debug:container` command. This makes the audit launch near-instant and ensures 100% accuracy, even for services added by complex compiler passes or decorators.

#### Example `igor_service_map.json`:

[](#example-igor_service_mapjson)

```
{
    "definitions": {
        "app.mail_service": {
            "class": "App\\Service\\MailService",
            "public": true,
            "shared": true
        },
        "logger": {
            "class": "Monolog\\Logger",
            "public": true,
            "shared": true
        }
    },
    "aliases": {
        "Psr\\Log\\LoggerInterface": "logger"
    }
}
```

---

⚙️ Configuration
----------------

[](#️-configuration)

You can customize Igor's behavior by creating an `igor.json` file at the root of your project:

```
{
  "exclude": ["vendor", "var", "src/Entity"],
  "safe_namespaces": ["Symfony\\", "Doctrine\\", "Twig\\", "IgorPhp\\IgorBundle\\"],
  "console_path": "bin/console",
  "env": "dev",
  "verbose": false
}
```

Time taken: 1.2s

💡 RECOMMENDATIONS: \[PROJECT\] Since this is your code, you should refactor these services to be stateless or implement ResetInterface to clear the state between requests. \[VENDOR\] This is third-party code. If you can't fix it, consider setting a 'max\_requests' limit in your Worker configuration to mitigate memory leaks.

```

---

## ⚙️ Configuration

You can customize Igor's behavior by creating an `igor.json` file at the root of your project:

```json
{
  "exclude": ["vendor", "tests", "Entity"],
  "safe_namespaces": ["Symfony\\", "Doctrine\\", "IgorPhp\\IgorBundle\\"],
  "scan_vendors": ["my-company/internal-bundle"],
  "baseline": "igor-baseline.json",
  "console_path": "bin/console",
  "env": "dev",
  "verbose": false
}

```

- **exclude**: List of directories to skip during indexing.
- **safe\_namespaces**: Igor will ignore state mutations in classes starting with these prefixes.
- **scan\_vendors**: List of sub-directories within `vendor/` to scan recursively.
- **baseline**: Path to a baseline file containing findings to ignore.
- **console\_path**: Custom path to the Symfony console binary. Defaults to `bin/console`.
- **env**: Symfony environment to use for container analysis. Defaults to `dev`.
- **verbose**: Enable verbose output to see skipped services and reasons. Defaults to `false`.

---

🧠 LLM Review &amp; Triage
-------------------------

[](#-llm-review--triage)

Igor can export findings in a structured JSON format and help you triage them using an LLM. This is particularly useful for distinguishing between harmless state (e.g., caches) and dangerous data leaks.

### 1. Frictionless Mode (No API key needed)

[](#1-frictionless-mode-no-api-key-needed)

Generate a ready-to-use prompt for your favorite LLM (ChatGPT, Claude, etc.):

```
# 1. Export the audit to JSON
igor-php --output llm . > audit.json

# 2. Generate the review prompt
igor-php review audit.json
```

Igor will create `igor-review-prompt.md`. Simply copy its content into an LLM to get a detailed security analysis and remediation plan.

### 2. Expert Mode (Automatic)

[](#2-expert-mode-automatic)

Configure Igor to call an LLM directly by updating your `igor.json`:

#### Option A: Using Gemini CLI (Recommended if installed)

[](#option-a-using-gemini-cli-recommended-if-installed)

If you have `gemini-cli` installed and configured, Igor can use it directly:

```
{
  "llm": {
    "provider": "gemini",
    "model": "gemini-1.5-pro"
  }
}
```

#### Option B: Using Ollama (Local LLM)

[](#option-b-using-ollama-local-llm)

If you run Ollama locally, Igor can use its OpenAI-compatible endpoint. This is great for privacy, but **please note that triage quality depends heavily on the model size.** Smaller local models (like Llama 3 8B) are significantly less capable than large online models for complex security triage.

```
{
  "llm": {
    "provider": "ollama",
    "model": "llama3"
  }
}
```

*Note: Igor defaults the `api_url` to `http://localhost:11434/v1` for Ollama.*

#### Option C: OpenAI-Compatible API

[](#option-c-openai-compatible-api)

```
{
  "llm": {
    "provider": "openai",
    "api_url": "https://api.openai.com/v1",
    "api_key_env": "OPENAI_API_KEY",
    "model": "gpt-4o"
  }
}
```

Then run:

```
# For Option C, ensure the API key is set
export OPENAI_API_KEY=your_secret_key

igor-php review audit.json
```

Igor will automatically send the audit to the LLM and save the report to `igor-review.md`.

---

### Selective Ignoring (Comments &amp; Attributes)

[](#selective-ignoring-comments--attributes)

#### 1. Line-by-Line Exclusions

[](#1-line-by-line-exclusions)

If you have a specific line that you know is safe, you can use the `// @igor-ignore` annotation:

```
// @igor-ignore
$this->cache = $data; // This line will be ignored

$this->counter++; // @igor-ignore - This line too
```

#### 2. Modern Exclusions with PHP 8 Attributes (Recommended)

[](#2-modern-exclusions-with-php-8-attributes-recommended)

Instead of line-by-line comments, you can use modern PHP 8 attributes to exclude entire classes, specific methods, or individual properties.

First, import the attribute (available via the embedded Symfony bundle):

```
use IgorPhp\IgorBundle\Attribute\WorkerSafe;
```

Then decorate your code elements:

- **Class-level**: Ignore all state leak and mutation findings within the entire class.

    ```
    #[WorkerSafe(scope: 'boot-time', reason: 'Configuration is frozen after warmup')]
    class MyService {
        // All mutations and state checks inside this class are ignored
    }
    ```
- **Method-level**: Ignore state mutations occurring inside a specific method.

    ```
    class MyService {
        #[WorkerSafe]
        public function warmUp() {
            $this->cache = ['foo' => 'bar']; // This mutation is ignored
        }
    }
    ```
- **Property-level**: Ignore all mutations on a specific property and exclude it from the `ResetInterface` verification. Works flawlessly with both standard and constructor-promoted properties!

    ```
    class MyService {
        #[WorkerSafe]
        private $cache = []; // Mutations and missing reset checks are ignored

        public function __construct(
            #[WorkerSafe]
            private StatefulService $safeService, // Promoted property is safe!
        ) {}
    }
    ```

---

🔍 Understanding Deep Audit Filtering
------------------------------------

[](#-understanding-deep-audit-filtering)

When using the **Deep Audit** mode (Symfony), Igor might analyze fewer services than your total container count. Use the `--verbose` flag to see exactly why a service was skipped. Common reasons include:

- **🔄 Duplicate File**: Multiple Service IDs (aliases, locators, etc.) pointing to the same PHP file. Igor only audits each unique file once.
- **♻️ Non-shared (Prototype)**: Services marked as `shared: false` are recreated on every request and don't persist state between workers. They are safe by design.
- **λ Closures / Synthetic**: Services that don't map to a physical PHP class (like Closures or synthetic services) cannot be statically analyzed.
- **🛡️ Safe Namespace**: The class belongs to a namespace defined in `safe_namespaces` (like `Symfony\` or `Doctrine\`).

> 💡 **Pro Tip**: If you notice **Entities, DTOs, or Data Models** appearing in the Igor audit, it means they are registered as "Shared Services" in your Symfony container. This is usually a configuration error in your `services.yaml`. You should exclude these directories from autowiring:
>
> ```
> # config/services.yaml
> services:
>     App\:
>         resource: '../src/'
>         exclude:
>             - '../src/Entity/'
>             - '../src/Dto/'
>             - '../src/Kernel.php'
> ```

---

🤖 CI/CD Integration
-------------------

[](#-cicd-integration)

Igor is designed to work out-of-the-box in your CI pipelines. It will exit with **code 1** if any error is found, effectively stopping your build.

### GitHub Actions support

[](#github-actions-support)

When running inside GitHub Actions, Igor automatically generates **inline annotations**. This means errors will appear directly in your Pull Request review, right next to the code causing the issue.

 [![Igor GitHub Review](assets/review.png)](assets/review.png)

### GitHub Actions Example

[](#github-actions-example)

```
name: Static Analysis
on: [push, pull_request]

jobs:
  igor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Install Dependencies
        run: composer install --no-progress --prefer-dist

      - name: Warmup Symfony Cache (for Deep Audit)
        run: php bin/console cache:warmup --env=dev

      - name: Run Igor Audit
        run: vendor/bin/igor-php .
```

---

🙏 Credits &amp; Inspirations
----------------------------

[](#-credits--inspirations)

- **[Phanalist](https://github.com/denzyldick/phanalist)**: Special thanks to `phanalist` and its rule `E0012` (Stateful Service) which inspired Igor's core mutation detection logic.
- **[Gemini CLI](https://github.com/google/gemini-cli)**: This project was built with the help of Gemini CLI.
- **[FrankenPHP](https://frankenphp.dev/)**: For the amazing server that makes these checks necessary!

---

📄 License
---------

[](#-license)

MIT

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance99

Actively maintained with recent releases

Popularity37

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~2 days

Total

23

Last Release

14d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/08757532c8680bba913dceb73ef2f282939f9a9cc1681fc6416d17ba33575f72?d=identicon)[KevinMartinsDev](/maintainers/KevinMartinsDev)

---

Top Contributors

[![KevinMartinsDev](https://avatars.githubusercontent.com/u/16019629?v=4)](https://github.com/KevinMartinsDev "KevinMartinsDev (153 commits)")

---

Tags

frankenphplinterphpstate-mutationsstatelessstatic-analysissymfonyworker-modesymfonystatic analysislinterfrankenphpworker

### Embed Badge

![Health badge](/badges/igor-php-igor-php/health.svg)

```
[![Health](https://phpackages.com/badges/igor-php-igor-php/health.svg)](https://phpackages.com/packages/igor-php-igor-php)
```

###  Alternatives

[qxsch/worker-pool

Runs tasks in a parallel processing workerpool.

108327.5k1](/packages/qxsch-worker-pool)

PHPackages © 2026

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