phpmnd: a PHP static analyser that flags numeric literals hiding in your code
PHP Magic Number Detector
At a glance
- What is it?
- phpmnd detects magic numbers in PHP source, and its design is narrower than most static analysis tools because it only looks at numeric literals and only in the places you tell it to. The extensions model is the key to using it without drowning in false positives.
- Who is it for?
- phpmnd fits a PHP team that has decided to name its constants and wants a quick audit of where the literals are, especially as a one-off scan of a legacy codebase or a check in a pull request pipeline. It is the wrong tool for a codebase that legitimately uses numeric literals, because the default extension set is narrow but the full extension set is noisy, and the README's own advice is to clean up with the defaults before adding more.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The rule of thumb is business logic, not mathematics
The README gives a plain definition first. A magic number is a numeric literal that is not defined as a constant, but which may change at a later stage, and therefore can be hard to update, and using numbers directly in source code without an explanation is considered a bad programming practice because it makes programs harder to read, understand, and maintain. But it also says not every literal number is a magic number, giving the example of a modulo check where the number 2 is obviously fine. The author's rule of thumb closes the loop: if the number came from business specs and is used directly, it is a magic number. That distinction matters for the tool, because it is the difference between a lint that a team adopts and a lint that a team disables on day two. A number in a mathematical expression is a fact about arithmetic. A number in a password length check is a decision someone made.
The worked example is a password length check
The README shows the transformation the tool is meant to drive. The first version is the anti-pattern, a class with a `setPassword` method that throws when `mb_strlen($password) > 7`. The refactored version adds a class constant:
class Foo
{
const MAX_PASSWORD_LENGTH = 7; // not const SEVEN = 7 :)
public function setPassword($password)
{
if (mb_strlen($password) > self::MAX_PASSWORD_LENGTH) {
throw new InvalidArgumentException("password");
}
}
}The comment in that snippet is worth pausing on, because it is a small piece of advocacy. Naming a constant `SEVEN` tells a reader nothing about intent, while `MAX_PASSWORD_LENGTH` says what the number is for and what it constrains. The README's claim is that this clearly improves code readability and also reduces its maintenance cost, and the second half is the practical argument. When the policy changes, there is one place to change it. That is the whole case for the tool.
Installation is one Composer command, local or global
There are two routes, and the choice is about where the tool lives. As a local, per-project development dependency, the README gives the Composer command:
$ composer require --dev povils/phpmndand then you invoke it with the `vendor/bin/phpmnd` executable. For a global install:
$ composer global require povils/phpmndafter which you need the global Composer binaries directory on your `PATH`, and the README shows the shape for Unix systems:
$ export PATH="$PATH:$HOME/.composer/vendor/bin"The minimum PHP version is 7.4.0 according to the badge in the README, and the package is published on Packagist as `povils/phpmnd`. The local install is the one that fits a team, because the version is then locked in `composer.json` and every developer and CI run uses the same one. The global install is for a developer who wants to point the tool at directories that are not projects, which is how you would audit a legacy codebase you are about to take over.
The default is deliberately narrow: conditions, returns, and switch cases
By default it analyses conditions, return statements, and switch cases. That is a much smaller surface than you might expect from a magic number detector, and it is the reason the tool can be adopted without a mass of false positives. The other extensions exist and are opt-in. The argument extension covers numeric literals passed as function arguments, the array extension covers literals in array literals, assign covers plain assignments, default_parameter covers function parameter defaults, operation covers literals inside arithmetic expressions, and property covers class property defaults. There is also an all extension to include everything. Each extension is documented with a one line PHP example, which makes the choice concrete rather than abstract. The removal syntax is worth knowing too. If extensions start with a minus, they are removed from the analysis, and the README recommends cleaning up code with the default extension before using any of the others.
Options for excluding the cases you have already decided about
The option set is where a team tunes the tool to its own conventions. The `--exclude` option excludes a directory relative to the source, and multiple values are allowed, with `--exclude=tests --exclude=examples` given as the example. The `--exclude-path` option excludes a path relative to the source, also with multiple values, and `--exclude-file` excludes a single file from the analysis, again with multiple values allowed. The `--ignore-numbers` option takes a comma separated list of numbers to exclude. The `--ignore-funcs` option excludes a list of comma separated functions when using the argument extension, and it defaults to `intval`, `floatval`, and `strval`. The `--ignore-strings` option excludes strings when using the strings option, and `--include-numeric-string` forces numeric strings such as "1234" to be treated as a number. The `--strings` option includes string literal search in the analysis, and `--suffixes` configures a comma separated list of valid source file extensions.
intval is the escape hatch for numbers that must stay
There is a small but genuinely useful trick in the README, and it is the answer to the case where a magic number is required. The example is a percentage calculation, `$percent = $number / 100`, which would show 100 as a magic number. Wrapping it, `$percent = $number / intval(100);`, marks 100 as not magic. This works because `intval`, `floatval`, and `strval` are in the default ignore list for the argument extension. It is a small piece of ceremony in exchange for a specific outcome, and it is the kind of thing that should be a deliberate decision rather than a habit, since the same trick would be wrong to apply to a business rule. The `--ignore-funcs` option lets you configure that list, and the `--hint` option goes further by suggesting replacements for magic numbers based on the constants already present in your codebase, which is the most directly useful option for a codebase that already has a naming convention.
Output options matter if you plan to run this in CI
Three options decide what the run produces. The `--progress` option displays a progress bar, which matters when you point the tool at a large tree. The `--xml-output` option generates a report in XML format to the path specified, which is the format a CI system can archive or diff. The `--whitelist` option only processes the files listed in the file specified, and the README describes this as useful for incremental analysis, so a second run after a pull request can look only at the files that changed. The `--allow-array-mapping` option allows keys as strings when using the array extension, which is a concession to code that uses string keys where an integer key was intended. A representative invocation from the README combines several of these:
$ phpmnd wordpress --ignore-numbers=2,-1 --ignore-funcs=round,sleep --exclude=tests --progress \
--extensions=default_parameter,-return,argumentThe extension list there both adds `default_parameter` and `argument` and removes `return`, which is the syntax in action: the minus prefix removes a default extension while the plain names add others.
Editorial conclusion
phpmnd fits a PHP team that has decided to name its constants and wants a quick audit of where the literals are, especially as a one-off scan of a legacy codebase or a check in a pull request pipeline. It is the wrong tool for a codebase that legitimately uses numeric literals, because the default extension set is narrow but the full extension set is noisy, and the README's own advice is to clean up with the defaults before adding more. Before you wire it into CI, run it once with the default extensions over one directory and read the output, because the number of findings tells you whether your team actually wants constants before you make the tool a build failure.
Frequently asked questions
What counts as a magic number in phpmnd?
A numeric literal that is not defined as a constant and may change later. The README adds that by default 0 and 1 are not considered magic numbers, and gives the rule of thumb that if the number came from business specs and is used directly, it is a magic number.
How do I install phpmnd?
Locally as a development dependency with `composer require --dev povils/phpmnd`, invoked afterwards through `vendor/bin/phpmnd`, or globally with `composer global require povils/phpmnd`, in which case you need the global Composer binaries directory on your PATH.
Which code does phpmnd analyse by default?
By default it analyses conditions, return statements, and switch cases. Other extensions cover arguments, arrays, assignments, default parameters, operations, and property defaults, and you can add or remove them with the `--extensions` option, where a leading minus removes an extension.
How do I stop phpmnd reporting a number I cannot change?
The `--ignore-numbers` option takes a comma separated list of numbers to exclude, and the `--ignore-funcs` option excludes functions, defaulting to `intval`, `floatval` and `strval`. Wrapping a literal in `intval(100)` also marks it as not magic, which the README shows for a percentage calculation.
Can phpmnd exclude directories and produce a machine readable report?
Yes. The `--exclude`, `--exclude-path` and `--exclude-file` options skip directories, paths, and individual files relative to the source, and the `--xml-output` option generates a report in XML format to the path you specify. The `--whitelist` option limits processing to the files listed in a given file, which the README notes is useful for incremental analysis.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/povils-phpmnd)Was this useful?