The series on not-so-well-known packages that might save your ass more than you think continues. Today we look on files as objects.
Do you work with files in 5 different places in your application in a single way and you miss consistent naming?
<?php declare(strict_types=1);
function processFile(string $file) {
// is absolute?
// relative to what?
}
function processFile(string $fileAbsolute) {}
function processFile(string $filePath) {}
function processFile(string $absoluteFile) {}
function processFile(string $relativePath) {}
function processFile(string $filename) {}
Do you need to test file paths on various CI machines?
Expected test string didn't match:
-Error was found in: /home/im-very-cool-guy/my-website/www/my-home-porn-web/public/index.php
+Error was found in: /travic-ci/travis-directory-for-this-web/public/index.php
Do you want to report the user the relative path instead of hard to read absolute one?
-Error was found in: /home/im-very-cool-guy/my-website/www/my-home-porn-web/public/index.php
+Error was found in: public/index.php
Do you want to forget all those file_*
functions and just work with the file instead?
<?php declare(strict_types=1);
file_exists($file);
is_file($file);
is_directory($file); // actually: is_dir($file);
is_readable($file);
is_absolute($file); // well, you have to create this one yourself, and don't forget the Windows and Linux differences!
Thanks to Symfony\Finder and its custom SplFileInfo
I can be lazy, use object API and work safer and faster in all these cases above and more. Actually, I started to using SplFileInfo
over stringly file paths/names after too many bugs appeared in my code and I'm happier and more relaxed ever since.
What is splFileInfo? It's native object in PHP - like DateTime
- that wraps the file and provides nice object API for it. The Symfony\Finder package adds 3 extra methods that make work with files just a bit smoother. They go very well together since the package creates all the splFileInfo
instances for you.
composer require symfony/finder
We'll find all available feature in the documentation, but basic usage it like this:
<?php declare(strict_types=1);
use Symfony\Component\Finder\Finder;
$finder = Finder::create()
->files()
->in(__DIR__)
->name('composer.json')
->getIterator();
foreach ($finder as $splFileInfo) {
var_dump($splFileInfo); // instance of "Symfony\Component\Finder\SplFileInfo"
}
Symfony\Component\Finder\Finder
name()
This method accepts also regular expressions. Do you want to find all YAML files?
<?php declare(strict_types=1);
use Symfony\Component\Finder\Finder;
$finder = Finder::create();
$finder->name('#\.(yaml|yml)$#');
append()
Do you want to add just a single file that finder criteria would not find? Normally, you'd have to create SplFileInfo
manually, think of relative/absolute paths etc. So much work. Instead, you can just append it and Finder will add it for you.
<?php declare(strict_types=1);
use Symfony\Component\Finder\Finder;
return Finder::create()
->name('#\.php$#')
->in(__DIR__ . '/Source')
->append([__DIR__ . '/Source/SomeClass.twig']);
notPath()
or exclude()
It's common and bad practice to put tests files into /src
. It's historical reason mostly, but we still have to deal with that.
You don't want to work with 3rd party code tests, right?
<?php declare(strict_types=1);
use Symfony\Component\Finder\Finder;
$finder = Finder::create()
// directories
->exclude('spec')
->exclude('test')
->exclude('Tests')
->exclude('tests')
->exclude('Behat')
->name('*.php');
// or match path name
$finder->notPath('#tests#');
Symfony\Component\Finder\SplFileInfo
getRelativePath()
Let's get back to the composer.json
example above (from in MonorepoBuilder). This is how we get relative directory:
<?php declare(strict_types=1);
/** @var \Symfony\Component\Finder\SplFileInfo $splFileInfo */
$splFileInfo->getRelativePath(); // "/"
getRelativePathname()
This method is bit different - it returns relative filename:
<?php declare(strict_types=1);
/** @var \Symfony\Component\Finder\SplFileInfo $splFileInfo */
$splFileInfo->getRelativePath(); // "composer.json"
This is very handy for output reporting in PHP CLI Apps like ECS, PHP CS Fixer, PHP_CodeSniffer or PHPStan. Compare yourself - the computer absolute scope:
An error was found in /home/im-very-cool-guy/my-website/www/my-home-porn-web/public/index.php
and human relative scope:
An error was found in public/index.php
There are few more methods that I use from time to time:
<?php declare(strict_types=1);
/** @var \Symfony\Component\Finder\SplFileInfo $splFileInfo */
$splFileInfo->getRealPath();
// absolute path - returns "/var/www/this-post/composer.json"
$splFileInfo->getContents();
// gets the content of file with error propagated to an exception - very nice!
$splFileInfo->getBasename('.' . $splFileInfo->getExtension());
// returns "composer"
Do you like it? Go and give Symfony\Finder or at least SplFileInfo
a try.
Happy coding!
Do you learn from my contents or use open-source packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!