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

Php Method Modifier Laravel Package

leovie/php-method-modifier

A small PHP utility for programmatically modifying method modifiers, making it easy to adjust method visibility and related characteristics at runtime. Useful for metaprogramming, testing, and code-generation workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a dev-time codemod tool, not a runtime library: it parses a PHP file with nikic/php-parser, mutates the AST node for one method, pretty-prints the result, and writes the file back. Nothing is loaded at runtime, so always install it as a dev dependency and never ship it in require.

composer require --dev leovie/php-method-modifier:^2.0

v2.0.0 is a platform bump, not a feature release. The only changes are a infection/infection dev-dependency bump and an upgrade to Symfony 6. Symfony 6 requires PHP >= 8.0.2, and it pulls Symfony 6 components into your dependency graph. That has two consequences before you type the command:

  • On PHP 7.4 you cannot install v2 at all — pin 1.0.1 instead, or run it in a throwaway directory.
  • On Laravel 8 or older (Symfony 5.x components) the install will conflict. Laravel 9/10 (Symfony 6.x) is fine.

First places to look, in order:

  1. vendor/leovie/php-method-modifier/src/ — the source is the documentation. grep -rn "public function" vendor/leovie/php-method-modifier/src gets you the real fluent surface in under a minute.
  2. composer.lock — check which nikic/php-parser major it resolved. v2 did not bump php-parser, so it still pins the v4 line; that pins everything else you can do.
  3. README.md — for the canonical entry point (a static factory or a MethodModifier instance) and whether it expects a class name, a file path, or a method node.

A representative first use case — instrumenting a service without hand-editing:

use LeoVie\PhpMethodModifier\MethodModifier;

MethodModifier::forMethod(App\Service\InvoiceService::class, 'send')
    ->addStatementAtBeginning('$this->logger->debug(__METHOD__);')
    ->write(); // or ->save(); — confirm the verb in src/

Before you run it for the first time:

git status --porcelain   # must be clean
cp -r src src.bak        # or just commit
vendor/bin/phpunit       # green baseline

Then run your one-off script, git diff, and review. The correct mental model is "a scripted sed that understands PHP syntax" — powerful, but the diff is the deliverable, not the call.

Implementation Patterns

One-shot codemod script, thrown away after review. The dominant pattern. Put it in tools/, run it once, inspect the diff, delete it or keep it in tools/codemods/ with a date prefix. Never wire it into the application bootstrap.

// tools/codemods/2024-06-add-tracing.php
require __DIR__ . '/../../vendor/autoload.php';

$targets = [
    App\Service\InvoiceService::class => ['send', 'void', 'refund'],
    App\Service\PaymentService::class => ['charge', 'authorize'],
];

foreach ($targets as $class => $methods) {
    foreach ($methods as $method) {
        MethodModifier::forMethod($class, $method)
            ->addStatementAtBeginning(
                'app(\App\Tracing\Tracer::class)->span(__METHOD__, function () {'
            )
            ->addStatementAtEnd('});')
            ->write();
    }
}

Loop over a discovered file set, not hand-listed classes. For anything touching more than a handful of classes, discover with Finder from symfony/finder and map FQCN by path, or use Composer's classmap. Hand-listing 60 classes is where these scripts rot.

Idempotency by marker comment. Because the modifier is a dumb AST edit, re-running duplicates statements. Guard with a sentinel:

$marker = '// @tracing-injected';
if (str_contains(file_get_contents($file), $marker)) {
    continue;
}

The sentinel comment also survives pretty-printing in most cases and makes the injected lines greppable and reviewable.

Pair with a formatter, always. Run vendor/bin/pint (or php-cs-fixer) immediately after the codemod run. The pretty printer re-emits the whole method, so whitespace and blank-line style will drift from the rest of the codebase; the formatter re-normalises it and shrinks your review diff to just the semantic changes.

Dry-run by writing to a temp copy. There is essentially no preview mode. Copy the file to scratch or a .new sibling, run the modifier against that, then diff -u. Only promote when the diff is what you wanted. This matters more than it sounds: a bad AST mutation can produce a file that still parses but has a wrong body.

Use it for scaffolding, not just refactoring. Realistic day-to-day wins:

  • Add an assert() / guard clause to the top of every public method in a legacy class.
  • Generate overrides: copy a parent method body into a subclass and prepend parent::method() handling.
  • Inject a try { ... } finally { $this->profiler->stop(); } wrapper before you do the manual version.
  • Bulk-add a deprecation trigger to methods listed in a deprecation plan.
  • Retrofit return $this; fluent returns onto a builder.

