Get filesystem path of installed composer package - php

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;

Related

Is it possible to not use namespaces in some classies? I'm in trouble with autoload

I'm working with a old project. Classies have no namespacies.
Path structure:
-container // Container
--app
----model // all classies without namespace
----model_common // all classies used by other two projects without namespace
------Rede // new librarie with namespace
--------Exception
--------Service
----view
----controller
spl_autoload_register function is in a file named init_client.php, inside app path:
define('M_CMN', CLI_APP. 'model_common/'); // $_SERVER['DOCUMENT_ROOT'] . '/app/model_common/'
define('M_CLI', CLI_APP. 'model/'); // $_SERVER['DOCUMENT_ROOT'] . '/app/model/'
function autoload_client($class){
$path_and_class = str_replace('\\', DIRECTORY_SEPARATOR, $class); // May case there is a namespace
if (file_exists(M_CMN . "{$path_and_class}.php")): // First local to find class
require_once M_CMN . "{$path_and_class}.php";
elseif (file_exists(M_CLI . "{$path_and_class}.php")):
require_once M_CLI . "{$path_and_class}.php";
else :
ErrorFunction("Class {$path_and_class} was not found.",ERROR_1);
endif;
}
spl_autoload_register('autoload_client');
Example:
$consult = new DealConsult; // app/model
$consult->checkTransaction('123') // It will use Rede\Store, Rede\Environment and Rede\eRede classies
Error: Class Rede/Store was not found. But file $_SERVER['DOCUMENT_ROOT'] . '/app/model/Rede/Store.php' exist.
DealConsult.php class:
use Rede\Store;
use Rede\Environment;
use Rede\eRede;
class DealConsult {
public function checkTransaction($cod) {
$this->store = new Rede\Store($_SESSION['trans']['id_filiacao'], $_SESSION['trans']['token'], Rede\Environment::sandbox());
$this->transaction = (new Rede\eRede($this->store))->getByReference($cod);
printf("Autorization status: %s\n", $this->transaction->getAuthorization()->getStatus());
}
What am I not getting understand? I'm learning namespacies recently as well as PSR-4 defaults.
Use Composer to automatically load all the classes. In your composer.json file, you need to have:
{
"autoload": {
"psr-4": {
"Rede\\": "container/app/model_common/"
// Add as many classes as you need here in this map...
}
}
}
After that, run:
compose dump-autoload --optimize
Finally, include vendor/autoload.php where necessary.

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();

Creating phpunit.xml file in phpunit

I have such structure of my project:
Root
-- /src
-- /tests
In src are my file such a Task1.php. In tests folder are my tests files.
Example:
<?php
require __DIR__ . '/../src/Task1.php';
class Task1Test extends PHPUnit_Framework_TestCase
{
public function testTask1(){
$this->assertEquals([1,1,1,3,5,9],fib([1,1,1],6));
}
}
I include my src files using require __DIR__ . '/../src/Task1.php';
I want to add bootstrap.php file and include src files there. And then add bootstrap.php in phpunit.xml.
My phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" strict="true" bootstrap="bootstrap.php"></phpunit>
What should I write in bootstrap.php file?
I try add Autoloader.php file in tests folder:
<?php
class AutoLoader {
static private $classNames = array();
/**
* Store the filename (sans extension) & full path of all ".php" files found
*/
public static function registerDirectory($dirName) {
$di = new DirectoryIterator($dirName);
foreach ($di as $file) {
if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
// recurse into directories other than a few special ones
self::registerDirectory($file->getPathname());
} elseif (substr($file->getFilename(), -4) === '.php') {
// save the class name / path of a .php file found
$className = substr($file->getFilename(), 0, -4);
AutoLoader::registerClass($className, $file->getPathname());
}
}
}
public static function registerClass($className, $fileName) {
AutoLoader::$classNames[$className] = $fileName;
}
public static function loadClass($className) {
if (isset(AutoLoader::$classNames[$className])) {
require_once(AutoLoader::$classNames[$className]);
}
}
}
spl_autoload_register(array('AutoLoader', 'loadClass'));
And bootsstrap.php in tests folder:
<?php
include_once('AutoLoader.php');
// Register the directory to your include files
AutoLoader::registerDirectory(__DIR__ . '/../src/');
But it doesn't work
I've carved two versions for you.
See https://github.com/Grawer/starckoverflow-41881997
First (branch global-functions) is a version with globally declared functions, the very basic one. What was done is basically the require is moved from file containing test to bootstrap.php, and that's all.
Second version (branch master), with autoloader is more complicated, and I realized that you might hold on with doing it. Still, I was insisting so here it is. You was almost there. There was a typo in your composer.json as you wrote prs-4 and it is supposed to be psr-4. What was left was to add autoloader require to bootstrap.php, and you would also need to put fib function inside a class. Then you could access it, without needing to require that class in the test class file.
if you are having problems with auto loader, and tests not working, try to do what I done, and change your composer.json
to include the lines:
"autoload": {
"psr-4": {
"foo\\": "foo"
}
},
foo is the namespace. and the difference was I had psr-0 instead of psr-4.
Worked straight after!

