Programatically manipulate PHP
/ Laravel
files on disk with an intuiutive, fluent API. Features include File- and Code/AST QueryBuilders, an inline PHP Template engine and categorization of read/write operations in Resource
endpoints.
composer require ajthinking/php-file-manipulator
php artisan vendor:publish --provider="PHPFileManipulator\ServiceProvider"
use PHPFile;
use LaravelFile;
// find files with the query builder
PHPFile::in('database/migrations')
->where('extends', 'Migration')
->andWhere('className', 'like', 'Create')
->get()
->each(function($file) {
// Do something
$file->add()->use(['Database\CustomMigration'])
->extends('Database\CustomMigration')
->save();
});
// add relationship methods
LaravelFile::load('app/User.php')
->hasMany(['App\Car'])
->hasOne(['App\Life'])
->belongsTo(['App\Wife'])
->methodNames()
// move User.php to a Models directory
PHPFile::load('app/User.php')
->namespace('App\Models')
->move('app/Models/User.php')
// install a package trait
PHPFile::load('app/User.php')
->addUseStatements('Package\Tool')
->addTraitUseStatement('Tool')
->save()
// add a route
LaravelFile::load('routes/web.php')
->addRoute('dummy', 'Controller@method')
->save()
// debug will write result relative to storage/.debug
LaravelFile::load('app/User.php')
->setClassName('Mistake')
->debug()
// add items to protected properties
LaravelFile::load('app/User.php')
->add()->fillable('message')
->add()->casts(['is_admin' => 'boolean'])
->add()->hidden('secret')
// create new files from templates
LaravelFile::model('Beer')
->save()
LaravelFile::controller('BeerController')
->save()
// many in one go
LaravelFile::create('Beer', ['model', 'controller', 'migration'])
Let's make a snippet for a method we want to insert. Start by creating a file storage/php-file-manipulator/snippets/my-stuff.php
like shown below. In the file, we put our template code including any encapsuling constructs (in our case we will have to put a class since methods only exists inside classes). Name anything you want to be configurable with a handle for instance '___TARGET_CLASS___'
. Even your snippet name itself may be a handle as long as it is unique.
<?php
/**
* Optionally use FAKE names to silence IDE warnings
*/
use PHPFileManipulator\Support\FakeName;
use PHPFileManipulator\Support\FakeName as ANY;
use PHPFileManipulator\Support\FakeName as ___TARGET_CLASS___;
/**
* This is just a placeholder class where we can add our snippets
*/
class _ extends FakeName
{
/**
* ___DOC_BLOCK___
*/
public function mySpecialMethod($arg)
{
$want = abs($arg);
return $this->doSomethingWith(___TARGET_CLASS___::class, 'my template')
->use(ANY::thing(new static('you' . $want)));
}
}
Your snippet is then instantly available anywhere in your code:
use PHPFileManipulator\Support\Snippet;
// Get the snippet
Snippet::mySpecialMethod()
// Pass an array of replacement pairs to replace any handles:
Snippet::mySpecialMethod([
'___DOC_BLOCK___' => 'Inserted with php-file-manipulator :)',
'___TARGET_CLASS___' => 'App\Rocket'
]);
// Integrated example
PHPFile::load('app/User.php')
->addMethod(
Snippet::mySpecialMethod([
// replacement pairs ...
])
)->save();
ℹ️ The
Snippet
class currently only supports templates on class methods.
As seen in the previous examples we can query and manipulate nodes with simple or primitive values, such as strings and arrays. However, if we want to perform custom or more in dept queries we must use the ASTQueryBuilder
.
Example: how can we fetch explicit column names in a migration file?
LaravelFile::load('database/migrations/2014_10_12_000000_create_users_table.php')
->astQuery() // get a ASTQueryBuilder
->method()
->named('up')
->staticCall()
->where('class', 'Schema')
->named('create')
->args
->closure()
->stmts
->methodCall()
->where('var->name', 'table')
->args
->value
->value
->get(); // exit ASTQueryBuilder, get a Collection
The ASTQueryBuilder examines all possible paths and automatically terminates those that cant complete the query:
- Three kinds of methods are provided (hinted with indentation in the code example)
- Traversing (
methods
,staticCalls
,firstArg
...) - Filtering (
named
,whereClass
...) - Resolving (
getValue
)
- Traversing (
- The ASTQueryBuilder relies entirely on nikic/php-parser. To understand this syntax better tinker with
dd($file->ast()
.
To list all the available methods A-Z on LaravelFile
, run:
php artisan file:api
To group methods by EndpointProvider
use the --group
flag:
php artisan file:api --group
Use the --provider
flag to only view methods from a specific EndpointProvider
, for instance IO
:
php artisan file:api --provider=IO
If a file can't be parsed, a FileParseError
will be thrown. This can happen if you try to explicitly load the file but also when performing queries matching problematic files.
To see all offending files run php artisan file:errors
. To ignore files with problems, put them in config/php-file-manipulator.php
-> ignored_paths
.
In general this package assumes code to be parsed follows guidellines and conventions from PSR and Laravel. Some examples are listed below.
-
Can't use group use syntax (
use Something\{X, Y};
) -
Assumes one class per file
-
Assumes no multiple/grouped property declarations (
protected $a, $b = 1;
)
The test suite requires that you are inside laravel application
laravel new host
cd host
git clone [email protected]:ajthinking/php-file-manipulator.git packages/Ajthinking/PHPFileManipulator
Add this to the host projects composer.json
"repositories": [
{
"type": "path",
"url": "/PATH/TO/PROJECTS/host/packages/Ajthinking/PHPFileManipulator"
}
],
Then,
composer require ajthinking/php-file-manipulator @dev
php artisan vendor:publish --provider="PHPFileManipulator\ServiceProvider"
Finally in host root run
vendor/phpunit/phpunit/phpunit packages/Ajthinking/PHPFileManipulator/tests
PRs and issues are welcome. Have a look at the Trello board for planned features.
MIT
- Built with nikic/php-parser
- PSR Printing fixes borrowed from tcopestake/PHP-Parser-PSR-2-pretty-printer