Composer plugin autoload depedency - php

So I'm working on a composer plugin that adds a custom command that can be run after an install or update.
I understand the autoloading configuration, and composer is autoloading all classes but it's also missing a file that just contains namespaced functions.
In my plugin composer.json I have the following:
{
"name": "myco/composer-s3-downloader",
"type": "composer-plugin",
"version": "0.1",
"require": {
"composer-plugin-api": "1.1.*",
"aws/aws-sdk-php": "3.20.*"
},
"autoload": {
"psr-4": {"MyCo\\Composer\\": "MyCo/"}
},
"extra": {
"class": "MyCo\\Composer\\S3Downloader"
}
}
My plugin classes load without a problem. All of the classes for my dependencies also load without a problem. So my plugin command code starts off just fine using the AWS SDK.
The problems comes here, when I try to instantiate an S3Client:
private function initClient() {
if (is_null($this->_s3Client)) {
$this->_s3Client = new \Aws\S3\S3Client([
"version" => "latest",
"region" => 'us-west-2',
"credentials" => [
"key" => $this->_creds['key'],
"secret" => $this->_creds['secret'],
]]);
}
}
I get the following error:
PHP Fatal error: Call to undefined function Aws\manifest()
in .../test/vendor/aws/aws-sdk-php/src/AwsClient.php on line 143
I can see the autoload config in the AWS composer.json and it's correct:
"autoload": {
"psr-4": {
"Aws\\": "src/"
},
"files": ["src/functions.php"]
}
The Aws\manifest function is declared in functions.php. And functions.php is then specified in vendor/composer/autoload_files.php. But near as I can tell that file isn't being loaded. So not all of my dependencies are actually available.
What step am I missing that forces the inclusion of autoload_files.php? I'm not doing a single include for anything in the vendor folder. I'm assuming that composer will handle that for me. But I guess I'm wrong.

So after posting an issue at the Composer Github repo, I did learn that the autoloader that runs during composer execution only includes classes. So if you do need to include loose functions, you'll have to manually run the full autoloader.
I added the following method to my Command class that is loaded by composer and defined in the extra section of the plugin's composer.json.
private function getIncludes() {
$vendorDir = $this->composerInstance->getConfig()->get('vendor-dir');
require $vendorDir . '/autoload.php';
}
I just call it in my plugin constructor and everything I need becomes available.

Related

Class not found using custom composer package

I have created a custom comoposer package and I want to use it on my project with this composer.json:
{
"name": "papillon/test",
"type": "library",
"version": "dev-master",
"require": {
"php": "^7.1.11"
},
"autoload": {
"psr-4": {
"Papillon\\Fountaine\\Eau\\": "src/Papillon/Fountaine/Eau/"
}
}
}
I compress it in zip. In the main project, I add a folder called repo, where I add de composer package zip. Then, I modify the composer.json of the main project like this:
{
"repositories": [
{
"type": "artifact",
"url": "var/main/repo"
}
],
"require": {
"papillon/test": "dev-master"
}
}
I execute composer update and the pakage is added to vendor folder; all seems to be going well... but if I want to test the package from the main project with this script:
<?php
require (__DIR__ . '/vendor/autoload.php');
use Papillon\Fountaine\Eau\FlowerClass;
echo FlowerClass::bloom();
It returns: PHP Fatal error: Uncaught Error: Class 'Papillon\Fountaine\Eau\FlowerClass' not found in .../test_package.php:6
Stack trace:
#0 {main}
thrown in .../test_package.php on line 6
I think that the package may not be recognized by the main project; maybe the package was improperly installed in the main project?
Debugging autoload can be very useful to catch errors. Take care with the route paths, the autoload tryed to find the classes files in a path with a lowercase folder when in the package composer.json the route was definded with that folder uppercase.

How to create a monolithic Composer package with a built-in composer-plugin?