how to use an autoloader in php

I'm new to PHP and not really familiar with using git.
I got this library:
https://github.com/CKOTech/checkout-php-library
and I wanna run the sample code here:
https://github.com/CKOTech/checkout-php-library/wiki/Tokens
I know the code may not work perfectly for you cuz you would need a secret key from the provider, however, I don't need general errors like " cannot find class ApiClient"
what I did is simply including the autoloader in my index.php file, is that all what I have to do to use an Autoloader? does it have to do anything with composer.json?
Thanks a ton for the help in advance.
Autoloader.php:
<?php
function autoload($className)
{
$baseDir = __DIR__;
$realClassName = ltrim($className, '\\');
$realClassName = str_replace('\\',DIRECTORY_SEPARATOR,$realClassName );
$fileName = '';
$includePaths = $baseDir.DIRECTORY_SEPARATOR.$realClassName. '.php';
if ( $file = stream_resolve_include_path($includePaths) ) {
if (file_exists($file)) {
require $file;
}
}elseif(preg_match('/^\\\?test/', $className)) {
$fileName = preg_replace('/^\\\?test\\\/', '', $fileName);
$fileName = 'test' . DIRECTORY_SEPARATOR . $fileName;
include $fileName;
} else {
$classNameArray = explode('_', $className);
$includePath = get_include_path();
set_include_path($includePath);
if (!empty($classNameArray) && sizeof($classNameArray) > 1) {
if (!class_exists('com\checkout\packages\Autoloader')) {
include 'com'.DIRECTORY_SEPARATOR.'checkout'.DIRECTORY_SEPARATOR.'packages'.DIRECTORY_SEPARATOR.'Autoloader.php';
}
}
}
}
spl_autoload_register('autoload');
If you want to use an autoloader to make your life measurably better:
Use namespaces/PSR4.
Use Composer.
So let's say I'm working on project foo, within my working directory [let's just say it's /] I make a folder named /src/ and inside is /src/FooClient.php. It contains:
<?php
namespace sammitch\foo;
class FooClient {}
While in / I run composer init and accept all of the defaults, because typing out the simple JSON config file that that generates is tedious. Now I have a composer.json that looks like:
{
"name": "Sammitch/foo",
"authors": [
{
"name": "Sammitch",
"email": "sammitch#sam.mitch"
}
],
"require": {}
}
All we need to do now is add a section to the end:
"autoload": {
"psr-4": {
"sammitch\\foo\\": "src/"
}
}
Now to make Composer do it's magic and make the autoloader just run composer dumpautoload. When this runs Composer will create the /vendor/ folder and the autoloader.
Now all we need to do is:
<?php
require('vendor/autoload.php');
use \sammitch\foo\Client as FooClient()
$c = new FooClient();
Now not only do you have a top-tier autoloader, but you're also set up to start using Composer packages and leveraging all that good stuff from Packagist.

Autoload classes from different folders

