Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Canary Laravel Package

stickee/canary

Canary is a Laravel package for feature flags and gradual rollouts. Define features in config, enable them for users, groups, or a percentage of traffic, and use simple runtime checks to ship safely and roll back quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Canary is a dev-only quality toolchain wrapper for Stickee Laravel projects: it bundles the linting/static-analysis stack (style fixer, PHPStan/Larastan, and friends) behind a single command so every project runs the same checks with the same rules. Treat it as a convention layer over tools you already know, not as a new analyser.

The current line is v4.x (latest at time of writing: v4.0.1).

1. Install as a dev dependency

composer require --dev stickee/canary:^4.0

Do not install it in require — it has no runtime role, and pulling analysis tools into production autoload is a needless attack surface. Pin the major version explicitly; see the upgrade note below before assuming a patch bump is inert.

2. Find the entry point before running anything

Because the package is small and internal (1 star, 0 dependents), spend five minutes reading the source rather than guessing at flags:

ls vendor/stickee/canary
cat vendor/stickee/canary/composer.json   # bin entries, required PHP, scripts
ls vendor/stickee/canary/src/Console     # actual command names and options
ls vendor/stickee/canary/config          # publishable config, if any

The bin section of that composer.json tells you the real binary name (vendor/bin/canary or similar). The require section tells you which underlying tools and versions you are implicitly adopting — that is the single most important thing to know about this package.

3. Publish and commit the config, if one ships

php artisan vendor:publish --provider="Stickee\Canary\CanaryServiceProvider" --tag=canary

If no config file appears, the rules are baked into the package. In that case record the pinned version in composer.json and note in your README that upgrading Canary may change your lint results.

4. First use case: run it on a clean branch

git checkout -b chore/canary
vendor/bin/canary          # or: vendor/bin/canary lint / vendor/bin/canary analyse

Run it before wiring it into CI. On an existing codebase the first run will almost certainly produce a wall of findings; you want to see the shape of that output before it starts blocking merges. Commit any auto-fixes as their own isolated commit so the diff is reviewable and revertible.

5. Then wire it into composer.json so it is discoverable

"scripts": {
    "lint": "canary lint",
    "analyse": "canary analyse",
    "check": ["@lint", "@analyse"]
}

Now composer check is the thing developers type, and the tool name is an implementation detail.

Implementation Patterns

Split "fix" from "check". Almost every wrapper like this exposes a fix mode and a check-only mode. Use fix mode locally and check mode in CI. Concretely:

"scripts": {
    "lint":       "canary lint --fix",
    "lint:check": "canary lint --check",
    "check":      ["@lint:check", "@analyse"]
}

Verify the exact flag names from vendor/bin/canary --help; the pattern matters more than the spelling.

Two-tier adoption for legacy code. Do not try to make a five-year-old codebase pass on day one. Generate a baseline for the analyser and exclude only genuinely generated paths:

# phpstan.neon (or whatever Canary delegates to)
includes:
    - vendor/stickee/canary/extension.neon   # if it ships one
parameters:
    baseline: phpstan-baseline.neon
    paths:
        - app
        - domain
    excludePaths:
        - app/Console/Kernel.php

Ship the baseline as a committed artefact and add a rule to your PR template: new code must not add baseline entries without a comment explaining why. Otherwise the baseline becomes a graveyard and the tool stops meaning anything.

CI as a single job, not five. One job, one cache key, one failure to read:

- uses: shivammathur/setup-php@v2
  with:
    php-version: '8.3'
    tools: composer:v2
    coverage: none

- uses: actions/cache@v4
  with:
    path: ~/.composer/cache
    key: composer-${{ hashFiles('composer.lock') }}

- run: composer install --prefer-dist --no-interaction --no-progress
- run: composer check

Keep composer.lock committed and install with --prefer-dist. Canary's results depend on the transitive versions of the underlying analysers; resolving fresh in CI means a green build today and a red build tomorrow with no code change.

Pre-commit hook for fast feedback, CI for the gate. A Husky/CaptainHook pre-commit that runs only the style fixer on staged files is worth it; running full static analysis on every commit is not.

"config": { "hooks": { "pre-commit": ["composer lint"] } }

For staged-file-only runs, check whether Canary accepts a path argument or a --diff flag. If it does not, run the underlying tool directly in the hook and let Canary own the CI gate. Mixing the two without knowing the diff semantics is how you get hooks that silently check nothing.

Per-environment strictness. Local runs should be forgiving (warnings, not failures) and CI strict. If Canary reads config from a published file, branch it:

// config/canary.php
return [
    'fail_on' => env('CANARY_FAIL_ON', app()->environment('testing') ? 'error' : 'never'),
];

