I'm trying to write a bash script that adds a line into a php function like this:
public function register()
{
$this->app->bind('App\Repositories\Corporate\CorporateContract',
'App\Repositories\Corporate\EloquentCorporateRepository');
}
Here's my code:
function bindContractToRepository {
sed -i -e '/register()
/a \
\t\t'${repoBinding}'
\t\t\t\t ' ./app/Providers/${repoName}${provider}.php 2> /dev/null
}
bindContractToRepository
I actually want my code to come inside the function itself like the example at the top.
NB. I can't specify a particular line because the line number varies with different version
In general it is very difficult to detect PHP function boundaries without a parser.
We can do the parser's job in Bash: iterate the code line by line, solve the opening and closing braces etc. until we get the range of lines covered by this function and, finally, replace it with new content. We can do the same using the sed
commands (in this case will likely look less readable than an assembly code).
But we already have a PHP parser! Why not use it?
The following PHP CLI script accepts:
replace-method.php
<?php
function usage($error = false) {
global $argv;
$str = <<<EOS
USAGE: php {$argv[0]} OPTIONS
OPTIONS:
-h, --help Print help message
-i, --input-file Input PHP file
-m, --method PHP function/method name, e.g.:
my_func, MyClass::myMethod
-c, --code The new PHP code including the function/method declaration.
String of code, or dash ('-') meaning the standard input.
EXAMPLE:
php {$argv[0]} -i source/file.php -m 'MyClass::register' -c - <<ENDOFPHP
public function register()
{
echo time();
}
ENDOFPHP
Replaces the code of 'MyClass::register' method with new code from the standard input
in source/file.php.
EOS;
fprintf($error ? STDERR : STDOUT, $str);
exit((int)!$error);
}
if (false === ($opt = getopt('hi:m:c:', ['help', 'input-file:', 'method:', 'code:']))) {
fprintf(STDERR, "Failed to parse options
");
usage(true);
}
if (isset($opt['h']) || isset($opt['help']))
usage();
// Using PHP7 Null coalescing operator
$file = $opt['i'] ?? $opt['input-file'] ?? null;
if (!file_exists($file)) {
fprintf(STDERR, "File '$file' does not exist
");
usage(true);
}
$new_code = $opt['c'] ?? $opt['code'] ?? null;
if (!$new_code) {
fprintf(STDERR, "Code option expected
");
usage(true);
}
if ($new_code == '-')
$new_code = file_get_contents('php://stdin');
$method = $opt['m'] ?? $opt['method'] ?? null;
if (!$method) {
fprintf(STDERR, "Method option expected
");
usage(true);
}
// You most likely want to include project's autoloading file instead.
// (You might accept it as a CLI argument, too.)
require_once $file;
$rf = strpos($method, '::') ?
new ReflectionMethod($method) :
new ReflectionFunction($method);
$start_line = $rf->getStartLine();
$end_line = $rf->getEndLine();
$lines = file($file);
$code = implode(array_slice($lines, 0, $start_line - 1)) .
$new_code . implode(array_slice($lines, $end_line));
file_put_contents($file, $code);
Suppose we have path/to/file.php
with A
class and its register
method:
<?php
class A
{
public function register()
{
echo 'aaaa';
}
}
We can replace the code of A::register
method in a Bash as follows:
php replace-method.php -i path/to/file.php -m 'A::register' -c - <<ENDOFPHP
public function register()
{
echo 'something new';
}
ENDOFPHP
I've used Bash here document for input. You can use any kind of the shell redirection, for instance:
generate_php_code | php 1.php -i file.php -m 'A::register' -c -