Hidden Gems of PHP Packages: Symfony\Finder and SplFileInfo

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.

Why I Use it?

  1. 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) {}
  2. 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
  3. 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
  4. 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.

How to Install

composer require symfony/finder

How I Use it

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

Honorable Mentions

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!


  Travis Knowns the Code Works

The code used in this post is tested daily with Travis CI. You can see tests on Github.

Thanks to tests this post:

  • always run against the most recent dependencies
  • gets updates and stays relevant for many years even when new major version of PHP or Symfony is released

Typo? Fix it, please  and join 49 people who build this website