I know that Composer can determine php, hhvm, ext-<name> and some lib-<name> dependencies. Would the same possible over commands and binaries on PATH? Actually, for instance, I wrote scripts to ensure that Tesseract OCR is present. It can be done with own Composer features?
You can achieve this using a Composer hook, like pre-install-cmd or pre-update-cmd, which executes a PHP method. Here is my test:
composer.json
{
"require": { "pimple/pimple": "*" },
"autoload": { "psr-0": { "Acme\\": "src/" } }
"scripts": {
"pre-install-cmd": "Acme\\Composer\\Hooks::checkBinary",
"pre-update-cmd": "Acme\\Composer\\Hooks::checkBinary"
}
}
src/Acme/Composer/Hooks.php
<?php
namespace Acme\Composer;
use Composer\Script\Event;
class Hooks
{
public static function checkBinary(Event $event) {
$io = $event->getIO();
$path = explode(':', getenv('PATH'));
// do something with $path elements or anything else
if ($somethingWentWrong) {
// Throwing an Exception will cause Composer to stop processing.
throw new \Exception('Check your PATH');
} else {
$io->write('checkBinary() completed.');
}
}
}
Related
How can I autoload helper functions (outside of any class)? Can I specify in composer.json some kind of bootstrap file that should be loaded first?
You can autoload specific files by editing your composer.json file like this:
"autoload": {
"files": ["src/helpers.php"]
}
(thanks Kint)
After some tests, I have came to the conclusions that adding a namespace to a file that contains functions, and setting up composer to autoload this file seems to not load this function across all the files that require the autoload path.
To synthesize, this will autoload your function everywhere:
composer.json
"autoload": {
"files": [
"src/greetings.php"
]
}
src/greetings.php
<?php
if( ! function_exists('greetings') ) {
function greetings(string $firstname): string {
return "Howdy $firstname!";
}
}
?>
...
But this will not load your function in every require of autoload:
composer.json
"autoload": {
"files": [
"src/greetings.php"
]
}
src/greetings.php
<?php
namespace You;
if( ! function_exists('greetings') ) {
function greetings(string $firstname): string {
return "Howdy $firstname!";
}
}
?>
And you would call your function using use function ...; like following:
example/example-1.php
<?php
require( __DIR__ . '/../vendor/autoload.php' );
use function You\greetings;
greetings('Mark'); // "Howdy Mark!"
?>
I have a Packagist package for composer, lwilson/onepage, with one PHP file. The file has one class, which contains one function. I have installed the package on my Web server, and I have a PHP file which is supposed to use it. I know that the composer autoloader is included properly, since I could use other composer libraries installed on the server. I get the following result:
Fatal error: Class 'OnePage\OnePage' not found in /home/photox952/public_html/onepage/test_onepage.php on line 6
test_onpepage.php (the file that uses the class):
<?php
require '../../vendor/autoload.php';
use OnePage\OnePage;
file_put_contents("index.php",OnePage::OnePage(json_decode(file_get_contents("cfg.json")),__DIR__));
?>
compiler.php (the file in the package):
<?php
namespace OnePage;
// This is licenced under the GNU GENERAL PUBLIC LICENSE. More info at: https://github.com/LeoWilson-XnD/lwilson_onepage/blob/master/LICENSE
class OnePage{
public static function OnePage($cfg,$dir){
$tmplt_u = "<?php if(\$_REQUEST['type']==\"{TYPE}\"&&\$_REQUEST['src']==\"{SRC}\"){header(\"Content-Type: {CT}\"); ?>{DATA}<?php } ?>";
$tmplt_c = "<?php if(\$_REQUEST['all']==\"{TYPE}\"){header(\"Content-Type: {CT}\"); ?>{DATA}<?php } ?>";
$res = "<?php /* This code is generated by the OnePage tool. Find out more here: https://github.com/LeoWilson-XnD/lwilson_onepage */ ?>";
$dir.=$cfg['dir'];
if($cfg['v']==1){
foreach($cfg['unique'] as $ka => $va){
foreach($cfg[$ka] as $kb => $vb){
$u = $tmplt_u;
$u=str_replace("{TYPE}",explode("/",$ka)[1],$u);$u=str_replace("{SRC}",$kb,$u);$u=str_replace("{CT}",$ka,$u);$u=str_replace("{DATA}",file_get_contents($dir.$vb),$u);
$res.=$u;
}
}
foreach($cfg['combine'] as $ka => $va){
$ds = "";
foreach($cfg[$ka] as $kb => $vb){
$ds.=file_get_contents($dir.$vb);
}
$u = $tmplt_c;
$u=str_replace("{TYPE}",explode("/",$ka)[1],$u);$u=str_replace("{CT}",$ka,$u);$u=str_replace("{DATA}",file_get_contents($dir.$vb),$u);
$res.=$u;
}
foreach($cfg['links'] as $key => $val){
$res = str_replace($val,$key,$res);
}
}
return $res;
}
}
?>
composer.json (the package's composer.json file):
{
"name": "lwilson/onepage",
"description": "This allows users to compile websites easily into one PHP file.",
"authors": [
{
"name": "Leo Wilson",
"email": "lwilson#xlww.net",
"homepage": "https://xlww.net/",
"role": "Developer"
}
],
"minimum-stability": "stable",
"version": "v1.0.0",
"homepage": "https://github.com/LeoWilson-XnD/lwilson_onepage",
"licence":"GPL-3.0",
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"": "/"
}
}
}
I apologize in advance if I'm missing something obvious.
I think you set the autoload wrong in composer. Try to create a folder named OnePage and put the file with the class in it. It must be named OnePage.php
Then set autoload like this
"autoload": {
"psr-4": {
"OnePage\\": "OnePage"
}
}
then run composer update from the shell. Then try running your php file again.
The OnePage.php file should be in folder /home/photox952/public_html/onepage/OnePage
I am new to using composer and psr-0. I have tried a small app using composer and psr-0. I have used namespace to load a particular class. When i call a class using composer vendor/autoload I am getting class not found error.
My composer.json file:/var/www/html/silexapp/composer.json
{
"require": {
"silex/silex": "~2.0",
"symfony/console": "~2.6"
},
"autoload": {
"psr-0": {
"MyApp": "/silexapp/app"
}
}
}
My composer vendor autoload file: /var/www/html/silexapp/vendor/autoload.php
<?php
// autoload.php #generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitf7241d907c173a8d77da0791cc918856::getLoader();
My class file name Underline.php: /var/www/html/silexapp/app/Tnq/Todo/Command/Underline.php
<?php
namespace MyApp\Tnq\Todo\Command;
class Underline{
public function add($a,$b){
return $result = $a+$b;
}
}
?>
My another class file name Bold.php: /var/www/html/silexapp/app/Tnq/Todo/Command/Bold.php
<?php
require_once "../../../../vendor/autoload.php";
//require_once "Underline.php";
use MyApp\Tnq\Todo\Command as tool;
echo "this is the index file to check namespace.";
$c = new tool\Underline();
echo "=============================";
echo "Addition : ".$c->add(2,2);
?>
I am getting "class not found error" in my bold.php class file, when I use autoload file. But when I directly included the underline class file, I am getting the output. Why it is not working when I use autoload?
Can anyone help me to find the issue?
The "key" should be a directory under the path you put as "value", that should be relative to your working directory. To look at it in a simple way, the namespace should map the directory structure; you are missing a MyApp directory.
If in your composer.json have:
"autoload": {
"psr-0": {
"MyApp\\": "app/"
}
}
Then you need a MyApp directory under app/. Try this:
composer.json:
// /var/www/html/silexapp/composer.json
{
"require": {
"silex/silex": "~2.0",
"symfony/console": "~2.6"
},
"autoload": {
"psr-0": {
"Tnq\\": "app/"
}
}
}
Underline.php:
<?php
// /var/www/html/silexapp/app/Tnq/Todo/Command/Underline.php
namespace Tnq\Todo\Command;
class Underline
{
public function add($a,$b)
{
return $result = $a+$b;
}
}
Bold.php:
<?php
// /var/www/html/silexapp/app/Tnq/Todo/Command/Bold.php
require_once "../../../../vendor/autoload.php";
use Tnq\Todo\Command as tool;
echo 'this is the index file to check namespace.' . PHP_EOL;
$c = new tool\Underline();
echo "=============================";
echo "Addition : ".$c->add(2,2);
In theory, that should works (not tested :) )
sources:
https://getcomposer.org/doc/04-schema.md#psr-0
http://www.php-fig.org/psr/psr-0/
I'm using composer in my latest project and mapping my function like this
"require": {
...
},
"require-dev": {
...
},
"autoload": {
"psr-4": {
...
},
"files": [
"src/function/test-function.php"
]
}
I imagine there will be a lot of files in a folder function, ex : real-function-1.php, real-function-2.php, etc. So, can composer call all the files in the folder function ? i lazy to use
"files": [
"src/function/real-function-1.php",
"src/function/real-function-2.php",
..,
"src/function/real-function-100.php",
]
Is there any lazy like me...
If you can't namespace your functions (because it will break a bunch of code, or because you can't use PSR-4), and you don't want to make static classes that hold your functions (which could then be autoloaded), you could make your own global include file and then tell composer to include it.
composer.json
{
"autoload": {
"files": [
"src/function/include.php"
]
}
}
include.php
$files = glob(__DIR__ . '/real-function-*.php');
if ($files === false) {
throw new RuntimeException("Failed to glob for function files");
}
foreach ($files as $file) {
require_once $file;
}
unset($file);
unset($files);
This is non-ideal since it will load every file for each request, regardless of whether or not the functions in it get used, but it will work.
Note: Make sure to keep the include file outside of your /real-function or similar directory. Or it will also include itself and turn out to be recursive function and eventually throw a memory exception.
There's actually a better way to do this now without any custom code. You can use Composer's classmap feature if you're working with classes. If you're working with individual files that contain functions then you will have to use the files[] array.
{
"autoload": {
"classmap": ["src/", "lib/", "Something.php"]
}
}
This whole process can be completely automated while still performing relatively well.
With the following package.json (note the absence of an autoload entry, you could however add others)
{
"scripts": {
"pre-autoload-dump": "\\MyComponentAutoloader::preAutoloadDump"
}
}
And then...
<?php
class MyComponentAutoloader
{
public static function preAutoloadDump($event): void
{
$optimize = $event->getFlags()['optimize'] ?? false;
$rootPackage = $event->getComposer()->getPackage();
$dir = __DIR__ . '/../lib'; // for example
$autoloadDefinition = $rootPackage->getAutoload();
$optimize
? self::writeStaticAutoloader($dir)
: self::writeDynamicAutoloader($dir);
$autoloadDefinition['files'][] = "$dir/autoload.php";
$rootPackage->setAutoload($autoloadDefinition);
}
/**
* Here we generate a relatively efficient file directly loading all
* the php files we want/found. glob() could be replaced with a better
* performing alternative or a recursive one.
*/
private static function writeStaticAutoloader($dir): void
{
file_put_content(
"$dir/autoload.php",
"<?php\n" .
implode("\n", array_map(static function ($file) {
return 'include_once(' . var_export($file, true) . ');';
}, glob("$dir/*.php"))
);
}
/**
* Here we generate an always-up-to-date, but slightly slower version.
*/
private static function writeDynamicAutoloader($dir): void
{
file_put_content(
"$dir/autoload.php",
"<?php\n\nforeach (glob(__DIR__ . '/*.php') as \$file)\n
include_once(\$file);"
);
}
}
Things to note:
preAutoloadDump takes care of adding the autoload.php entrypoint to composer.
autoload.php is generated every time the autoloader is dumped (e.g. composer install / composer update / composer dump-autoload)
when dumping an optimised autoloader (composer dump-autoload --optimize), only the files found at that point will be loaded.
you should also add autoload.php to .gitignore
I would like to use composer script to do some post installation such as copying files from bootstrap vendor folder to my web application public folder. I have a baby experience with PHP world and web application development.
I'm trying to learn doing this by following this tutorial
This is my directory structure*
This is my composer.json
{
"name": "Composer Script",
"description": "An example to demonstrate the use of Composer scripts",
"version": "1.0.0",
"require": {
"twitter/bootstrap": ">=3.0"
},
"scripts": {
"post-install-cmd": [
"ComposerScript\\Installer::postInstall"
],
"post-package-install": [
"/var/www/test/composer-script/install.sh"
]
}
}
This is ComposerScript\Installer.php
class Installer
{
public static function postInstall(Event $event)
{
$composer = $event->getComposer();
// do stuff
}
public static function postPackageInstall(Event $event)
{
$installedPackage = $event->getOperation()->getPackage();
// do stuff
}
public static function warmCache(Event $event)
{
// make cache toasty
}
}
After execute composer install I got this error
install.sh is empty at this moment
How to fix this error, and especially what is autoload?, I don't event know what keywords to search for please suggest me some reading.
Just in case someone stumbled again to this problem. Here is a spoon feed sample. :)
In a given scenario like this
We have to set on our composer.json to the ff. settings
"autoload": {
"psr-0": {
"ComposerScript\\Installer" : ""
}
},
"scripts": {
"post-package-update": [
"ComposerScript\\Installer::postPackageUpdate"
]
}
Then the content of Installer.php is
namespace ComposerScript;
use Composer\Script\Event;
class Installer
{
public static function postUpdate(Event $event)
{
$composer = $event->getComposer();
// do stuff
}
public static function postPackageUpdate(Event $event)
{
$packageName = $event->getOperation()
->getPackage()
->getName();
echo "$packageName\n";
// do stuff
}
public static function warmCache(Event $event)
{
// make cache toasty
}
}
Then executing php composer.phar update will work without warning.
The post-package-install value is relative to the location of the composer.json file. Do not use absolute location here.
Also, Composer's install scripts expect PHP, not sh!
Kongthap, I ran into the same problem and this is how I fixed it:
"require": {...},
"autoload": {
"psr-0": {
"Dphp": "src/"
}
},
"scripts": {...}
Dphp is the root namespace of my lib.
Hope it helps.
Sometimes the problem can be global packages are outdated, you can fix it by running the command composer global update before running composer install