Can't import functions using the use function keywords described in PHP.net. Recreating the example locally is returning a PHP Fatal error: Uncaught Error: Call to undefined function.
composer.json
{
"autoload": {
"psr-4": {
"My\\": ""
}
}
}
full.php
<?php
namespace My\Full;
function functionName()
{
echo 'Hello Stackoverflow';
}
index.php
<?php
require 'vendor/autoload.php';
use function My\Full\functionName as func;
func();
Note: I understand I can require the file, but I wanted to know if it was possible without doing so.
use function does not include any files or function definitions it simply aliases a fully qualified function name meaning when you call the function you don't need to specify the namespace.
In your example you are using composer which is great for automatically including files however from https://www.php-fig.org/psr/psr-4/ PSR-4 is
a specification for autoloading classes from file
paths
It does not autoload functions or files which don't conform to this specification.
You can however use composer to automatically include files for situations like this. You need to update your composer.json then run composer dumpautoload
composer.json
{
"autoload": {
"files": ["full.php"]
}
}
The rest of your can then remain unchanged and it should work.
Related
I am writing a simple set of PHP functions, I use only pure PHP7, no framework, nothing. These functions will later on be used in a plugin in a CMS, but that is besides the point. I want to write unit tests for my functions using Codeception (to get familiar with it, I know that Codeception essentially only runs PHPUnit here), but I don't really know how to point Codeception to my code in a reasonable way.
My structure is as follows: I have path/to/functions.php which contains the functions I want to test, something along the lines of:
<?php
namespace App;
if (!defined('CONST')) {
die('What are you doing? Get out of here!');
}
function change_string($string){
return $string . '123';
}
I have used Composer to install Codeception to the root of my project and I used Codeception bootstrap to get started and then I also used Codeception to generate the unit test file, to which I added my unit test. Now most tutorials/explanations/articles on the subject just write tests and Codeception magically knowns where to find the code to test. This makes zero sense to me, and does not work in my case. So what I did is the following:
<?php
class NamesPathsTest extends \Codeception\Test\Unit
{
/**
* #var \UnitTester
*/
protected $tester;
protected function _before()
{
defined('CONST') or define('CONST', 'XXX');
require_once('path/to/functions.php');
}
protected function _after()
{
}
// tests
public function testChangeString() {
$this->assertEquals('a123',App\change_string('a'));
}
}
This works, but I think that there must be a better way to explain to Codeception where is the code to run than using the require_once('path/to/functions.php'). How to do this? What is the smart way of pointing Codeception to my code? Can it also handle defining the constant, so that I can actually test the functions?
How does your application code knows where the functions and classes are?
The magic ingredient is called autoloading.
Since you are using Composer already, the easiest way is to configure Composer to load your classes and functions.
Autoloading only works with classes, for them you can map namespace prefix to directory,
{
"autoload": {
"psr-4": {
"Monolog\\": "src/",
"Vendor\\Namespace\\": ""
}
}
}
Files containing functions must be included, but Composer helps with that too:
{
"autoload": {
"files": ["path/to/functions.php"]
}
}
Combined result:
{
"autoload": {
"files": ["path/to/functions.php"]
"psr-4": {
"Monolog\\": "src/",
"Vendor\\Namespace\\": ""
}
}
}
Since Codeception is installed using Composer, no additional work is required to get autoloading work in tests.
To benefit from autoloading in your application code, you must require 'vendor/autoload.php'; near the entry point.
Regarding your constant question, nobody uses this way to prevent direct execution of files, it is much simpler and more secure to move the code away from public directory and leave only small index.php file that can be accessed directly in public directory.
Problem:
I have an index.php file which has several composer dependencies.
Inside the index.php file i'm trying to call the static method from the outside class in a different php (let's say auth.php) file like this:
/*creating a class instance*/
$var = new AuthClass();
/*accessing an outside class method*/
$var = AuthClass::checkTime($tokenCode);
The issue is the checkTime method inside the class requires a composer dependency as well, which isn't inherited, although the file is located in the same folder as index.php and index.php is included.
PHP Fatal error: Uncaught Error: Class 'Token' not found
I've tried everything - from adding require_once/include 'index.php' to copying the composer autoload to auth.php outside and inside the AuthClass code, but nothing works, i'm still getting the same error.
Additional code:
index.php
require __DIR__ . '/src/vendor/autoload.php';
$argument1 = $_GET['argument1'];
$tokenCode = $_GET['tokenCode'];
include 'config/database.php';
include 'objects/program1.php';
include 'auth.php';
use ReallySimpleJWT\Token;
use Carbon\Carbon;
$secret = "somesecret";
if (($_SERVER['REQUEST_METHOD']) == "GET") {
if ($_GET['url'] == "bankquery") {
if($tokenCode===NULL){
echo "no correct token provided";
print($results);
} else {
$results = Token::validate($tokenCode, $secret);
if ($results = 1){
$var = new AuthClass();
$var = AuthClass::checkTime($tokenCode);
} else {
echo "no correct token provided";
}
}
} else {
echo "some GET other query";
}
?>
auth.php
// loading composer
require __DIR__ . '/src/vendor/autoload.php';
//loading my index.php file
include 'index.php';
//using composer dependencies
use ReallySimpleJWT\Token;
use Carbon\Carbon;
class AuthClass{
public static function checkTime($tokenCode){
// getting payload from token code by accessing the composer dependency method in a class Token
$received = Token::getPayload($tokenCode);
return $received;
}
}
?>
Need help, guys.
You're using AuthClass before it was defined - try move include 'index.php'; line at the end of the file.
You should also include vendor/autoload.php only once - you don't need to repeat this in every file, just make sure that it is included at the top of the entry file which handles request.
But this is more like a result of design problem. You should define AuthClass in separate file and avoid any additional side effects in it - file should only define class. This is part of PSR-1 rules:
Files SHOULD either declare symbols (classes, functions, constants, etc.)
or cause side-effects (e.g. generate output, change .ini settings, etc.)
but SHOULD NOT do both.
Since you're already using autoloader from Composer it should be relatively easy to register your own autoloading rules, so Composer's autoloader will take care about classes autoloading.
If at this point you're still getting Class 'X' not found, you probably did not installed some dependency or your autoloading rules are incorrect.
The simplest solution would be to include your own code in the composer autoloading.
The composer website tells you how to do it.
You don't need to require the composer files yourself and composer handles everything for you.
The PSR-4 tells you how to namespace your code to use Namespacing.
composer.json:
{
"require": {
"smarty/smarty": "v3.1.17"
}
}
index.php:
define('SMARTY_SPL_AUTOLOAD', 1); // now smarty should use its own autoloader
require_once __DIR__ . "/vendor/autoload.php";
function my_classes_loader($class) {
$path = "{$class}.class.php";
if (file_exists($path)) {
include_once $path;
return true;
}
return false;
}
spl_autoload_register('my_classes_loader');
$smarty = new Smarty();
$smarty->setCompileDir("templates_c");
$smarty->display('main.html');
exit();
If I open it in browser I get
Fatal error: Class 'Smarty_Internal_TemplateCompilerBase' not found in
//smarty-example/vendor/smarty/smarty/distribution/libs/sysplugins/smarty_internal_smartytemplatecompiler.php
on line XX
The file is there. And it has content. And it is accessible / readable for PHP etc.
What am I doing wrong? What is missing?
It is a good idea to only have one point deciding about autoloading, and this should be Composer alone.
Try to put your own autoload function away, use a autoload declaration in your composer.json instead. Unfortunately you are not using either PSR-0 or PSR-4 naming standard, but Composer allows you to use "classmap" in this case. Consider moving all your file names to comply with PSR-0.
And the smarty autoloading should already be done by requiring it via Composer. No need to set that constant.
Last but not least I think your autoload function should not return anything. Especially it should not return false if it cannot find the file it supposes to contain the class, because depending on how the autoload functions are ordered on the stack, your function might get called first for ALL classes, including all Smarty ones. If you return false in these cases, you destroy the working autoload stack by not allowing later functions to load that class.
So all in all it is best to use Composer for all autoloading. The developers did everything to provide the best performing autoload function - your own function probably can only be as fast as theirs, but will probably be slower.
I know you can use Composer's files scheme to include functions or definitions automatically in every file.
I'd like to take this one step further and never have to manually write a use classname as statement again in individual files. This is my ideal set up:
/composer.json:
{
"require": {
"slim/slim": "2.*"
}
"autoload": {
"files": ["init.php"]
}
}
/init.php:
use Slim\Slim as Slim;
use Slim\Route as Route;
define("RYAN","BRODIE");
/example.php:
require '/vendor/autoload.php';
echo RYAN; // ✔ outputs "BRODIE"
new Slim(); // × Fatal error: Class 'Slim' not found
This way whenever I add a new namespace or class to an existing namespace, I can add it to init.php and then use it as I so wish across all of the repository.
Unless you modify the PHP runtime, this is not possible. namespace and use declarations are only valid in the physical file they appear in. These declarations do not span across includes. If they did you'd have a real nightmare managing namespaces and aliases.
Declaring these at the top of each file is simply something you'll have to live with. Most languages that have features similar to PHP's namespaces do the same.
Link to documentation.
I'm trying to use PHP namespaces for the first time and can't even get a very basic example working with 2 files. Here's my directory setup:
/Framework/
/Framework/index.php
/Framework/Models/TestModel.php
And here's the code behind the two files.
index.php:
namespace Framework;
use \Framework\Models\TestModel;
$model = new TestModel();
$model->test();
TestModel.php:
namespace Framework\Models;
class TestModel
{
public function test()
{
print("test");
}
}
The error is simply that it cannot find the TestModel class:
Fatal error: Class 'Framework\Models\TestModel' not found in C:\xampp\htdocs\Framework\index.php on line 7
I'm running the PHP via a web browser at localhost/Framework/index.php. It must be something really simple I'm not seeing, can anyone point it out for me?
The Namespace on the File itself "distinguishes" from other classes and functions, however PHP/Server does not know where the physical file is simply based on a Namespace.
So including the file directly, as people has mentioned, lets PHP know exactly what you mean.
When PHP can't find the file, it will call the function spl_autoload_register() and in that method people will usually put a little function to match namespace to directory structure and then load files according.
Another option is to include Composer in your project and use the PSR-4 autoload
{
"require": {
},
"autoload": {
"psr-4": {
"App\\": "app_directoy/",
"Framework\\": "framework_directory/",
}
}
}
When including the composer autoload it will look for everything Framework/* within your framework_directory as you defined.
You should remove 'namespace Framework' and include TestModel.php instead in your index.php - Something like this:
require_once('Models/TestModel.php');
use \Framework\Models\TestModel;
$model = new TestModel();
$model->test();