I want my package to ship with a built-in composer-plugin.
I have a structure like this:
composer.json
src/
...
plugin/
composer.json
src/
...
The root composer.json is configured like this:
{
"name": "foo/bar",
"type": "library",
"autoload": {
"psr-4": {
"Foo\\Bar\\": "src/"
}
},
"repositories": [
{
"type": "path",
"url": "./tools",
"options": {
"symlink": false
}
}
],
"require": {
"foo/bar-plugin": "*"
}
}
And the built-in composer-plugin's plugin/composer.json like this:
{
"name": "foo/bar-plugin",
"type": "composer-plugin",
"require": {
"composer-plugin-api": "^1",
"composer/composer": "^1",
"foo/bar": "*"
},
"autoload": {
"psr-4": {
"Foo\\Bar\\Plugin\\": "src/"
}
},
"extra": {
"class": "Foo\\Bar\\Plugin\\MyComposerPlugin"
}
}
Notice how there's a two-way dependency here - the plugin depends on foo/bar, and the project itself depends on foo/bar-plugin.
Here's where it gets weird. During a fresh installation with e.g. composer install or composer update, everything is fine - the plugin does it's thing, which, right now, means just announcing itself on the console.
Now, after installation, if I type just composer, I'd expect to see the plugin announce itself, same as before, right?
Instead, it generates a fatal "class not found error", as soon as it tries to reference any class belonging to the foo/bar package.
It's as though composer lost track of the fact that foo/bar-plugin requires foo/bar, and for some reason it's classes aren't auto-loadable.
Is there any reason this shouldn't be possible? Why not?
Of course I can just package this stuff in separate external package, but that isn't going to make much sense, since these packages are just going to depend on each other - they're effectively one unit, a packaging them as two packages is going to result in a mess of major version increases with every small change, as basically every release of foo/bar will break foo/bar-plugin.
Ideally, I'd like to simply add the composer-plugin directly into the main package, but it appears that's not possible for some reason? Only a package with type composer-plugin is allowed to add plug-ins, it seems?
If the plugin is essentially a part of your package, you should not use it as such. Composer offers alternatives.
As Jens mentioned in a comment to your question, there is 'scripts' key in composer.json. You can invoke shell commands inside, but also call static class methods.
About plugin solution - composer explicitly mentions this on its site:
Composer makes no assumptions about the state of your dependencies prior to install or update. Therefore, you should not specify scripts that require Composer-managed dependencies in the pre-update-cmd or pre-install-cmd event hooks. If you need to execute scripts prior to install or update please make sure they are self-contained within your root package.
(my side note - this also roughly applies to plugins).
Anyway - to provide you with a solution: discard 'plugin' approach. Instead modify your composer.json file so it looks as follows:
composer.json
{
"name": "foo/bar",
"type": "library",
"autoload": {
"psr-4": {
"Foo\\Bar\\": "src/"
}
},
"require": {
},
"scripts": {
"post-install-cmd": [
"Foo\\Bar\\Composer\\Plugin::postInstall"
],
"post-update-cmd": [
"Foo\\Bar\\Composer\\Plugin::postUpdate"
]
}
}
Additionally, in src/Composer folder create Plugin.php:
src/Composer/Plugin.php
<?php
namespace Foo\Bar\Composer;
use Foo\Bar\Test;
/**
* Composer scripts.
*/
class Plugin
{
public static function postInstall()
{
print_r("POST INSTALL\n");
print_r(Test::TEST_CONST);
print_r("\n");
}
public static function postUpdate()
{
print_r("POST UPDATE\n");
print_r(Test::TEST_CONST);
print_r("\n");
}
}
As you see, it prints constant from Test class. Create it in src/:
src/Test.php
<?php
namespace Foo\Bar;
/**
* Test class.
*/
class Test
{
const TEST_CONST = "HERE I AM";
}
Run this and check, how it plays out.

New composer package that uses Guzzle - cant find it

