Workaround for doctrine generator in PSR-4 codebase - php

With Symfony 2 & Doctrine on a Windows machine I'm trying to
generate entities from an existing schema:
php app/console doctrine:mapping:import --force CoreBundle annotation
generate getters/setters on them:
php app/console doctrine:generate:entities --path=/path/to/codebase/src/MyProject/CoreBundle/Entities CoreBundle
generate REST CRUD controllers on them using Voryx:
php app/console voryx:generate:rest --entity="CoreBundle:User"
The first steps works fine and I can find the entities in my CoreBundle/Entity folder with the correct namespace:
MyVendor\MyProject\CoreBundle\Entity
Good so far.
However, running the other 2 commands will fail:
[RuntimeException]
Can't find base path for "CoreBundle" (path:
"\path\to\codebase\src\MyProject\CoreBundle", destination:
"/path/to/codebase/src/MyProject/CoreBundle").
The autoload in my composer.json looks like this:
"autoload": {
"psr-4": {
"MyVendor\\": "src/"
}
},
I found out that Doctrine can't deal with PSR-4 namespaces, that's probably what makes it fail.
I would really like the entities to live in the PSR-4 CoreBundle though - is there a workaround for it?
I tried this, but it doesn't work, either:
"autoload": {
"psr-0": {
"MyVendor\\MyProject\\CoreBundle\\Entity": "src/MyProject/CoreBundle/Entity/"
},
"psr-4": {
"MyVendor\\": "src/"
}
},
Thank you.

User janvennemann on GitHub fixed Doctrine for PSR-4. You can find the patch on Gist, or linked here below
Step to fix it
mkdir -p app/VendorOverride;
cp vendor/doctrine/doctrine-bundle/Mapping/DisconnectedMetadataFactory.php app/VendorOverride/DisconnectedMetadataFactory.php;
apply the DisconnectedMetadataFactory patch;
add app/VendorOverride to the classmap section in composer.json;
run composer dump-autoload.
Then almost all scaffolding command works.
DisconnectedMetadataFactory PSR-4 patch
/**
* Get a base path for a class
*
* #param string $name class name
* #param string $namespace class namespace
* #param string $path class path
*
* #return string
* #throws \RuntimeException When base path not found
*/
private function getBasePathForClass($name, $namespace, $path)
{
$composerClassLoader = $this->getComposerClassLoader();
if ($composerClassLoader !== NULL) {
$psr4Paths = $this->findPathsByPsr4Prefix($namespace, $composerClassLoader);
if ($psr4Paths !== array()) {
// We just use the first path for now
return $psr4Paths[0];
}
}
$namespace = str_replace('\\', '/', $namespace);
$search = str_replace('\\', '/', $path);
$destination = str_replace('/'.$namespace, '', $search, $c);
if ($c != 1) {
throw new \RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination));
}
return $destination;
}
/**
* Gets the composer class loader from the list of registered autoloaders
*
* #return \Composer\Autoload\ClassLoader
*/
private function getComposerClassLoader() {
$activeAutloaders = spl_autoload_functions();
foreach($activeAutloaders as $autoloaderFunction) {
if (!is_array($autoloaderFunction)) {
continue;
}
$classLoader = $autoloaderFunction[0];
if ($classLoader instanceof \Symfony\Component\Debug\DebugClassLoader) {
$classLoader = $classLoader->getClassLoader()[0];
}
if (!is_object($classLoader)) {
continue;
}
if ($classLoader instanceof \Composer\Autoload\ClassLoader) {
return $classLoader;
}
}
return NULL;
}
/**
* Matches the namespace against all registered psr4 prefixes and
* returns their mapped paths if found
*
* #param string $namespace The full namespace to search for
* #param \Composer\Autoload\ClassLoader $composerClassLoader A composer class loader instance to get the list of psr4 preixes from
* #return array The found paths for the namespace or an empty array if none matched
*/
private function findPathsByPsr4Prefix($namespace, $composerClassLoader) {
foreach ($composerClassLoader->getPrefixesPsr4() as $prefix => $paths) {
if (strpos($namespace, $prefix) === 0) {
return $paths;
}
}
return array();
}

