PHPackages                             opencoreemr/openemr-phpstan-rules - 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. [Testing &amp; Quality](/categories/testing)
4. /
5. opencoreemr/openemr-phpstan-rules

ActivePhpstan-extension[Testing &amp; Quality](/categories/testing)

opencoreemr/openemr-phpstan-rules
=================================

PHPStan rules for OpenEMR core and module development

0.9.0(3mo ago)0965[2 PRs](https://github.com/openCoreEMR/openemr-phpstan-rules/pulls)1GPL-3.0-or-laterPHPPHP ^8.2CI passing

Since Jan 17Pushed 1mo agoCompare

[ Source](https://github.com/openCoreEMR/openemr-phpstan-rules)[ Packagist](https://packagist.org/packages/opencoreemr/openemr-phpstan-rules)[ RSS](/packages/opencoreemr-openemr-phpstan-rules/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (8)Versions (9)Used By (1)

PHPStan Rules for OpenEMR
=========================

[](#phpstan-rules-for-openemr)

Composer-installable PHPStan rules for OpenEMR core and module development. Enforces modern coding patterns and best practices.

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

[](#installation)

```
composer require --dev opencoreemr/openemr-phpstan-rules
```

The rules are automatically loaded via [phpstan/extension-installer](https://github.com/phpstan/extension-installer). No manual configuration needed.

**Important:** Do not manually include `extension.neon` in your phpstan configuration. The extension-installer handles this automatically. Adding a manual include will cause "File included multiple times" warnings.

Bundled Extensions
------------------

[](#bundled-extensions)

This package includes and configures these PHPStan extensions:

- **[spaze/phpstan-disallowed-calls](https://github.com/spaze/phpstan-disallowed-calls)** - Forbids legacy function calls
- **[phpstan/phpstan-deprecation-rules](https://github.com/phpstan/phpstan-deprecation-rules)** - Reports usage of deprecated code

Rules
-----

[](#rules)

### Why Custom Rules Instead of Just `@deprecated`?

[](#why-custom-rules-instead-of-just-deprecated)

This package provides custom rules that forbid specific functions by name (e.g., `sqlQuery()`, `call_user_func()`). You might wonder why we don't just mark these functions as `@deprecated` in OpenEMR and rely on `phpstan-deprecation-rules`.

**The reason: module analysis without OpenEMR loaded.**

When running PHPStan on a standalone OpenEMR module, OpenEMR core may not be installed as a dependency or autoloaded. PHPStan's deprecation rules require the actual function/class definitions to read `@deprecated` annotations. If OpenEMR isn't available at scan-time, those annotations can't be read.

Our custom rules work by **function name matching**, so they catch forbidden calls even when the function definitions aren't available. This ensures modules get the same static analysis protection whether they're analyzed standalone or within a full OpenEMR installation.

### Database Rules

[](#database-rules)

**Disallowed SQL Functions** (via spaze/phpstan-disallowed-calls)

- **Forbids:** Legacy `sql.inc.php` functions (`sqlQuery`, `sqlStatement`, `sqlInsert`, etc.)
- **Requires:** `QueryUtils` methods instead
- **Example:**```
    // ❌ Forbidden
    $result = sqlStatement($sql, $binds);

    // ✅ Required
    $records = QueryUtils::fetchRecords($sql, $binds);
    ```

**ForbiddenClassesRule**

- **Forbids:** Laminas-DB classes (`Laminas\Db\Adapter`, `Laminas\Db\Sql`, etc.)
- **Requires:** `QueryUtils` or `DatabaseQueryTrait`

### Globals Rules

[](#globals-rules)

**ForbiddenGlobalsAccessRule**

- **Forbids:** Direct `$GLOBALS` array access
- **Requires:** `OEGlobalsBag::getInstance()`
- **Example:**```
    // ❌ Forbidden
    $value = $GLOBALS['some_setting'];

    // ✅ Required
    $globals = OEGlobalsBag::getInstance();
    $value = $globals->get('some_setting');
    ```

### Testing Rules

[](#testing-rules)

**NoCoversAnnotationRule**

- **Forbids:** `@covers` annotations on test methods
- **Rationale:** Excludes transitively used code from coverage reports

**NoCoversAnnotationOnClassRule**

- **Forbids:** `@covers` annotations on test classes
- **Rationale:** Same as above - incomplete coverage tracking

### HTTP Rules

[](#http-rules)

**ForbiddenCurlFunctionsRule**

- **Forbids:** Raw `curl_*` functions (`curl_init`, `curl_exec`, `curl_setopt`, etc.)
- **Requires:** PSR-18 HTTP client
- **Example:**```
    // ❌ Forbidden
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);

    // ✅ Required - use a PSR-18 HTTP client
    $response = $httpClient->sendRequest($request);
    ```

### Legacy PHP Rules

[](#legacy-php-rules)

**Disallowed call\_user\_func** (via spaze/phpstan-disallowed-calls)

- **Forbids:** `call_user_func()` and `call_user_func_array()`
- **Requires:** First-class callables (PHP 8.1+)
- **Example:**```
    // ❌ Forbidden
    call_user_func([$object, 'method'], $arg1, $arg2);
    call_user_func_array('someFunction', $args);

    // ✅ Required - first-class callable syntax
    $callable = $object->method(...);
    $callable($arg1, $arg2);

    $callable = someFunction(...);
    $callable(...$args);

    // Static methods
    $callable = SomeClass::staticMethod(...);
    $callable($arg);
    ```

### Exception Handling Rules

[](#exception-handling-rules)

**CatchThrowableNotExceptionRule**

- **Forbids:** `catch (\Exception $e)`
- **Requires:** `catch (\Throwable $e)`
- **Rationale:** Catches both exceptions and errors (`TypeError`, `ParseError`, etc.)
- **Example:**```
    // ❌ Forbidden
    try {
        $service->doSomething();
    } catch (\Exception $e) {
        // Misses TypeError, ParseError, etc.
    }

    // ✅ Required
    try {
        $service->doSomething();
    } catch (\Throwable $e) {
        // Catches everything
    }
    ```

### Controller Rules

[](#controller-rules)

**NoSuperGlobalsInControllersRule**

- **Forbids:** `$_GET`, `$_POST`, `$_FILES`, `$_SERVER` in Controller classes
- **Requires:** Symfony `Request` object methods
- **Example:**```
    // ❌ Forbidden in controllers
    $name = $_POST['name'];
    $filter = $_GET['filter'];

    // ✅ Required
    $request = Request::createFromGlobals();
    $name = $request->request->get('name');
    $filter = $request->query->get('filter');
    ```

**NoLegacyResponseMethodsRule**

- **Forbids:** `header()`, `http_response_code()`, `die()`, `exit`, direct `echo` in controllers
- **Requires:** Symfony `Response` objects
- **Example:**```
    // ❌ Forbidden in controllers
    header('Location: /some/path');
    http_response_code(404);
    echo json_encode($data);
    die('Error');

    // ✅ Required
    return new RedirectResponse('/some/path');
    return new Response($content, 404);
    return new JsonResponse($data);
    throw new ModuleException('Error');
    ```

**ControllersMustReturnResponseRule**

- **Forbids:** Controller methods returning `void` or no return type
- **Requires:** Return type declaration of `Response` or subclass
- **Example:**```
    // ❌ Forbidden
    public function handleRequest(): void
    {
        // ...
    }

    // ✅ Required
    public function handleRequest(): Response
    {
        return new Response($content);
    }
    ```

Baselines
---------

[](#baselines)

If you're adding these rules to an existing codebase, generate a baseline to exclude existing violations:

```
vendor/bin/phpstan analyze --generate-baseline
```

New code will still be checked against all rules.

Development
-----------

[](#development)

### Running Tests

[](#running-tests)

```
composer install
vendor/bin/phpunit
```

License
-------

[](#license)

GNU General Public License v3.0 or later. See LICENSE

Authors
-------

[](#authors)

- Michael A. Smith

Links
-----

[](#links)

- [OpenEMR](https://www.open-emr.org)
- [OpenCoreEMR](https://opencoreemr.com)
- [PHPStan Documentation](https://phpstan.org)

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance85

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor2

2 contributors hold 50%+ of commits

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

2

Last Release

118d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5e3711ec05d536a75786ee9918fef942a50379ca1c5e241a3755c097153a6e89?d=identicon)[michael@opencoreemr.com](/maintainers/michael@opencoreemr.com)

---

Top Contributors

[![kojiromike](https://avatars.githubusercontent.com/u/1566303?v=4)](https://github.com/kojiromike "kojiromike (9 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (8 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (2 commits)")

---

Tags

PHPStanstatic analysiscode qualityopenemr

###  Code Quality

TestsPHPUnit

Static AnalysisRector

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/opencoreemr-openemr-phpstan-rules/health.svg)

```
[![Health](https://phpackages.com/badges/opencoreemr-openemr-phpstan-rules/health.svg)](https://phpackages.com/packages/opencoreemr-openemr-phpstan-rules)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[ekino/phpstan-banned-code

Detected banned code using PHPStan

2925.6M92](/packages/ekino-phpstan-banned-code)[staabm/phpstan-dba

2912.3M2](/packages/staabm-phpstan-dba)[shipmonk/dead-code-detector

Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.

3462.2M52](/packages/shipmonk-dead-code-detector)[szepeviktor/phpstan-wordpress

WordPress extensions for PHPStan

3257.8M898](/packages/szepeviktor-phpstan-wordpress)[shipmonk/phpstan-rules

Various extra strict PHPStan rules we found useful in ShipMonk.

1491.7M114](/packages/shipmonk-phpstan-rules)

PHPackages © 2026

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