I'm currently developing on Windows with WampServer and have Composer working (with OpenSSL), with Propel installed without issue, and everything seems to work fine. However, my project now needs to make use of the Equal Nest Behaviour found here.
I thought this would allow me to use the propel behaviour. In my schema.xml I have the following snippet:
<table name="friend">
<behavior name="equal_nest">
<parameter name="parent_table" value="user" />
</behavior>
</table>
But when I run propel-gen sql I get the error:
[phingcall] Unknown behavior "equal_nest"; make sure you configured the propel.be
havior.equal_nest.class setting in your build.properties
The documentation says:
Then, if you don't use Composer, or an autoloader in your application, add the following configuration to your build.properties or propel.ini file:
Making me presume that I didn't have to put in the build.properties file. However, putting it in gives me the following error:
PHP Fatal error: Class 'EqualNestParentBehavior' not found in C:\home\movesleag
ue.com\vendor\craftyshadow\propel-equalnest-behavior\src\EqualNestBehavior.php o
n line 74
I wasn't sure if that was something to do with autoloading not working or namespaces (my schema has a namespace, but I get this same error when removing it too).
My composer.json file looks like this:
{
"require": {
"craftyshadow/propel-equalnest-behavior": "dev-master"
}
}
Note: I did have Propel in there itself, but as the equalnest behaviour requires it itself I'm just letting that do its job.
So, what's the correct way to use Propel behaviours with Composer, and if I'm doing it right, why do I see the errors above?
Updates
I added this line to the top of EqualNestBehaviour.php:
include __DIR__ . DIRECTORY_SEPARATOR . 'EqualNestParentBehavior.php';
And the SQL seems to be generated correctly without errors. However, changing that file doesn't seem clever to me! Could it be a problem with autoloading? Is there anything you can think of that I can do to test that?
I can confirm that using Equal Nest Behaviour in my actual Propel code works fine, using functions like addFriends() - this is with the above changes still in place.
In my autoload_namespaces.php file I have the following:
<?php
// autoload_namespaces.php generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
This is an autoloading issue.
Please check that you have
propel.behavior.equal_nest.class = vendor.craftyshadow.propel-equalnest-behavior.src.EqualNestBehavior
in your build.properties (for Propel).
Please check that the composer generated autoloader file is included during the bootstrap process of your application. Composer generates a "vendor/autoload.php" file. If you include it, then you get autoloading for free. And everything installed by Composer is found automatically.
require 'vendor/autoload.php';
Related
I have a package I am creating here
It's a standard composer PHP package with a Symfony command to generate Avro files.
When registering the package command in the bin/avro directory I add the following
require file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
This should, if my understanding is correct, autoload all files for a project where the package is loaded in.
I run the following in an empty Laravel project for example
composer require lukecurtis93/avrogenerate
./vendor/bin/avro generate
The code here which uses get_declared_classes() - excerpt:
// ...
$arr = [];
foreach (get_declared_classes() as $className) {
if (in_array(Avroable::class, class_implements($className))) {
$arr[] = $className;
}
}
// ...
- does not return any files stored in my App/Models directory for example which are in the Laravel App (or any others for that matter).
What am I doing incorrectly? Shouldn't these files be available from get_declared_classes()? Is there additional configuration I need to do for a package?
This should, if my understanding is correct, autoload all files for a project where the package is loaded in.
You have misunderstood what "autoload" means in this context. It doesn't mean "automatically find and load all classes at once"; it means "automatically load classes on demand".
There is a description of how this works in the official manual. The short version is that whenever you try to use a class that isn't defined yet, PHP passes the class name to a callback; that callback can do whatever it likes to define that class, normally loading a file based on some naming convention.
So, until you've tried to use a class, it will remain undefined, and get_declared_classes will not know about it.
The class_exists function will, by default, trigger the autoloader, which might be helpful to you. If you really need to dynamically list all the classes in a package, you'll need to find some other way of iterating the files on disk.
Is there additional configuration I need to do for a package?
IMSoP already explained the details for PHP autoload when you want to use get_declared_classes().
An alternative to that is to re-use the Composer classmap, however it requires to generate it, the --optimize argument of composer dump-autoload does this. Given the Composer Autloader has been dumped that way, the list of "declared" classes is quite easy to obtain:
$autoload = require file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
$classmap = $autoload->getClassMap();
Then $classmap is an associative array with the class-names as key and the (absolute) path (w/ relative segments) to the file.
This is with the downside that the autoloader must be dumped with a specific configuration. And with the benefit that you'd be ready in no time.
Better Reflection
The package roave/better-reflection provides a class-listing for composer.json and //vendor-dir/composer/installed.json without relying on the class-map of the Composer autoloader.
You could add it as a requirement and then it would be available with the autoloader.
Getting the list of classes is within their documentation1, it depends on the path to the project. You could make it an argument option for your utility as I don't know how to obtain the root project path from the autoloader instance, the following would assume the default vendor-dir configuration, YMMV:
$projectAutoload = file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
$autload = require $projectAutoload;
$projectRoot = dirname($projectAutload, 2);
It does not implement 100% of the Composer autoloader configuration, but I've not seen another component that does apart from Composer itself. Just FYI when you wonder in integration testing, it is in the 99.5% camp, you may not even miss anything at all in your use-case.
$astLocator = (new BetterReflection())->astLocator();
$reflector = new DefaultReflector(new AggregateSourceLocator([
(new MakeLocatorForComposerJsonAndInstalledJson)($projectRoot, $astLocator),
new PhpInternalSourceLocator($astLocator, new ReflectionSourceStubber())
]));
$classes = $reflector->reflectAllClasses();
$classNames = array_map(fn (ReflectionClass $rfl) => $rfl->getName(), $classes);
Better Reflection also is not the fastest horse in the stable. Have decent CPU and fast disk I/O. The benefit of it are its features and in your specific case you can use it for preview (get_declared_classes() stays empty, no autoloading involved, you may use the gathered information to do your inheritance checks without loading the files into PHP - I can imagine this is probably good to have for your utility).
Write it yourself
It is relatively easy to write an ad-hoc loading of all PHP files in the vendor folder so that get_declared_classes() has them afterwards. But with such a direct implementation you can easily run into fatal errors, which don't help with your cause, also if you want to adhere to the composer packages, you need to give it some love. That depends also on which packages (and package style) you want to support. I've not much about a clue of your project, so in case you may still want to try it for yourself, some pointers:
The Composer autoloader configuration is in their schema, additional information is available in the optimizing section for auto-loading.
Remember that a call of require or include returns the autoloader instance. The class it not #internal, which means the public interface is stable to use.
The other thing good to know is that in //vendor-dir/composer/installed.json you find a list of all installed packages and some details about the dev install.
The format/layout of the installed.json depends on the composer version, good to know if you want to support all Composer versions.
I'm not aware there is a composer configuration matrix / change-log over all versions. Make your own research.
There are four kind of autoloaders in Composer: psr-0, psr-4, classmap and files (this is documented).
File extensions are *.php and *.inc (this is documented).
classmap can have excludes, with * and ** globs, the later implicitly appended if no globs in the exclude option argument (this is documented).
Composer also contains the class scanner for class map creation. IIRC it is internal but looked relatively easy to extract as it is merely ~60 lines of straight forward code, most of it regex operations. The regexes and processing around looked quite battle-proven the times I checked. You may need this if you want to come close to --optimize / class-map generation as Composer does.
Expect to stumble over Symfony or Phpunit files occasionally.
Compare "Inspecting code and dependencies of a composer-based project"
I'm trying to make a composer package, but I'm struggling to setup the autoload to work in the project where I'm developing it.
I have two projects:
Project Foo (where I'm developing the package).
Project Bar (where I've installed the package: composer require myhandle/mypackage).
In Project Foo I (obviously) have to be able to use the package as well. But setting up the autoload in my own package is not working.
However... When I commit the changes to Github, update the package at Packagist and run composer update in Project Bar, then it works(?!).
And wierdly enough... If I do composer update from Project Foo (the project where it isn't working). So updating the package to it's current version (?), makes it start working.
So it must somehow be related to how I've setup the autoload.
A note:
I started the package by making a new (empty) folder in the vendor directory, and then build the package there. I figured it was smart, since it then would mirror how it would look had I composer required the package.
I don't know if this is bad practice, since Composer no longer 'are in control' of all files in the vendor directory.
There are tons of guides on how to make a composer package out there, - but non of them explains about a good way to structure the files in the project where the package is being developed.
Here's what I do to 'get the error' (in Project Foo):
Create new class file, such as: myhandle/mypackage/src/Test.php
Then I instantiate it like this: $test = new MyNamespace\MyPackageName\Test();
And then I get the error:
Fatal error: Uncaught Error: Class 'MyNamespace\MyPackageName\Test' not found
And this is what works in Project Bar (the very same code).
I can't find a guide on how to correctly setup autoload in the package I'm developing. I'm using this autoload file, that I found in another composer project. I've put it in the root of my project. It looks like this:
<?php
namespace MyNamespace\MyPackageName;
spl_autoload_register(function($cls) {
$cls = ltrim($cls, '\\');
if (strpos($cls, __NAMESPACE__) !== 0) {
return;
}
$classWithoutBaseNamespace = str_replace(__NAMESPACE__, '', $cls);
// Load files from 'src' directory based on their class name without
// the StoutLogic\AcfBuilder namespace.
$path = dirname(__FILE__).
DIRECTORY_SEPARATOR.
'src'.
str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutBaseNamespace).
'.php';
require_once($path);
});
I can't find it in the Composer Documentation, how to set it up in a new project/package. However I can find a bazillions guides on how to use autoload.
As yivi and Daniel Protopopov pointed out:
Check the documentation at getcomposer.org regarding autoloading
Delete your custom autoloader definition, register your namespace in composer.json (hoping you follow PSR-4 already), run composer dump-autoload.
Last but not least, when- and wherever you need to use it, just include the
require __DIR__ . '/vendor/autoload.php';
I've created a small command line tool in PHP and I've also packed that as a PHAR archive.
Next thing I did was publish my archive to packagist.org aka composer.
I can now install my PHAR package through composer like so:
composer global require acme/mypackage
This installs my package fine. And I'm able to run it through command as well.
So far so good, but here comes the problem I´m currently facing.
I have another project should use acme/mypackage. I want that project to reference a class that is packed into that PHAR. Something like this:
<?php
class SomeClass extends AcmeClass {
}
The problem is that the PHP code doesn't recognize the AcmeClass class. Makes sense, because it´s obviously "globally" installed somewhere on the system.
How do other libraries solve this issue? If I'm not mistaken then PHPUnit does something similar right?
How can I solve this issue?
You'll need to add a composer.json file to the root of your project:
The first (and often only) thing you specify in composer.json is the require key. You're simply telling Composer which packages your project depends on.
{
"require": {
"monolog/monolog": "1.0.*"
}
}
Next, you'll need to autoload your dependencies.
For libraries that specify autoload information, Composer generates a vendor/autoload.php file. You can simply include this file and you will get autoloading for free.
require 'vendor/autoload.php';
https://getcomposer.org/doc/01-basic-usage.md
I'm hoping someone can spot what I've forgotten to do. Here are my steps:
Downloaded and unpacked the ZendFramework-2.3.5 into /usr/share.
Updated include_path in my php.ini file to include '/usr/share/ZendFramework-2.3.5/library' per the INSTALL.md, and restarted Apache to confirm the path is set (now ".:/usr/share/php:/usr/share/ZendFramework-2.3.5/library").
Created a test script in my web document root (using the class 'CamelCaseToUnderscore' as an example):
use Zend\Filter\Word\CamelCaseToUnderscore;
$filter = new CamelCaseToUnderscore();
echo $filter->filter('BigsAndLittles');
...and I get the fatal error "class 'zend\filter\word\camelcasetoseparator' not found".
In order to do use Zend classes like this, do I need to do some additional configuration or create an autoloader or something to find them? Seems like this should have worked. If I include the CamelCaseToUnderscore.php file in a require_once statement, then I get a fatal error that it's parent class doesn't exist (CamelCaseToSeparator.php). What am I missing?
You can use require 'Zend/Mvc/Application.php' to test if your include path is correct, but you will need an autoloader:
http://framework.zend.com/manual/current/en/modules/zend.loader.standard-autoloader.html.
You can find an example here (lines 18-20):
https://github.com/zendframework/zf2/blob/master/demos/Zend/Feeds/consume-feed.php
I strongly suggest using composer as it will save you a lot of time troubleshooting your include paths, but it also allows you manage version better. It makes it easier for other developers and to deploy your code.
Starting with composer is very easy, just install it and create composer.json:
https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
Run:
composer require zendframework/zendframework
Composer will download all libraries to vendor folder and will generate an autoloader, all you have to do is to include
require 'vendor/autoload.php';
https://getcomposer.org/doc/01-basic-usage.md#autoloading
Most popular PHP frameworks use composer for managing dependencies:
https://github.com/zendframework/zf2/blob/master/composer.json
https://github.com/symfony/symfony/blob/2.7/composer.json
I am trying to use this composer package with a new project I am working on https://packagist.org/packages/activecollab/activecollab-sdk. However when i try and create a new class I keep getting the following errors.
Fatal error: Class 'ActiveCollab\Client' not found
The file that is throwing this error looks like this.
require "vendor/autoload.php";
new ActiveCollab\Client;
Which is just being used to test if the files are being loaded in properly. The composer.json of the file which I am trying to use looks like such. And I have a feeling the problem is in this file but I can't figure out what.
stuff...
"autoload": {
"psr-0": {
"ActiveCollab\\": "ActiveCollab"
}
}
...stuff
Also looking at the autload_namespaces.php file it is being generated as such.
<?php
// autoload_namespaces.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'ActiveCollab' => array($vendorDir . '/activecollab/activecollab-sdk/ActiveCollab'),
);
I have used psr-0 in some composer packages of my own and everything looks to be right except maybe the camel case in the namespace but i don't see this as being disallowed in the php proposal for psr-0.
Thanks for any help this has been driving me crazy.
The thing is: You cannot simply add a composer.json file with a random autoloading configuration and hope that it works - it actually has to match the naming scheme you are using. That is what this project got wrong, and nobody tested it. Which probably means nobody uses this library, and you can expect no support from the creators due to lack of interest.
But let's see how they react on my pull request to get things back to working again.
The composer config looks fine: Is it just the case that you omitted the leading \ from your class name?
new \ActiveCollab\Client;
You'll need that if your code is inside another namespace, as it will load it relative to the current namespace.
EDIT: I've just checked out that library, and even with the above fix, the autoloader wasn't quite working. The autoloader may also be broken due to the composer.json file for the library specifying a PSR0 autoloader, but using ".class.php" extensions (not PSR0 compatible). An autoload.php file is included with the library, so if you just require that file, you should be able to use the classes:
require 'vendor/activecollab/activecollab-sdk/ActiveCollab/autoload.php';
After doing this, I was able to use the class.