This is how I autoload all the classes in my controllers folder,
# auto load controller classes
function __autoload($class_name)
{
$filename = 'class_'.strtolower($class_name).'.php';
$file = AP_SITE.'controllers/'.$filename;
if (file_exists($file) == false)
{
return false;
}
include ($file);
}
But I have classes in models folder as well and I want to autoload them too - what should I do? Should I duplicate the autoload above and just change the path to models/ (but isn't this repetitive??)?
Thanks.
EDIT:
these are my classes file names in the controller folder:
class_controller_base.php
class_factory.php
etc
these are my classes file names in the model folder:
class_model_page.php
class_model_parent.php
etc
this is how I name my controller classes class usually (I use underscores and lowcaps),
class controller_base
{
...
}
class controller_factory
{
...
}
this is how I name my model classes class usually (I use underscores and lowcaps),
class model_page
{
...
}
class model_parent
{
...
}
I see you are using controller_***** and model_***** as a class naming convention.
I read a fantastic article, which suggests an alternative naming convention using php's namespace.
I love this solution because it doesn't matter where I put my classes. The __autoload will find it no matter where it is in my file structure. It also allows me to call my classes whatever I want. I don't need a class naming convention for my code to work.
You can, for example, set up your folder structure like:
application/
controllers/
Base.php
Factory.php
models/
Page.php
Parent.php
Your classes can be set up like this:
<?php
namespace application\controllers;
class Base {...}
and:
<?php
namespace application\models;
class Page {...}
The autoloader could look like this (or see 'a note on autoloading' at the end):
function __autoload($className) {
$file = $className . '.php';
if(file_exists($file)) {
require_once $file;
}
}
Then... you can call classes in three ways:
$controller = new application\controllers\Base();
$model = new application\models\Page();
or,
<?php
use application\controllers as Controller;
use application\models as Model;
...
$controller = new Controller\Base();
$model = new Model\Page();
or,
<?php
use application\controllers\Base;
use application\models\Page;
...
$controller = new Base();
$model = new Page();
EDIT - a note on autoloading:
My main auto loader looks like this:
// autoload classes based on a 1:1 mapping from namespace to directory structure.
spl_autoload_register(function ($className) {
# Usually I would just concatenate directly to $file variable below
# this is just for easy viewing on Stack Overflow)
$ds = DIRECTORY_SEPARATOR;
$dir = __DIR__;
// replace namespace separator with directory separator (prolly not required)
$className = str_replace('\\', $ds, $className);
// get full name of file containing the required class
$file = "{$dir}{$ds}{$className}.php";
// get file if it is readable
if (is_readable($file)) require_once $file;
});
This autoloader is a direct 1:1 mapping of class name to directory structure; the namespace is the directory path and the class name is the file name. So the class application\controllers\Base() defined above would load the file www/application/controllers/Base.php.
I put the autoloader into a file, bootstrap.php, which is in my root directory. This can either be included directly, or php.ini can be modified to auto_prepend_file so that it is included automatically on every request.
By using spl_autoload_register you can register multiple autoload functions to load the class files any which way you want. Ie, you could put some or all of your classes in one directory, or you could put some or all of your namespaced classes in the one file. Very flexible :)
You should name your classes so the underscore (_) translates to the directory separator (/). A few PHP frameworks do this, such as Zend and Kohana.
So, you name your class Model_Article and place the file in classes/model/article.php and then your autoload does...
function __autoload($class_name)
{
$filename = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class_name)).'.php';
$file = AP_SITE.$filename;
if ( ! file_exists($file))
{
return FALSE;
}
include $file;
}
Also note you can use spl_autoload_register() to make any function an autoloading function. It is also more flexible, allowing you to define multiple autoload type functions.
If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once.
Edit
Note : __autoload has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged. Please refer to PHP documentation for more details. http://php.net/manual/en/function.autoload.php
I have to mention something about "good" autoload scripts and code structure, so read the following CAREFULLY
Keep in Mind:
Classname === Filename
Only ONE class per file
e.g: Example.php contains
class Example {}
Namespace === Directory structure
e.g: /Path1/Path2/Example.php matches
namespace Path1\Path2;
class Example {}
There SHOULD be a Root-Namespace to avoid collisions
e.g: /Path1/Path2/Example.php with root:
namespace APP\Path1\Path2;
class Example {}
NEVER use manually defined path or directory lists, just point the loader to the top most directory
Keep the loader AS FAST AS POSSIBLE (because including a file is expensive enough)
With this in mind, i produced the following script:
function Loader( $Class ) {
// Cut Root-Namespace
$Class = str_replace( __NAMESPACE__.'\\', '', $Class );
// Correct DIRECTORY_SEPARATOR
$Class = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, __DIR__.DIRECTORY_SEPARATOR.$Class.'.php' );
// Get file real path
if( false === ( $Class = realpath( $Class ) ) ) {
// File not found
return false;
} else {
require_once( $Class );
return true;
}
}
Where to place it..
/Loader.php <-- there goes the loader
/Controller/... <-- put ur stuff here
/Model/... <-- or here, etc
/...
Remeber:
if you use a root namespace, the loader has to be in this namespace too
you may prefix $Class to match your needs (controller_base {} -> class_controller_base.php)
you may change __DIR__ to an absolute path containing your class files (e.g. "/var/www/classes")
if you don't use namespaces, all files has to be in the same directory together with the loader (bad!)
Happy coding ;-)
A little review at other answers:
THIS IS JUST MY PERSONAL OPINION - NO OFFENSE INTENDED!
https://stackoverflow.com/a/5280353/626731
#alex good solution, but don't make you class names pay for bad file structures ;-)
this is job for namespaces
https://stackoverflow.com/a/5280510/626731 #Mark-Eirich it works, but its pretty nasty/ugly/slow/stiff[..] style to do it this way..
https://stackoverflow.com/a/5284095/626731 #tealou for his problem to be solved this is the most clear approach so far :-) ..
https://stackoverflow.com/a/9628060/626731 #br3nt this reflects my point of view, but please(!) .. dont use strtr!! .. which brings me to:
https://stackoverflow.com/a/11866307/626731 #Iscariot .. to you, a little "you-know-bullshit-benchmark:
Time sprintf preg_replace strtr str_replace v1 str_replace v2
08:00:00 AM 1.1334 2.0955 48.1423 1.2109 1.4819
08:40:00 AM 1.0436 2.0326 64.3492 1.7948 2.2337
11:30:00 AM 1.1841 2.5524 62.0114 1.5931 1.9200
02:00:00 PM 0.9783 2.4832 52.6339 1.3966 1.4845
03:00:00 PM 1.0463 2.6164 52.7829 1.1828 1.4981
Average 1.0771 2.3560 55.9839 1.4357 1.7237
Method Times Slower (than sprintf)
preg_replace 2.19
strtr 51.97
str_replace v1 1.33
str_replace v2 1.6
Source: http://www.simplemachines.org/community/index.php?topic=175031.0
Questions?.. (But he is in fact right about full path including)
https://stackoverflow.com/a/12548558/626731 #Sunil-Kartikey
https://stackoverflow.com/a/17286804/626731 #jurrien
NEVER loop in time critical environment! Don't search for files on os! - SLOW
https://stackoverflow.com/a/21221590/626731 #sagits .. much better than Marks ;-)
function autoload($className)
{
//list comma separated directory name
$directory = array('', 'classes/', 'model/', 'controller/');
//list of comma separated file format
$fileFormat = array('%s.php', '%s.class.php');
foreach ($directory as $current_dir)
{
foreach ($fileFormat as $current_format)
{
$path = $current_dir.sprintf($current_format, $className);
if (file_exists($path))
{
include $path;
return ;
}
}
}
}
spl_autoload_register('autoload');
Here is my solution,
/**
* autoload classes
*
*#var $directory_name
*
*#param string $directory_name
*
*#func __construct
*#func autoload
*
*#return string
*/
class autoloader
{
private $directory_name;
public function __construct($directory_name)
{
$this->directory_name = $directory_name;
}
public function autoload($class_name)
{
$file_name = 'class_'.strtolower($class_name).'.php';
$file = AP_SITE.$this->directory_name.'/'.$file_name;
if (file_exists($file) == false)
{
return false;
}
include ($file);
}
}
# nullify any existing autoloads
spl_autoload_register(null, false);
# instantiate the autoloader object
$classes_1 = new autoloader('controllers');
$classes_2 = new autoloader('models');
# register the loader functions
spl_autoload_register(array($classes_1, 'autoload'));
spl_autoload_register(array($classes_2, 'autoload'));
I'm not sure whether it is the best solution or not but it seems to work perfectly...
What do you think??
My version of #Mark Eirich answer:
function myload($class) {
$controllerDir = '/controller/';
$modelDir = '/model/';
if (strpos($class, 'controller') !== false) {
$myclass = $controllerDir . $class . '.php';
} else {
$myclass = $modelDir . $class . '.inc.php';
}
if (!is_file($myclass)) return false;
require_once ($myclass);
}
spl_autoload_register("myload");
In my case only controller class have the keyword in their name, adapt it for your needs.
Simpliest answer I can give you without writing down those complex codes and even without using the namespace (if this confuses you)
Sample Code. Works 100%.
function __autoload($class_name){
$file = ABSPATH . 'app/models/' . $class_name . '.php';
if(file_exists($file)){
include $file;
}else{
$file = ABSPATH . 'app/views/' . $class_name . '.php';
if(file_exists($file)){
include $file;
}else{
$file = ABSPATH . 'app/controllers/' . $class_name . '.php';
include $file;
}
}
I guess the logic is explainable itself. Cheers mate! Hope this helps :)
Here's what I'd do:
function __autoload($class_name) {
$class_name = strtolower($class_name);
$filename = 'class_'.$class_name.'.php';
if (substr($class_name, 0, 5) === 'model') {
$file = AP_SITE.'models/'.$filename;
} else $file = AP_SITE.'controllers/'.$filename;
if (!is_file($file)) return false;
include $file;
}
As long you name your files consistently, like class_controller_*.php and class_model_*.php, this should work fine.
Everyone is is coping and pasting things from code they got off the internet (With the exception of the selected answer). They all use String Replace.
String Replace is 4 times slower than strtr. You should use it instead.
You should also use full paths when including classes with autoloading as it takes less time for the OS to resolve the path.
__autoload() function should not be use because it is not encourged. Use spl_autoload(), spl_autoload_register() instead. __autoload() just can load one class but spl_autoload() can get more than 1 classes. And one thing more, in future __autoload() may deprecated. More stuff can be find on http://www.php.net/manual/en/function.spl-autoload.php
Altough this script doesn't have the name convention and this thread is already a bit old, in case someone is looking of a possible answer, this is what I did:
function __autoload($name) {
$dirs = array_filter(glob("*"), 'is_dir');
foreach($dirs as $cur_dir) {
dir_searcher($cur_dir, $name);
}
}
function dir_searcher($cur_dir, $name) {
if(is_file("$cur_dir/$name.php")) {
require_once "$cur_dir/$name.php";
}
$dirs = array_filter(glob($cur_dir."/*"), 'is_dir');
foreach($dirs as $cdir) {
dir_searcher("$cdir", $name);
}
}
not sure if it is really optimal, but it searches through the folders by reading dir recursively. With a creative str_replace function you can get your name cenvention.
I use this. Basically define your folder structure (MVC etc) as a constant in a serialised array. Then call the array in your autoload class. Works efficiently for me.
You could obviously create the folder array using another function but for MVC you may as well type it in manually.
For this to work you need to call your classes ...... class.classname.php
//in your config file
//define class path and class child folders
define("classPath","classes");
define("class_folder_array", serialize (array ("controller", "model", "view")));
//wherever you have your autoload class
//autoload classes
function __autoload($class_name) {
$class_folder_array = unserialize (class_folder_array);
foreach ($class_folder_array AS $folder){
if(file_exists(classPath."/".$folder.'/class.'.$class_name.'.php')){require_once classPath."/".$folder.'/class.'.$class_name.'.php';break;}
}
}

Categories