Hidden Gems of PHP Packages: Nette\Utils

In this series, I will show you not-so-known PHP packages, that I happily use in my daily workflow. They're hard to describe in few words for their various features, but awesome and simple to use.

Today we start with Nette\Utils package.

Why I Use it

  1. Do you know how is called the PHP function that checks the presence of a string in another string?

  2. Do you know what is the difference between those 3 calls?

$contains = strpos('content', $lookFor) === 0;
$contains = strpos('content', $lookFor) == 0;
$contains = strpos('content', $lookFor) === false;

If you can tell the difference under 1 s, good job!


I can't. I have to think hard to remember what was that danger WTF of strpos() function...

...that makes this code run into condition, even though you don't want to:

$contains = strpos('content', $lookFor);
if ($contains) {
   // ... should I be here?
}
  1. Do you prefer exceptions over false? Do you prefer exceptions over learning various errors codes in PHP native functions like file_get_contents() or preg_replace?
  1. Do you prefer thinking less about PHP language details and doing more effective work while being safe?

  2. Do you prefer PHPStan not reporting forgotten validation of bool|string return type?

if (file_exists($filePath)) {
    // can return `false|string`
    $fileContent = file_get_contents($filePath);
}

Thanks to Nette\Utils I can be lazy, safe and much faster in all these cases above and more.

How to Install

composer require nette/utils

How I Use it

We can look at the Nette\Utils documentation to find 12 classes with many methods and study their API... and leave the page overwhelmed by details and over-bored.

Instead, we can look at real-life examples in Symplify code. That's much more interesting and relevant, right? When we use Github search, we see that Symplify packages use Nette\Utils package at 100+ places. What are these cases?

Strings class

replace()

It accepts content, pattern to look for and replacement. This is basic building stone for packages like LatteToTwigConverter:

// in Latte: {var $var = $anotherVar}
// in Twig: {% set var = anotherVar %}
$content = Nette\Utils\Strings::replace($content, '#{var \$?(.*?) = \$?(.*?)}#s', '{% set $1 = $2 %}');

It's nice that you don't have to deal with PHP native edge-cases of regular expressions. I think regulars are difficult enough to work with, so this piece comes very handy.

contains()

It accepts the content and the string we look for:

if (Nette\Utils\Strings::contains($key, '.')) {
    // is a code
    $this->skippedCodes[$key] = $settings;
} else {
    // is a class
    $this->skipped[$key] = $settings;
}

startsWith()

How to detect a nullable type?

# < 1 s of thinking
return Nette\Utils\Strings::startsWith($type, '?');
# > 1 s of thinking
return strpos('Content', $lookingFor) === ?;
# or was it this one?
return strpos('Content', $lookingFor) === strlen($lookingFor);

endsWith()

if (Strings::endsWith($class, 'Interface')) {
    // is interface
}

FileSystem class

read()

If we know the file will be there (e.g. it's convention or we just put it there), we can use file_get_contents().

$content = file_get_contents($accidentallyMissingFile);
// $content is `FALSE`

But we find out 35 lines later in this method:

private function processContent(string $content)
{
    // ...
}

But all we see is $content should be string, bool passed error. Just great, isn't it?

Of course, you can put file_exists() and is_file() validation everywhere and teach every programmer in your team to use them and also create a sniff, that will enforce this behavior in CI level (which nobody really does) and spend hundreds of hours on regression bugs or...

...you could use helper method and make yourself useful instead:

Nette\Utils\FileSystem::read($accidentallyMissingFile);

✅ Kaboom! An Exception!

createDir()

Do you want to create a directory for your cache?

mkdir($cacheDirectory);

Oh, but what if that already exists?

Already exists error!

Ok, let's say you're lucky, your hard drive was wiped out and it doesn't exist yet.

mkdir($cacheDirectory);

But what if the directory is some/cache?

Nested directory error!

Ok, but what if we don't care about these because all we need is to create a directory?

Nette\Utils\FileSystem::createDir($cacheDirectory);

delete()

We want to delete temporary data in tests or a gallery of pictures. All we have is a $source variable.

I admit I often have to Google the name of this function because it's super counter-intuitive to first that pops to my mind - delete(file|directory).

So let's try:

unlink($source);

It's a directory error.

Ah, let's try this then:

rmdir($source);

The directory is not empty error.

Doh, I already imagine some glob() of Finder madness.

Or maybe we just want to delete it:

Nette\Utils\FileSystem::delete($source);


Do you like it? Go and give Nette\Utils a try. It's the only package I allow to use static methods and that's a lot, since I'm very strict to them.

Do you want to play with details someone else already solved for you
or
build your awesome application instead?

Happy coding!




Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!