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
Related
im learning something about RedBeanPHP ORM and add the
code downloaded from http://www.redbeanphp.com/downloadredbean.php
to my project autoload using 'composer dump-autoload' command and
the configuration 'composer.json' in the root directory is:
{
"autoload": {
"classmap": [
"vendor/redbeanphp/src/rb.php",
"vendor/myowncode/src/Model.php"
]
}
}
on 'vendor/composer/installed.json' i put this:
[
{
"name": "gabordemooij/redbean",
"version": "5.4",
"require": {},
"autoload": {
"psr-4": {"RedBeanPHP\\": "src"}
}
},
"name": "myowncode/src",
"version": "1.0",
"require": {},
"autoload": {
"psr-4": {"MyCode\\": "src"}
}
}
]
and all works fine, at least until i try the example from the RedBean web
about 'Models' and the code:
<?php
require 'vendor/autoload.php';
class Model_Band extends RedBean_SimpleModel {
public function update() {
if ( count( $this->bean->ownMember ) >4 )
throw new Exception( 'Too many members!' );
}
}
results in error:
PHP Fatal error: Cannot declare class RedBeanPHP\RedException, because the name is already in use in /opt/lampp/htdocs/testing/vendor/redbeanphp/src/rb.php on line 8358
Fatal error: Cannot declare class RedBeanPHP\RedException, because the name is already in use in /opt/lampp/htdocs/testing/vendor/redbeanphp/src/rb.php on line 8358
but, if i dont use autoload and do this:
require 'vendor/redbean/src/rb.php';
class Model_Band extends RedBean_SimpleModel {
public function update() {
if ( count( $this->bean->ownMember ) >4 )
throw new Exception( 'Too many members!' );
}
}
it works, but i want that works with the autoload, i know, i can just open composer.json file and add the package name ("gabordemooij/redbean": "dev-master"), but i want to learn more about autoload and
get a good comprehension of whats wrong on my configuration/code.
The problem was the code from http://www.redbeanphp.com/downloadredbean.php its not
prepared for use with composer autoload, is some kind of amalgamation, all the code
in a single file, and i try downloading a release from:
https://github.com/gabordemooij/redbean/archive/v5.4.2.zip, i do the same process
to generate autoload, but we must edit the file loader on RedBeanPHP dir on the
release and change the REDBEANPHP_MAIN_DIR from phar://rb.phar/RedBeanPHP/
to vendor/redbean-5.4.2/RedBeanPHP/, i put the code on vendor/redbean-5.4.2,
and thats all problem solved :)
I had a working composer package with a single files and a single class. So, now I'm trying to change the package so that it is more like SOLID.
I have a file structure like this...
PackageName.php
addresses.php
names.php
interfaces
names.php
addresses.php
When I use PHPSpec the methods in the main PackageName.php are getting validated but within one of the methods I have something like...
namespace blah\PackageName;
use blah\PackageName\ProcessNames;
class PackageName
{
public function formatData($user)
{
$place_holders = array();
$place_holders = ProcessNames::process_name($user, $place_holders);
$place_holders = ProcessAddresses::process_address($user, $place_holders);
return json_encode($place_holders);
}
}
which gives the error...
48 ! should do the address
exception [err:Error("Class 'blah\PackageName\ProcessNames' not found")] has been thrown.
The composer.json is like...
{
"name": "blah\PackageName",
"description": "Format data.",
"require": {
"nesbot/carbon": "^1.34",
"php": ">7.0.0"
},
"require-dev": {
"phpspec/phpspec": "^4.3"
},
"authors": [
{
"name": "me",
"email": "me#emailaddress.com"
}
],
"autoload": {
"psr-4": {
"blah\\PackageName\\": "src/",
"spec\\blah\\PackageName\\": "spec/"
},
"files": {
"src/interfaces/names.php",
"src/names.php"
}
}
}
I can't see how to include files in the package. I'm not sure I need the "files" part on composer.json but I'm trying to figure out how to do it. Any info much appreciated.
This is the same issue someone else had today! A simple error.
Replace:
namespace blah\PackageName;
with:
namespace blah;
The full class name includes the namespace and class name. So essentially your class was actually an instance of blah\PackageName\Packagename
Possibly take Packagename out of the composer.json, depending on your needs, and if you change that, remember to run composer dumpautoload
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'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 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.');
}
}
}