How can I autoload helper functions (outside of any class)? Can I specify in composer.json some kind of bootstrap file that should be loaded first?
You can autoload specific files by editing your composer.json file like this:
"autoload": {
"files": ["src/helpers.php"]
}
(thanks Kint)
After some tests, I have came to the conclusions that adding a namespace to a file that contains functions, and setting up composer to autoload this file seems to not load this function across all the files that require the autoload path.
To synthesize, this will autoload your function everywhere:
composer.json
"autoload": {
"files": [
"src/greetings.php"
]
}
src/greetings.php
<?php
if( ! function_exists('greetings') ) {
function greetings(string $firstname): string {
return "Howdy $firstname!";
}
}
?>
...
But this will not load your function in every require of autoload:
composer.json
"autoload": {
"files": [
"src/greetings.php"
]
}
src/greetings.php
<?php
namespace You;
if( ! function_exists('greetings') ) {
function greetings(string $firstname): string {
return "Howdy $firstname!";
}
}
?>
And you would call your function using use function ...; like following:
example/example-1.php
<?php
require( __DIR__ . '/../vendor/autoload.php' );
use function You\greetings;
greetings('Mark'); // "Howdy Mark!"
?>
Related
I am trying to make my own mock MVC framework as a project. This is my first time using composer outside of using it for requiring dependencies for Laravel. The actual autoloading works well, but when I try to autoload the helpers.php something weird happens. The file is autoloaded(if I change the path of the file I get the file not found error) but the contents inside it are not. In another file I try to call any function from the helpers.php file and I get
Fatal error: Uncaught Error: Call to undefined function
This is the file structure of the example
composer.json
App
Utils
helpers.php
public
index.php
This is my composer.json file:
{
"name": "admin/projecttest",
"autoload": {
"psr-4": {
"Admin\\Projecttest\\": "src/",
"App\\": "App/"
},
"files": [
"App/Utils/helpers.php"
]
},
"minimum-stability": "dev"
}
The helpers.php
<?php
namespace App\Utils;
use Leonlav77\Frejmcore\helpers\DotEnv;
function config($config){
$config = explode(".", $config);
$file = $config[0];
$configFile = require "../config/$file.php";
return $configFile[$config[1]];
}
function env($key, $default = null){
(new DotEnv(__DIR__ . '../../.env'))->load();
return getenv($key) ? getenv($key) : $default;
}
function baseDir(){
return __DIR__ . "/../";
}
index.php (where I call the function from the helper)
<?php
require "../vendor/autoload.php";
var_dump(function_exists('baseDir'));
var_dump(baseDir());
from the function_exists I get false
As the user Foobar suggested the the problem was in the namespace of the helpers.php . Since it had a namespace the functions also had a namespace, so insted of baseDir() I needed to use App/Utils/baseDir().
The solution was to simply remove the namespace from helpers.php
I am trying to make my own mock MVC framework as a project. This is my first time using composer outside of using it for requiring dependencies for Laravel. The actual autoloading works well, but when I try to autoload the helpers.php something weird happens. The file is autoloaded(if I change the path of the file I get the file not found error) but the contents inside it are not. In another file I try to call any function from the helpers.php file and I get
Fatal error: Uncaught Error: Call to undefined function
This is the file structure of the example
composer.json
App
Utils
helpers.php
public
index.php
This is my composer.json file:
{
"name": "admin/projecttest",
"autoload": {
"psr-4": {
"Admin\\Projecttest\\": "src/",
"App\\": "App/"
},
"files": [
"App/Utils/helpers.php"
]
},
"minimum-stability": "dev"
}
The helpers.php
<?php
namespace App\Utils;
use Leonlav77\Frejmcore\helpers\DotEnv;
function config($config){
$config = explode(".", $config);
$file = $config[0];
$configFile = require "../config/$file.php";
return $configFile[$config[1]];
}
function env($key, $default = null){
(new DotEnv(__DIR__ . '../../.env'))->load();
return getenv($key) ? getenv($key) : $default;
}
function baseDir(){
return __DIR__ . "/../";
}
index.php (where I call the function from the helper)
<?php
require "../vendor/autoload.php";
var_dump(function_exists('baseDir'));
var_dump(baseDir());
from the function_exists I get false
As the user Foobar suggested the the problem was in the namespace of the helpers.php . Since it had a namespace the functions also had a namespace, so insted of baseDir() I needed to use App/Utils/baseDir().
The solution was to simply remove the namespace from helpers.php
im learning something about RedBeanPHP ORM and add the
code downloaded from http://www.redbeanphp.com/downloadredbean.php
to my project autoload using 'composer dump-autoload' command and
the configuration 'composer.json' in the root directory is:
{
"autoload": {
"classmap": [
"vendor/redbeanphp/src/rb.php",
"vendor/myowncode/src/Model.php"
]
}
}
on 'vendor/composer/installed.json' i put this:
[
{
"name": "gabordemooij/redbean",
"version": "5.4",
"require": {},
"autoload": {
"psr-4": {"RedBeanPHP\\": "src"}
}
},
"name": "myowncode/src",
"version": "1.0",
"require": {},
"autoload": {
"psr-4": {"MyCode\\": "src"}
}
}
]
and all works fine, at least until i try the example from the RedBean web
about 'Models' and the code:
<?php
require 'vendor/autoload.php';
class Model_Band extends RedBean_SimpleModel {
public function update() {
if ( count( $this->bean->ownMember ) >4 )
throw new Exception( 'Too many members!' );
}
}
results in error:
PHP Fatal error: Cannot declare class RedBeanPHP\RedException, because the name is already in use in /opt/lampp/htdocs/testing/vendor/redbeanphp/src/rb.php on line 8358
Fatal error: Cannot declare class RedBeanPHP\RedException, because the name is already in use in /opt/lampp/htdocs/testing/vendor/redbeanphp/src/rb.php on line 8358
but, if i dont use autoload and do this:
require 'vendor/redbean/src/rb.php';
class Model_Band extends RedBean_SimpleModel {
public function update() {
if ( count( $this->bean->ownMember ) >4 )
throw new Exception( 'Too many members!' );
}
}
it works, but i want that works with the autoload, i know, i can just open composer.json file and add the package name ("gabordemooij/redbean": "dev-master"), but i want to learn more about autoload and
get a good comprehension of whats wrong on my configuration/code.
The problem was the code from http://www.redbeanphp.com/downloadredbean.php its not
prepared for use with composer autoload, is some kind of amalgamation, all the code
in a single file, and i try downloading a release from:
https://github.com/gabordemooij/redbean/archive/v5.4.2.zip, i do the same process
to generate autoload, but we must edit the file loader on RedBeanPHP dir on the
release and change the REDBEANPHP_MAIN_DIR from phar://rb.phar/RedBeanPHP/
to vendor/redbean-5.4.2/RedBeanPHP/, i put the code on vendor/redbean-5.4.2,
and thats all problem solved :)
I am new to using composer and psr-0. I have tried a small app using composer and psr-0. I have used namespace to load a particular class. When i call a class using composer vendor/autoload I am getting class not found error.
My composer.json file:/var/www/html/silexapp/composer.json
{
"require": {
"silex/silex": "~2.0",
"symfony/console": "~2.6"
},
"autoload": {
"psr-0": {
"MyApp": "/silexapp/app"
}
}
}
My composer vendor autoload file: /var/www/html/silexapp/vendor/autoload.php
<?php
// autoload.php #generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitf7241d907c173a8d77da0791cc918856::getLoader();
My class file name Underline.php: /var/www/html/silexapp/app/Tnq/Todo/Command/Underline.php
<?php
namespace MyApp\Tnq\Todo\Command;
class Underline{
public function add($a,$b){
return $result = $a+$b;
}
}
?>
My another class file name Bold.php: /var/www/html/silexapp/app/Tnq/Todo/Command/Bold.php
<?php
require_once "../../../../vendor/autoload.php";
//require_once "Underline.php";
use MyApp\Tnq\Todo\Command as tool;
echo "this is the index file to check namespace.";
$c = new tool\Underline();
echo "=============================";
echo "Addition : ".$c->add(2,2);
?>
I am getting "class not found error" in my bold.php class file, when I use autoload file. But when I directly included the underline class file, I am getting the output. Why it is not working when I use autoload?
Can anyone help me to find the issue?
The "key" should be a directory under the path you put as "value", that should be relative to your working directory. To look at it in a simple way, the namespace should map the directory structure; you are missing a MyApp directory.
If in your composer.json have:
"autoload": {
"psr-0": {
"MyApp\\": "app/"
}
}
Then you need a MyApp directory under app/. Try this:
composer.json:
// /var/www/html/silexapp/composer.json
{
"require": {
"silex/silex": "~2.0",
"symfony/console": "~2.6"
},
"autoload": {
"psr-0": {
"Tnq\\": "app/"
}
}
}
Underline.php:
<?php
// /var/www/html/silexapp/app/Tnq/Todo/Command/Underline.php
namespace Tnq\Todo\Command;
class Underline
{
public function add($a,$b)
{
return $result = $a+$b;
}
}
Bold.php:
<?php
// /var/www/html/silexapp/app/Tnq/Todo/Command/Bold.php
require_once "../../../../vendor/autoload.php";
use Tnq\Todo\Command as tool;
echo 'this is the index file to check namespace.' . PHP_EOL;
$c = new tool\Underline();
echo "=============================";
echo "Addition : ".$c->add(2,2);
In theory, that should works (not tested :) )
sources:
https://getcomposer.org/doc/04-schema.md#psr-0
http://www.php-fig.org/psr/psr-0/
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