I'm learning how to make a composer package. So far I've done this:
composer.json
{
"name": "Iv/MyPackage",
"autoload": {
"psr-4": {
"Iv\\MyPackage\\": "src/"
}
},
"require": {
"guzzlehttp/guzzle": "~6.0"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"guzzlehttp/guzzle": "~6.0"
},
"autoload-dev": {
"psr-4": {
"Iv\\MyPackage\\Tests\\": "tests/"
}
}
}
And I have a class under the namespace: namespace Iv\MyPackage\Api; called Consumer and in its __construct method it has this:
public function __construct(array $credentials)
{
$this->client = new Client();
$this->credentials = $credentials;
}
And on the top of that class I have use GuzzleHttp\Client;.
The error I'm getting is:
Fatal error: Class 'GuzzleHttp\Client' not found in path\to\package\Iv\MyPackage\src\Api\Consumer.php on line 27 when I do:
$package = new Iv\MyPackage\Api\Consumer(['user', 'password']);
$query = $api->prepare('/api-endpoint', 'GET');
Edit:
This is what my Consumer class looks like:
<?php
namespace Iv\MyPackage\Api;
use GuzzleHttp\Client;
class Consumer
{
private $credentials = [];
public function __construct(array $credentials)
{
$this->client = new Client();
$this->credentials = $credentials;
}
...
}
Also I'm using PhpStorm, which tells me that I have the GuzzleHttp package, because it autoimports it for me when I type Client() and press ALT + ENTER. Which means I have ran composer install/update.
Edit 2:
I have a file - index.php which has the following:
<?php
include('vendor/autoload.php');
$api = new Iv\MyPackage\Api\Consumer(['user', 'password']);
$query = $api->prepare('/endpoint', 'GET');
var_dump($api->execute($query));
The folder structure is as it follows:
-Iv/
--MyPackage/
---src/
----Api/
----Exceptions/
----vendor/
----tests/
----otherfiles (composer.json, phpunit.xml, etc)
-vendor/
-composer.json
-index.php (I mentioned above)
Edit 3:
The content of my vendor/composer/autoload_psr4.php:
<?php
// autoload_psr4.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
'Iv\\MyPackage\\Tests\\' => array($baseDir . '/tests'),
'Iv\\MyPackage\\' => array($baseDir . '/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
);
Edit 4:
In the folder where is index.php I have this composer:
{
"name": "Test MyPackage",
"autoload": {
"psr-4": {"Iv\\MyPackage\\": "Iv/MyPackage/src"}
}
}
^^ I saw that on SO in a topic that OP was asking how to test his package before uploading it to packagist/git. I can load the Consumer class, no errors about it, but every dependency is not found. (GuzzleHttp\Client, Symfony\Component\Yaml\Parser, etc)
In the composer.json in the main directory you are missing a reference to your package. You are just autoloading it, but you are not autoloading all of its dependencies.
To do this you have to add a section like this in your composer.json in the main directory:
"repositories": [
{
"url": "path/to/your/package",
"type": "path"
}
]
and add it to the require section, some thing like:
"require": {
"Iv/MyPackage" : "dev-master"
}
Alternatively, you could just move your dependencies from the composer.json inside your package to the one in the main directory
I think you pretty much mixed everything up a bit, but it is solvable.
What I see is that you have an odd directory structure: You have Iv/MyPackage/src/... in your main project (i.e. on the same level there is a file composer.json).
IF you want to use your "Iv/MyPackage" as a Composer package in your main project, you have to add it as a dependency. This means that your package name must appear inside a "require"-Section in the main composer.json file. Currently it is not. Instead of this, you manually added autoloading for the path your package is currently in - without telling Composer that this should be treated as a package, and that it has dependencies that should also be downloaded.
You added the dependencies of your package inside the directory of it, but this is not how Composer works.
To fix it, you have to do two simple things:
Add your package as a dependency in your main composer.json.
If your package is currently hosted in a private repository, you have to add it's URL to the repositories key in composer.json.
The first one is just this:
{
"name": "Test MyPackage",
"require": {
"Iv/MyPackage": "dev-master"
},
"autoload": {
}
}
Remove the autoloading for your package - it is contained inside that package already. If you want Composer to autoload your main project, add that autoloading here (and I suggest you do, because you'd probably cannot write a better autoloader yourself).
Second thing is adding the repository:
{
"name": "Test MyPackage",
"repositories": [
{
"type": "vcs",
"url": "your repo url, either http or ssh"
}
],
"require": {
"Iv/MyPackage": "dev-master"
},
"autoload": {
}
}
This tells Composer to look into that repository and see if it can find something. Everything found is added to the collection of known packages (with the default source being Packagist) and used to select the best version matches.
Some corrections you should apply:
Package names should be lower case, so it's better to use iv/mypackage.
If you give a name in your main project, it has to follow the form <vendor>/<package>, not be some random string with a name. Composer will not deny working, but once you get there you will benefit from more of the available infrastructure in the Composer area, and correct names will help you. Otherwise, just leave it out for the moment.
This should set you up and running. I probably can answer more details you didn't even ask, but suggest you read about Composer on the documentation page or here on Stackoverflow, because the common problems usually have already been solved.

Issue with Composer's Autoloader

I'm starting work on a new mini-framework project, which I have in a local GIT repo on my machine. I've set up a test project that pulls in the local repo via Composer, however the autoloader isn't working as expected (Fatal Error: Class X not found errors). This is the first time I've used autoloading outside of what is automatically generated (e.g. when using an existing framework) and despite reading around, I can't seem to solve this.
Package
In an attempt to get this working, the package only contains a src directory with a single App.php class on top of the composer.json file in the root.
composer.json
{
"name": "myvendor/framework",
"description": "Framework Description",
"license": "MIT",
"authors": [
{
"name": "Joe Bloggs",
"email": "joe#email.com"
}
],
"autoload": {
"psr-0": {
"Framework": "src/"
}
}
}
Project
composer.json
{
"repositories": [
{
"type": "vcs",
"url" : "../Framework"
}
],
"require": {
"myvendor/framework": "dev-master"
}
}
This successfully clones the local repo and adds the code to the vendor directory.
The namespace is also successfully added to Composer's autoload_namespaces.php file like so;
vendor/composer/autoload_namespaces.php
'Framework' => array($vendorDir . '/myvendor/framework/src'),
When I attempt to load the App class however using the following code, I get the error;
web/index.php
<?php
require_once '../vendor/autoload.php';
$app = new \Framework\App();
You're using the psr-0 specification for the class loader. This means that the full namespace has to be visible in the file structure. The prefix only tells the autoloader were to look for this namespace.
So in your case, you configured that the "Framework" namespace is available in the "src/" directory. This means that the class \Framework\App should life in src/Framework/App.php. In your case, it exists in src/App.php. This means that the autoloader cannot find your class.
However, there is a class loader specification that does what you want: psr-4. This is also the recommended specification (psr-0 might be removed in the future). With PSR-4, the file structure only includes the namespaces after the configured prefixes. So when doing "psr-4": { "Framework\": "src/" }, a class called \Framework\App should life in src/App.php and a class called \Framework\Some\Special\App should life in src/Some/Special/App.php.

Symfony2 Composer Bundle Namespace

I'm trying for a while to import an own bundle via composer but I got a few problems. I got following bundle:
<?php
namespace Platform\Bundle\PollBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class PlatformPollBundle extends Bundle
{
}
The bundle is in vendor/platform/pollbundle/.
In the "main" composer.json I definied the namespace for autoloading:
"autoload": {
"psr-0": {
"": "src/" ,
"Platform\\": "vendor/platform"
}
},
and in the composer.json from the bundle I definied:
{
"name" : "platform/pollbundle",
"type": "symfony-bundle",
"extra": {
"servicePath": ""
},
"autoload": {
"psr-0": {
"Platform\\Bundle\\PollBundle": ""
}
},
"target-dir": "pollbundle"
}
In the autoload_namespaces there is correctly following line:
'Platform\\' => array($vendorDir . '/platform'),
But I got the error:
Fatal error: Class 'Platform\Bundle\PollBundle\PlatformPollBundle' not found in ........Controller.php on line 13
I tried about 100 solutions but nothing works. Would be great if somebody can help me.
Bundles aren't loaded by composer, but instead are handled by the Symfony kernel itself. In the app directory, edit AppKernel.php like this:
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
...,
new Platform\Bundle\PollBundle\PlatformPollBundle()//<-- add this
);
}
In the app/autoload.php file, meanwhile, register the new namesepace. It used to be done through the $loader instance, by calling $loader->registerNameSpaces(), but now, you have to call a static method on the AnnotationRegistry class:
AnnotationRegistry::registerAutoloadNamespace('PollBundle', 'path/to/PollBundle');
A hacky fix I suggested, which is apparently what fixed it for you, would be to run php app/console generate:bundle in the console, to generate a new bundle with the same name, and then simply replace that bundle's directory (in src/) with your bundle.
It is wrong to define ANY autoloading in the main application for anything pointing into the vendor folder! That's what composer is for. Composer will read the autoload declaration for every package contained in there and add the appropriate autoloading automatically. There is no need to add this yourself.
And even if you have to use software that didn't yet add a composer.json file, the autoloading of only that package should go into the package definition block, not into the autoload definition.

Categories