So - I have a simple PCR0 auto-loader in my bootstrap.php, that should load any PCR0 compatible library class from vendors directory...
spl_autoload_register( function( $classname ) {
$path = preg_match( '/\\\\/', $classname )
? str_replace( '\\', DIRECTORY_SEPARATOR, $classname )
: str_replace( '_', DIRECTORY_SEPARATOR, $classname );
$file = VENDORS_PATH . DIRECTORY_SEPARATOR . $path . '.php';
if ( file_exists( $file ) ) {
require_once( $file );
}
});
I'm not sure if I understand why composer generates auto-loading files in vendors directory (namely composer directory and autoload.php file) ?
Can I stop Composer from generating those auto-loader files? or am I missing something? I don't think I need them?
There are three autoload related files, each having a different purpose.
vendor/autoload.php initializes the autoloaders of composer. Composer offers a autoloaders to enable composer compatible libraries to be load.
vendor/composer/autoload_classmap.php this file is used by the classmap autoloader, this is for either libraries that are not even PSR-0 compatible, or production environments (classmap is faster than a lookup through the file system).
vendor/composer/autoload_namespaces.php this is the configuration for the PSR-0 autoloading that composer comes with
Now you mentioned that you have your own PSR-0 classloader, which you are not supposed to use for composer dependencies - you are simply supposed to require/include the vendor/autoload.php and have composer take care of the rest.
This is why there is no option to disable the generation of the autoloading files. In the end composer is supposed to enable you to use the library installed, and enables you by providing all loading you need.
Unfortunately, It doesn't sound like Composer is going to support this feature: https://github.com/composer/composer/issues/1663
In my CMS EFFCORE I used the following solution...
For UNIX shell:
File composer.json
"scripts": {
"post-install-cmd": [
"rm vendors/autoload.php",
"rm -rf vendors/composer"
],
"post-update-cmd": [
"rm vendors/autoload.php",
"rm -rf vendors/composer"
]
}
For Win/Nix:
File composer.json
"scripts": {
"post-install-cmd": [
"php vendors/post-install-cmd.php"
],
"post-update-cmd": [
"php vendors/post-install-cmd.php"
]
}
File vendors/post-install-cmd.php
# rm -rf vendors/packages/composer
if (file_exists('vendors/packages/composer/')) {
$composer_items = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator('vendors/packages/composer/', \FilesystemIterator::UNIX_PATHS|\FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
foreach ($composer_items as $c_path => $c_spl_file_info) {
if ($c_spl_file_info->isFile()) {if (#unlink($c_path)) print "File '". $c_path. "' was removed.\n";}
if ($c_spl_file_info->isDir ()) {if (#rmdir ($c_path)) print "Directory '". $c_path. "/' was removed.\n";} }
if (#rmdir('vendors/packages/composer/')) {
print "Directory 'vendors/packages/composer/' was removed.\n";
}
}
# rm vendors/packages/autoload.php
if (file_exists('vendors/packages/autoload.php') &&
#unlink('vendors/packages/autoload.php')) {
print "File 'vendors/packages/autoload.php' was removed.\n";
}
There is an option --no-autoloader on install and update commands.
It's implemented in https://github.com/composer/composer/pull/3453 since the December of 2014.
Personally I added those files to .gitignore since the project I am working on has an autoloader that works fine
Related
I'm writing unit tests for my Shopware Plugin. I've setup the same PHPUnit environment as SwagPayPal.
What I did:
composer.json
[...]
"require": {
"ext-json": "*",
"php": ">= 8.0",
"goetas-webservices/xsd2php-runtime": "^0.2.16",
"enqueue/stomp": "^0.10.16",
"shopware/core": "6.4.12.0"
},
"autoload": {
"psr-4": {
"Namespace\\Plugin\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Namespace\\Plugin\\Test\\": "tests/"
}
},
[...]
phpunit.xml.dist bootstrap="tests/TestBootstrap.php"
TestBootstrap.php is a 1:1 copy of the same file from SwagPayPal
except ->addActivePlugins('SwagCmsExtensions') was removed. https://github.com/shopwareLabs/SwagPayPal/blob/10694e851a11ac60e98cdb2130e66724611ef9a6/tests/TestBootstrap.php
My tests were all working fine. I now added the following code in one test:
$customerRepository = $this->getContainer()->get('customer.repository');
$customerRepository->upsert([$customerData], Context::createDefaultContext());
After calling upsert it crashes with Compile Error: Cannot declare class Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor, because the name is already in use
I can get it working when I run sudo rm -rf var/cache/test_* but only once. When running it a second time it crashes again.
It seems it loads Shopware from the vendor folder of the plugin and then also from the root folder. I don't get why it's working the first time, but not a second time.
Edit: I checked all my namespace declarations in the PHP test files and I'm pretty sure they're correct. So I think it's another cause I can't find.
This is caused by the classes being autoloaded multiple times. It's hard to say what causes this without knowing all the code involved. Rather than copying from an existing plugin I'd suggest you start from scratch and follow the documentation on how to setup a clean environment for your tests.
I just updated my tests/TestBootstrap.php to fix my problem.
I removed the ->setClassLoader(require dirname(__DIR__) . '/vendor/autoload.php') call, so it doesn't load the composer generated stuff anymore.
I added the following code so it adds the PSR-4 namespace prefixes for my plugin:
$pluginComposerDir = realpath(__DIR__ . '/..');
$pluginComposerJson = json_decode(file_get_contents($pluginComposerDir . '/composer.json'));
foreach (['autoload', 'autoload-dev'] as $loader) {
if (is_object($pluginComposerJson->$loader) && is_object($pluginComposerJson->$loader->{'psr-4'})) {
foreach ($pluginComposerJson->$loader->{'psr-4'} as $namespace => $path) {
$classLoader->addPsr4($namespace, $pluginComposerDir . '/' . $path);
}
}
}
So now I have best of both worlds:
My plugin only adds the autoload-dev namespace(s) for tests.
It doesn't load the composer dependecies of my plugin, so I get no collisions with the Shopware root or other dependencies
I need to install my plugin dependencies in the Shopware root and if I don't do it Shopware won't install my extension and warn me about the failed dependencies:
Failed Dependency: Required plugin/package "goetas-webservices/xsd2php-runtime ^0.2.16" is missing or not installed and activated
Failed Dependency: Required plugin/package "enqueue/stomp ^0.10.16" is missing or not installed and activated
Full tests/TestBootstrap.php:
<?php declare(strict_types=1);
// based on tests/TestBootstrap.php of SwagPayPal
/*
* (c) shopware AG <info#shopware.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Shopware\Core\TestBootstrapper;
if (is_readable(__DIR__ . '/../../../../vendor/shopware/platform/src/Core/TestBootstrapper.php')) {
// from Shopware root vendor dir
require __DIR__ . '/../../../../vendor/shopware/platform/src/Core/TestBootstrapper.php';
} elseif (is_readable(__DIR__ . '/../../../../vendor/shopware/core/TestBootstrapper.php')) {
// from Shopware root vendor dir
require __DIR__ . '/../../../../vendor/shopware/core/TestBootstrapper.php';
} else {
throw new Exception('TestBootstrapper not found. Please install Shopware root dependencies.');
}
$classLoader = (new TestBootstrapper())
->setProjectDir($_SERVER['PROJECT_ROOT'] ?? dirname(__DIR__, 4))
->setLoadEnvFile(true)
->setForceInstallPlugins(true)
->addCallingPlugin()
->bootstrap()
->getClassLoader();
$pluginComposerDir = realpath(__DIR__ . '/..');
$pluginComposerJson = json_decode(file_get_contents($pluginComposerDir . '/composer.json'));
foreach (['autoload', 'autoload-dev'] as $loader) {
if (is_object($pluginComposerJson->$loader) && is_object($pluginComposerJson->$loader->{'psr-4'})) {
foreach ($pluginComposerJson->$loader->{'psr-4'} as $namespace => $path) {
$classLoader->addPsr4($namespace, $pluginComposerDir . '/' . $path);
}
}
}
return $classLoader;
Edit: adjusted tests/TestBootstrap.php to load TestBootrapper class from Shopware root.
I'm having trouble understanding with doesn't Composer autoloads the packages I required.
My current composer.json file has the following:
{
"require": {
"atlas/orm": "#dev"
},
"require-dev": {
"atlas/cli": "#dev"
}
}
It was supposed to generate a Namespace in the /vendor/composer/autoload_namespaces.php file. But it doesn't. The file only has the following:
// autoload_namespaces.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Psr\\Log\\' => array($vendorDir . '/psr/log'),
);
Of course, when I try to use the "require DIR . '/vendor/autoload.php';" to autoload the package and then use its classes, it does not work.
Any idea on how can I solve this?
For requiring in all of the installed dependencies, you have to require 'autoload.php'. For autoloading(PSR-4), in the composer.json file, you have to give a name under which everything will be namespaced and the folder name from which files will be autoloaded.
"Namespace_name\\":"folder_name"
Note: The backslash after the namespace_name needs to be escaped, hence the extra backslash.
Then run composer dump-autoload -o
I did my best to find a question/answer that applied, but I don't think I understand enough about the autoloader to recognize a suitable answer.
I have a package with the following composer.json:
{
"name": "Pva_agent",
"type":"library",
"description" : "query the pva agent",
"version":"0.1b",
"authors" : [
{
"name":"Ed Greenberg",
"email":"ed#precisionpros.com"
}
],
"minimum-stability":"dev",
"require": {},
"autoload": {
"psr-0": {
"Pva_agent": "."
}
}
}
My directory structure after composer installation of the package:
.
./vendor
./vendor/autoload.php
./vendor/Pva_agent
./vendor/Pva_agent/Agent.php
./vendor/Pva_agent/composer.json
./vendor/Pva_agent/.gitignore
./vendor/composer
./vendor/composer/autoload_psr4.php
./vendor/composer/autoload_real.php
./vendor/composer/autoload_classmap.php
./vendor/composer/autoload_namespaces.php
./vendor/composer/installed.json
./vendor/composer/autoload_static.php
./vendor/composer/ClassLoader.php
./vendor/composer/LICENSE
./composer.lock
./composer.json
./test_pva_agent.php
My test program:
<?php
require_once('vendor/autoload.php');
use Pva_agent\Agent;
$agent = new Agent();
My result:
edg#arthur pva_project $ php test_pva_agent.php
PHP Fatal error: Class 'Pva_agent\Agent' not found in /home/edg/PhpstormProjects/pva_project/test_pva_agent.php on line 6
PHP Stack trace:
PHP 1. {main}() /home/edg/PhpstormProjects/pva_project/test_pva_agent.php:0
edg#arthur pva_project $
I didn't think I needed the 'use' statement, since the autoloader should find the class, right?
Can somebody tell me where the problem lies?
Thanks,
Ed Greenberg
Your Pva_agent library should not sit in the vendor/ directory. This directory should contain only auto-generated data from Composer. This directory is usually not stored in VCS.
You should consider refactoring your directory structure to something similar to this one:
.
|____composer.json
|____composer.lock
|____src
| |____Pva_agent
|____vendor
Your library functionality should be added to src/Pva_agent directory.
Consider to use PSR-4 instead of PSR-0 for autoload functionality, as there is no need to regenerate the autoloader when you add classes. dump-autoloader has to be run in case of PSR-0 after adding classed.
For the directory structure above and the PSR-4 autoloader your composer.json autoload section should look similar to this one:
"autoload": {
"psr-4": { "Pva_agent\\": "src/Pva_agent" }
}
Your library should be auto loaded after this. Your auto-loaded library will be registered under the Pva_agent namespace.
One can install PHP packages from packagist.org with a command such as composer require vendor/package-name. Now the composer documentation states that there are hooks for several events during composer execution:
A script, in Composer's terms, can either be a PHP callback (defined as a static method) or any command-line executable command. Scripts are useful for executing a package's custom code or package-specific commands during the Composer execution process.
The doc further state:
Note: Only scripts defined in the root package's composer.json are executed. If a dependency of the root package specifies its own scripts, Composer does not execute those additional scripts.
Does this mean there is no way to execute PHP code while a package is required from packagist.org via composer require vendor/package-name?
There was a fairly long discussion about this topic in the composer issue-queue a few years ago.
https://github.com/composer/composer/issues/1193
TLDR: It's not supported for post-update-cmd hooks, but it is possible to get around this by writing composer plugins. For example, I did a generic plugin to run a compile hook for any new or changed packages:
https://github.com/civicrm/composer-compile-plugin
An example from the docs:
{
"name": "foo/bar",
"require": {
"civicrm/composer-compile-plugin": "~0.8",
"scssphp/scssphp": "1.2.0",
"padaliyajay/php-autoprefixer": "~1.2"
},
"autoload": {"psr-4": {"ScssExample\\": "src"}},
"extra": {
"compile": [
{"run": "#php-method \\ScssExample\\ScssExample::make"}
]
}
}
And the corresponding callback:
namespace ScssExample;
class ScssExample
{
public static function make(array $task)
{
$scssCompiler = new \ScssPhp\ScssPhp\Compiler();
$scss = 'div { .foo { hyphens: auto; } }';
$css = $scssCompiler->compile($scss);
$autoprefixer = new \Padaliyajay\PHPAutoprefixer\Autoprefixer($css);
file_put_contents("build.css", $autoprefixer->compile());
}
}
composer will dispatch a post-update-cmd event after installing the requested package with require. This will execute scripts defined in your root composer.json for this event. See this sample scenario:
composer.json
{
"autoload": {
"psr-4": {
"fknffa\\": "src"
}
},
"scripts": {
"post-update-cmd": [
"fknffa\\floom::sayHello"
]
}
}
src/floom.php
<?php
namespace fknffa;
class floom
{
public static function sayHello()
{
print 'I am in ' . __METHOD__ . PHP_EOL;
}
}
Execute require command
$ composer require twig/twig
Using version ^1.23 for twig/twig
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing twig/twig (v1.23.1)
Writing lock file
Generating autoload files
> fknffa\floom::sayHello
I am in fknffa\floom::sayHello
I've added composer to an existing project that uses the PHP autoload function. Now that composer autoload.php is being used I've removed my old autoload function and I'm trying to load my existing source directory via composer autoload but it isn't picking up any of my existing source classes.
Everything installed by composer loads fine and can be accessed via namespaces etc. so it's just the existing sources in the source directory not being picked up. Any suggestions?
I've looked at a few other of the composer questions on stackoverflow but nothing I've read has solved my problem.
File structure:
index.php
root/
sources/
vendor/
composer.json
media/
Composer autoload:
"autoload": {
"psr-0": {
"" : "sources/"
}
}
There were two things causing issues for me, one was the class file names and the second was a composer command that needed to be run.
My class file names were in the format {classname}.class.php when they need to be in the format that PSR-0 expects which is Classname.php (uppercase first letter) and in turn the classname in the class file follows the file name.
class Classname
{
...
The second issue was that I needed to run the below command.
composer dump-autoload
From composer.org:
If you need to update the autoloader because of new classes in a classmap package for example, you can use "dump-autoload" to do that without having to go through an install or update.
If your code structure is too complex to convert to PSR-* structure, you can use your old autoloader and composer autoload together.
spl_autoload_register( function ( $class ) {
$file = "sources/" . $class . ".class.php";
if ( file_exists( $file ) ) {
require $file;
return;
}
} );
require "vendor/autoload.php";