Extend rather than fork. If you need an extra rule, register it through the underlying tool's own extension mechanism (a PHPStan extension, a Pint preset, a PHPCS ruleset XML) and point Canary at it via config. Forking an internal one-star package means you now own the maintenance of the fork, and the next upstream release will conflict.

Version-pin deliberately, and treat patch bumps as potentially behavioural. Because the wrapper hides several tools, a bump can change your whole result set — v4.0.1 is a patch release that only updated the README and required an updated Larastan config, which is exactly the kind of change that can add or remove findings without a feature flag. The workflow:

composer require --dev stickee/canary:^4.0 --update-with-dependencies
composer check   # always run immediately after, in the same commit

Upgrade Canary in its own PR with any resulting fix commit attached. Never bundle a Canary bump into a feature branch — a patch bump is not automatically a no-op.

Gotchas and Tips

This is a small internal package with near-zero public adoption. One star and zero dependents means: no community Stack Overflow answers, no third-party blog posts, and a real bus-factor risk. It is actively released (v4.0.1 landed with two first-time contributors, so maintenance is not stalled), but the knowledge lives in the repo, not the ecosystem. Two mitigations, both cheap: (a) pin a known-good version in composer.lock and upgrade on your schedule, not composer update's; (b) keep a one-paragraph docs/linting.md in your repo describing what composer check runs and which underlying tool owns which error code. If Canary is abandoned, you delete the dependency and keep the config.

Patch releases can change the rules underneath you. v4.0.1's only substantive change was "require updated Larastan config" — no new flag, no changelog entry describing user-visible behaviour. That means a green build can go red on a composer update with no code change on your side. Always run composer check in the same commit as the bump, and read the diff of the underlying config file if the results move.

Silent no-ops are the main failure mode. A wrapper that finds no files to analyse exits 0 and looks identical to a clean pass. After wiring it into CI, deliberately introduce a violation (an unused import) and confirm the build goes red. Do this once per project, and again after any Canary major/minor bump. It is the highest-value five minutes you will spend on this integration.

Check the require block, not the README. The version of PHPStan/Larastan that Canary pulls in determines your PHP compatibility ceiling. If Canary pins PHPStan 1.x, you will not get 2.x features and you may hit conflicts when your app requires a newer one. Inspect with:

composer why phpstan/phpstan
composer show stickee/canary --tree

Config precedence. When a published config/canary.php exists, it usually wins over the package defaults — but paths may still resolve relative to the package directory rather than your base path. If you point it at a custom ruleset and get "file not found", use base_path() or __DIR__ explicitly and verify with php artisan config:show canary (clear the config cache first: php artisan config:clear).

Analysis order matters. Style fixers rewrite files. If your pipeline runs analyse before lint --fix, you analyse code that is about to change, and CI and local results diverge in confusing ways. Always fix, then analyse. If Canary runs them in one command, confirm the ordering in the source before trusting it.

Memory and time. Static analysis on a large Laravel app will blow past PHP's default memory limit. Set memory_limit=-1 for the analysis process (via the script, not globally) and expect the first cold run to take minutes. Cache aggressively — most analysers support a tmpDir/cacheDir; point it at a path your CI caches between runs.

Parallelism can mask errors. If the wrapper fans out across processes, interleaved output is easy to misread, and a worker crash can look like a clean result. Use the verbose flag (-v/-vvv) when debugging and consider forcing single-process mode to confirm the "real" result before trusting the fast one.

Exit codes. Confirm what a non-zero exit means — some wrappers return the count of findings, some return 1, some return 2 for internal errors. In CI, distinguish "code is bad" from "tool is broken"; the second should page someone, the first should not.

Debugging a surprising finding. Drop to the underlying tool with the same config to isolate whether the problem is the rule or the wrapper:

vendor/bin/phpstan analyse --configuration=phpstan.neon -vvv
vendor/bin/pint --test -v

If the raw tool agrees with Canary, the rule is doing its job. If it disagrees, the config is being loaded from an unexpected location — a genuinely common bug with wrapper packages, and the likeliest cause of a sudden result change after a config-only patch release.

Extension points worth knowing. Look for: a published config file (rules, paths, excludes), the ability to pass a custom ruleset/preset name, and any service-provider bindings that let you swap the runner. If none of those exist, the escape hatch is per-path ignores in the underlying tool's own config file — always available, never blocked by the wrapper.

Do not let it block local commits on a dirty tree. If pre-commit hooks run full analysis, developers will --no-verify and you lose the gate entirely. Keep hooks to style fixes on staged files only.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package