This post focuses on bin files. It's the smallest part of PHP CLI Application, so I usually start with it.
Yet, there are still a few blind paths you can struggle with. I'll drop a few extra tricks to make your bin file clean and easy to maintain.
The bin file is not a trash bin. It's a binary file, the entry point to your application the same way www/index.php
is. You probably already use them:
The bin file should be named after the application, short and easy to type.
So when I first released EasyCodingStandard, I used easy-coding-standard
name. It was easy to remember, but when I had a talk I often miss-typed such a long name. After a while, I moved to ecs
.
It should be also easy to remember and unique.
Imagine that php-cs-fixer
would be phpcf
or phpcf
. Since there is already phpcs
taken, it might be trouble to remember. I think that's why the name is a little bit longer.
Where to put the file? Few people in the past put it in the root directory (only php-cs-fixer
from 4 projects above have it that way). But the trend is to use bin
directory. The same way index.php
was moved to www/index.php
or public/index.php
.
bin/your-bin-file
With structure like this:
/bin/your-bind-file
/src
/vendor/autoload.php
The obvious code to add to /bin/your-bind-file
is:
<?php declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
And that's it!
Not that fast. It might work for your www/index.php
file in your application, but is that application ever installed as a dependency?
How do we cover autoload for a dependency? Imagine somebody would install your-vendor/your-package
on his application. The file structure would look like this:
/src/
/vendor/autoload.php
/vendor/your-vendor/your-package/bin/your-bin-file
Now we need to get to /vendor/autoload.php
of that application:
<?php declare(strict_types=1);
-require_once __DIR__ . '/../vendor/autoload.php';
+require_once __DIR__ . '/../../../../vendor/autoload.php',
Great, people can use our package now. But it stopped working for our local repository. We'll probably have to seek for both of them:
<?php declare(strict_types=1);
$possibleAutoloadPaths = [
// local dev repository
__DIR__ . '/../vendor/autoload.php',
// dependency
__DIR__ . '/../../../../vendor/autoload.php',
];
foreach ($possibleAutoloadPaths as $possibleAutoloadPath) {
if (file_exists($possibleAutoloadPath)) {
require_once $possibleAutoloadPath;
break;
}
}
Comments are very important because this is very easy to get lost in. Trust me, I managed to fail a dozen times. Also, other people will appreciate it because it's WTF to see loading more than one vendor/autoload.php
.
Imagine you'd move your package to a monorepo structure:
$possibleAutoloadPaths = [
- // local dev repository
+ // after split repository
__DIR__ . '/../vendor/autoload.php',
// dependency
__DIR__ . '/../../../../vendor/autoload.php',
+ // monorepo
+ __DIR__ . '/../../../vendor/autoload.php',
];
<?php declare(strict_types=1);
$possibleAutoloadPaths = [
// local dev repository
__DIR__ . '/../vendor/autoload.php',
// dependency
__DIR__ . '/../../../../vendor/autoload.php',
];
+$isAutoloadFound = false;
foreach ($possibleAutoloadPaths as $possibleAutoloadPath) {
if (file_exists($possibleAutoloadPath)) {
require_once $possibleAutoloadPath;
+ $isAutoloadFound = true;
break;
}
}
+
+if ($isAutoloadFound === false) {
+ throw new RuntimeException(sprintf(
+ 'Unable to find "vendor/autoload.php" in "%s" paths.',
+ implode('", "', $possibleAutoloadPaths)
+ ));
+}
Since the bin file doesn't have a .php
suffix by convention a system doesn't know, what language it's in. What happens when we run the bin file?
bin/your-bin-file
↓
vendor/bin/your-bin-file: 1: vendor/bin/your-bin-file: Syntax error: "(" unexpected
Well, we know it's in PHP so run it with PHP:
php bin/your-bin-file
All works! But do we ever run this?
php composer
No, because we're lazy and we want to type as less as possible. How do we achieve the same effect for our file?
We add a shebang - a special line that will tell the system what interpret should be used:
#!/usr/bin/env php
<?php declare(strict_types=1);
// ...
It can be translated to:
/usr/bin/env php bin/your-bin-file
Try it. Does it work?
This allows to run the bin file on other people's computer:
chmod +x bin/your-bin-file
If we install your package, we'll find the bin file here:
/vendor/your-vendor/your-package/bin/your-bin-file
But not in:
/vendor/bin/your-bin-file
Too bad. We're super lazy, so we want it there. How can we make it happen?
The Composer has special bin section, where we can define the symlink path for your bin file. Just add this to composer.json
of your package:
{
"bin": "bin/your-bin-file"
}
Tada! After we install such a package, we'll find it in the right place.
/vendor/bin/your-bin-file
#!/usr/bin/env php
<?php declare(strict_types=1);
$possibleAutoloadPaths = [
// local dev repository
__DIR__ . '/../vendor/autoload.php',
// dependency
__DIR__ . '/../../../autoload.php',
];
foreach ($possibleAutoloadPaths as $possibleAutoloadPath) {
if (file_exists($possibleAutoloadPath)) {
require_once $possibleAutoloadPath;
break;
}
}
// your PHP code to run
$container = (new ContainerFactory)->create();
$application = $container->get(Application::class);
exit($application->run());
And that's it!
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!