If somebody comes across this issue.. I got it finally working. I'm not quite sure what exactly fixed it, so here are all the steps I did:
As I was running Symfony 2.3, first I upgraded to 2.7
I re-generated the bundle from scratch... I changed the location of the bundle to MyProject\CoreBundle and renamed the bundle class to MyProjectCoreBundle.
I can now run all of these commands successfully:
php app/console doctrine:mapping:import --force MyProjectCoreBundle annotation
php app/console doctrine:generate:entities MyProjectCoreBundle
php app/console doctrine:generate:form MyProjectCoreBundle:User
php app/console voryx:generate:rest --entity=MyProjectCoreBundle:User
(Note that the call of doctrine:generate:form was not in the OP.)
My best guess is that one step of the upgrade was to change the composer autoload - this, or the 2.7 autoloader, seems to have fixed it:
"autoload": {
"psr-4": { "": "src/", "SymfonyStandard\\": "app/" }
},

Related

Composer - modifying autoloading SRC at run time based on PHP version

I was wondering if there is a way to load different folder based on php version. I could do it writing my own autoloader, but i was wondering if there is a way of using composer for that?
Ps. I have seen this in use before in module / plugin application that where redistributed globally to work with wide range of env. Those scripts where using own autoloading classes.
I am curios is there a way to use composer in similar way.
Scenario: class / folder structure:
class >
php5.6 >
- SomeClass.php
...
php7.x >
- SomeClass.php
...
php8.x >
- SomeClass.php
...
Compare php version and do something:
$classPathForAutoloader = '';
if (version_compare(PHP_VERSION, '8.0.0') >= 0) {
$classPathForAutoloader = 'php8.x';
// do something to composer autoload or
// use declaration
}else if(version_compare(PHP_VERSION, '7.0.0') >= 0){
$classPathForAutoloader = 'php7.x';
// do something to composer autoload or
// use declaration
}else if(version_compare(PHP_VERSION, '5.6.0') >= 0{
$classPathForAutoloader = 'php5.6';
// do something to composer autoload or
// use declaration
}else{
// throw Exception ...
}
standard composer setup:
{
"name": "some/name",
"require": {
},
"authors": [
{
"name": "Some Name",
"email": "some#email.com"
}
],
"autoload": {
"psr-4": {
"Devwl\\": "class/",
"Tools\\": "tools/"
},
"classmap": [
"class/"
],
"exclude-from-classmap": []
}
}
I don't think Composer provides a way to dynamically autoload different paths.
Can you use version_compare in a single SomeClass.php class only where the functionality differs by PHP version? Or write the entire SomeClass.php to be backwards compatible?
To me loading different classes depending on the PHP version is asking for trouble when it comes to reproducibility across environments.
Another option would be to use require_once to load the different classes, but for maintainability I'd really lean towards a single class with version checks only when absolutely necessary.
So i have created a class which does what I need. It can laso work alongside Composer autoloader.
<?php
class PhpVersionAutoloader{
private $baseClassesDirPath = null;
private $phpVersionArr = [];
private $phpDir = null;
private $classes = []; /** Keep a record of all loaded classes */
/**
* Allows to change base dir path.
* If not set the path will be set to file _DIR_
*
* #see $this->baseClassesDirPath
*
* #param string $path
* #return void
*/
public function setBaseClassesDirPath($path)
{
$this->baseClassesDirPath = $path;
}
/**
* Map available dir name and php version
*
* #see $this->phpVersionArr
*
* #param string $directory name
* #param string $phpVersion
* #return void
*/
public function registerPhpDir($dir, $phpVersion){
$this->phpVersionArr[] = [$dir => $phpVersion];
}
/**
* Compare curent php version with $this->phpVersionArr to determin the right path for class load
*/
public function selectPhpDir(){
foreach ($this->phpVersionArr as $key => $phpVDir) {
$this->position = $key;
foreach($phpVDir as $key => $value){
if (version_compare(PHP_VERSION, $value) >= 0){
$this->phpDir = $key;
break 2;
}
}
}
}
/**
* Register autloader
*
* #return void
*/
public function register(){
spl_autoload_register(function($className)
{
$namespace = str_replace("\\","/",__NAMESPACE__);
$className = str_replace("\\","/",$className);
$this->baseClassesDirPath = ($this->baseClassesDirPath === null) ? str_replace("\\","/",__DIR__) : $this->baseClassesDirPath;
$class = $this->baseClassesDirPath."/classes/".$this->phpDir.'/'. (empty($namespace)?"":$namespace."/")."{$className}.php";
$this->classes[] = $class;
if (file_exists($class)){
include_once($class);
}else{
// ... if not exsist try to load lower php version file?
// ... or throw new Error("Error Processing Request", 1);
}
});
}
}
Use the PhpVersionAutoloader object like this:
/**
* Use example
*/
$loader = new PhpVersionAutoloader(); // init PhpVersionAutoloader object
$loader->setBaseClassesDirPath('C:/xampp/htdocs/blog/blog autoloading'); // if not used will use _DIR_ to create path
$loader->registerPhpDir('php8.x', '8.0.0'); // as "folder name" => "php version"
$loader->registerPhpDir('php7.x', '7.0.0'); // ...
$loader->registerPhpDir('php5.6', '5.6.0'); // ...
$loader->selectPhpDir(); // compare system php version and selects the correct phpX.X subfolder
$loader->register(); // register autoloader
I also created more functional class of this loader which allow to force specific directory to load from or even allow to load classes from older version of PHP if not found in selected version.
See github [here]

PSR4 not working?

Class not found, apparently. I've tried various things but nothing works.
Composer:
"autoload": {
"psr-4": {
"App\\": "application/"
}
}
File structure:
https://i.imgur.com/h9wOEqI.png
<?php
namespace App\Library\Classes;
defined('START') or exit('We couldn\'t process your request right now.');
class Application
{
private static $libraries = array();
public static function get($library) {
if (isset(self::$libraries[$library]) && isset(self::$classes[$library])) {
return self::$libraries[$library];
}
$fixedLibrary = str_replace('.', '/', $library);
$file = ROOT . '/application/library/classes/' . strtolower($fixedLibrary) . '.php';
self::$libraries[$library] = $library;
$declared = get_declared_classes();
$workingClass = end($declared);
self::$libraries[$library] = new $workingClass();
return self::$libraries[$library];
}
}
?>
Error is on this line:
Application::get('test')->test();
Yet, if I change it to this, it works:
include ROOT . '/application/Library/Application.php';
App\Library\Classes\Application::get('test')->test();
The PSR4 is not built-in part or PHP, you need an implementation of autoloader to use this standard such as provided by the Composer.
When you install or update depedencies, composer generates the relevant code of autoloading, but you can directly update it by the command dump-autoload, as #jibsteroos said. Next you should explicitly include the file vendor/autoload.php in the entry point of your application.
Also, error message says about class Application, but you should add the use statement at first:
use App\Library\Classes\Application;
Application::get('test')->test();
Or use the fully qualified class name (class name with namespace prefix):
\App\Library\Classes\Application::get('test')->test();

Attempted to load class from namespace. Did you forget a "use" statement for another namespace? with vendor php namespace

I am trying to load a php namespace into my symfony project but keep getting the following error at runtime.
Attempted to load class "FM" from namespace "VehicleTracking\Src\Vendors\FM".
Did you forget a "use" statement for another namespace?
The controller that it is being called from
namespace BWT\FMBundle\Controller;
use VehicleTracking\Src\Vendors\FM\FM;
class FMController extends Controller
{
/**
* #Route("/fuel_data", name="fuelData")
* #return \Symfony\Component\HttpFoundation\Response
*/
public function fuelDataAction(Request $request)
{
//...
$tripProcesses = new FM(); //<-this is the line where I get the error
$_results = $tripProcesses->getTripWithTotals($form->get('fleetNo')->getData(), $form->get('startDate')->getData(), $form->get('endDate')->getData());
}
}
The FM.php file. which is in the directory vendor/bwt/vehicle_tracking/src/vendors
tracking.interface and tracking.class are in the same directory
<?php
namespace VehicleTracking\Src\Vendors\FM;
// : Includes
include_once (dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . 'tracking.interface');
include_once (dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . 'tracking.class');
// : End
use VehicleTracking\Src\Vendors\Vendors as Vendors;
use VehicleTracking\Src\Vendors\TrackingInterface as TrackingInterface;
class FM extends Vendors\Vendors implements TrackingInterface\TrackingInterface
{
public function getTrackingData()
{...}
}
autoload_namespace.php
<?php
// autoload_namespaces.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
//...
'' => array($vendorDir . '/bwt/vehicle_tracking/src/vendors'),
);
We eventually solved this by adding
"autoload" : {
"psr-4" : {
"Vendors\\" : "src/"
}
},
to the composer.json of the external package and changed the namespace of the classes to namespace Vendors; so that it would be the same as the directory.
I have to run composer update -o to optimise the autoloader otherwise I keep getting these errors.
As a rule you should avoid editing anything under vendor. Your changes will be lost. In this case you can edit your projects composer.json file
"autoload": {
"psr-4": {
"": "src/",
"VehicleTracking\\Src\\Vendors\\FM\\": "vendor/bwt/vehicle_tracking/src/vendors"
},
After making changes, run composer dump-autoload to update the autoload stuff.
The path I gave is based on your question, at least that was the intent. It assumes that FM.php is located directly under vendor/bwt/vehicle_tracking/src/vendors
I only tested a fake FM.php class. That fact that there are include statements in there and some other strange code might generate additional errors.

Composer Autoload Multiple Files in Folder

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

Get filesystem path of installed composer package

How can get filesystem path of composer package?
composer.json example:
{
"require" : {
"codeception/codeception" : "#stable",
"willdurand/geocoder": "*"
}
}
example:
$composer->getPath("\Geocoder\HttpAdapter\HttpAdapterInterface");
and return it as:
"/home/me/public_html/vendor/willdurand/geocoder/src/Geocoder/HttpAdapter"
All of this is based on the assumption that you are actually talking about packages and not classes (which are mentioned in the example but are not asked for in the question).
If you have the Composer object, you can get the path of the vendor directory from the Config object:
$vendorPath = $composer->getConfig()->get('vendor-dir');
$vendorPath should now contain /home/me/public_html/vendor/.
It shouldn't be too hard to construct the rest of the path from there, as you already have the package name.
If this feels too flaky or you don't want to write the logic, there is another solution. You could fetch all packages, iterate until you find the right package and grab the path from it:
$repositoryManager = $composer->getRepositoryManager();
$installationManager = $composer->getInstallationManager();
$localRepository = $repositoryManager->getLocalRepository();
$packages = $localRepository->getPackages();
foreach ($packages as $package) {
if ($package->getName() === 'willdurand/geocoder') {
$installPath = $installationManager->getInstallPath($package);
break;
}
}
$installPath should now contain /home/me/public_html/vendor/willdurand/geocoder
Try ReflectionClass::getFileName - Gets the filename of the file in which the class has been defined.
http://www.php.net/manual/en/reflectionclass.getfilename.php
Example:
$reflector = new ReflectionClass("\Geocoder\HttpAdapter\HttpAdapterInterface");
echo $reflector->getFileName();
Or you may use this:
$loader = require './vendor/autoload.php';
echo $loader->findFile("\Geocoder\HttpAdapter\HttpAdapterInterface");
The first method try to load class and return loaded class path. The second method return path from composer database without class autoload.
Here is my solution to get the vendor path without using the $composer object :
<?php
namespace MyPackage;
use Composer\Autoload\ClassLoader;
class MyClass
{
private function getVendorPath()
{
$reflector = new \ReflectionClass(ClassLoader::class);
$vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName() );
if($vendorPath && is_dir($vendorPath)) {
return $vendorPath . '/';
}
throw new \RuntimeException('Unable to detect vendor path.');
}
}
Not sure if the following is the correct way for this because composer is changing so fast.
If you run this command:
php /path/to/composer.phar dump-autoload -o
it will create a classmap array in this file
vender/composer/autoload_classmap.php
with this format "classname" => filepath.
So to find filepath of a given class is simple. If you create the script in your project's root folder, you can do this:
$classmap = require('vender/composer/autoload_classmap.php');
$filepath = $classmap[$classname]?: null;

Categories