Version-pin it exactly. "leovie/php-method-modifier": "2.0.0", not ^2.0. v2.0.0 is a maintenance bump on a package with no dependents; there is no ecosystem to catch a breakage. Better still: commit the one-off script, not the dependency — delete the package after the codemod lands and keep the diff.

Cross-check against Rector before committing to this package. For "add a comment/attribute/statement to many methods", rector/rector with a custom RectorInterface does the same job with dry-run (--dry-run), diff output, and a maintained ecosystem. This package is a reasonable choice when you want a five-line script and nothing else; it's a poor choice when you want repeatability. If you're already running Rector in CI, write the rule there instead.

Gotchas and Tips

Two dependency walls now, not one. The v2 Symfony 6 bump means an install failure can come from either direction. Diagnose before and after:

composer why symfony/console   # v5.x on Laravel ≤8 → v2 will not install
composer why nikic/php-parser  # v5 vs the package's ^4 pin

If either conflicts, run the codemod in a throwaway directory with its own composer.json and point it at absolute file paths. That side-steps both problems and is the right move on any legacy Laravel app.

nikic/php-parser major-version conflict is still the number-one install failure. v2.0.0 did not touch it, so the package still pins the v4 line. If your project already uses php-parser v5 (common with modern Rector, PHPStan extensions, or phpdocumentor/reflection), Composer will either refuse to install or silently downgrade a dependency something else needs. Treat the v2 release as "same parser, newer PHP floor" and expect the same conflict you had on v1.

Formatting is not preserved. AST round-tripping through PrettyPrinter\Standard normalises indentation, blank lines, array formatting, and multi-line signatures. Your reviewer will see the entire method as changed even though you added one line. Always format afterwards, and consider reviewing with git diff --ignore-all-space.

Comments and docblocks are the fragile part. Comments attached to statements inside the body may be relocated or dropped depending on the node visitor and printer settings. If the method you're editing is heavily docblocked, snapshot the original into tests/fixtures/ and diff against it rather than trusting the output.

$ and \ escaping in injected code strings. You're passing PHP source as a PHP string. '$this->foo()' with single quotes is fine, but double-quoted strings will interpolate $this and mangle \App\Foo into a control character. Use heredoc/nowdoc for anything multi-line:

->addStatementAtBeginning(<<<'PHP'
$this->logger->debug(__METHOD__, ['id' => $this->id]);
PHP)

$this is context-dependent. Injecting $this->... into a method that turns out to be static, or into a trait, or into a closure body, produces code that parses fine and fatals at runtime. The tool has no type inference. Verify your method list — grep -n "static function" src/ — and run the test suite after the codemod, not just php -l.

No semantic awareness means no protection against the real world. It won't notice that the method is abstract, that it's in an interface (no body to modify), that it's already been edited, or that the FQCN resolves through a class alias. abstract/interface methods are the most common silent no-op — you get success and zero diff.

Windows / CRLF. If the repo is checked out with CRLF, the printer writes LF (or vice versa) and the diff is 100% of the file. Set core.autocrlf sensibly, or normalise with the formatter's line-ending config first.

Brace/write failures are silent. If the target file is read-only, the write() step can fail without a loud error depending on how the package uses file_put_contents. Assert on the change: git diff --quiet <file> && echo "NO CHANGE — investigate". Wrap the whole run in a shell script that fails loudly on an empty diff.

Don't run it on a dirty tree, and don't run it in CI as a gate. Because the output is not deterministic across php-parser versions, a "check that codemod produces no diff" CI step will flake on the first dependency bump. It's a development-time tool: local, reviewed, committed as a normal diff.

Debugging an unexpected result: reduce to a single file and a single method, print the modified source to stdout instead of writing (->getModifiedSource() or similar — confirm the getter name in src/), and php -l the output. If the printer output looks mangled, it's usually an unescaped string in your injected code, not a bug in the package.

Extension points are thin, so plan to drop it. The library is a wrapper around php-parser; when you need conditional logic, multiple edits per method, or per-node decisions, you'll outgrow the fluent API quickly and should write the ~80-line NodeVisitor yourself. v2.0.0 adds no new API surface — it only raises the PHP floor and refreshes a dev dependency — so every extension-point limitation from v1 carries over unchanged. Treat this package as a fast path for the first one or two codemods, and as a reference implementation for the third.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib