I'm trying to inject a third-party authentication library into my Phalcon application. The file exists at /lib/Foo_Bar_AuthProvider.php:
<?php
namespace Foo\Bar;
class AuthProvider
{
public function authenticate()
{
return true;
}
}
I register this directory with the Phalcon autoloader in my bootstrapper, located at /public/index.php, and add it to the DI:
<?php
try {
//Register an autoloader
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
'../app/controllers/',
'../app/models/',
'../lib/'
))->register();
//Create a DI
$di = new \Phalcon\DI\FactoryDefault();
$di->set('authProvider', function() {
return new \Foo\Bar\AuthProvider();
});
// ...
}
Then I try to use this component in /app/controllers/AccountController.php:
<?php
class AccountController extends \Phalcon\Mvc\Controller
{
public function loginAction()
{
if (!$this->request->isPost()) {
return;
}
$success = $this->authProvider->authenticate();
if (!$success) {
$this->flash->error('Authentication failed.');
return;
}
$this->flash->success('Authentication succeeded. Welcome!');
return $this->dispatcher->forward(array('controller' => 'index', 'action' => 'index'));
}
}
This throws an exception:
Fatal error: Class 'Foo\Bar\AuthProvider' not found in /public/index.php on line 44
I am pretty new at using PHP namespaces so I'm probably missing something obvious, but I haven't been able to figure it out. I tried adding a backslash before the namespace declaration in Foo_Bar_AuthProvider.php like this:
namespace \Foo\Bar;
This didn't change anything. I also tried removing this backslash from the bootstrapper:
$di->set('authProvider', function() {
return new Foo\Bar\AuthProvider();
});
No dice here either. Finally, I tried adding the use statement to AccountController.php:
use \Foo\Bar;
Also:
use Foo\Bar;
I believe the purpose of the autoloader is to avoid such things, but adding the use statements didn't work anyways.
Read about the PSR-0 standard. Phalcon applies most of their conventions and others PHP Frameworks as well.
From my understanding the _ is only meaningful in the class name, elsewhere underscores are literal. For example...
A class name Foo_Bar_AuthProvider means: in each registered dir search for the Foo/Bar path then check for the AuthProvider.php file. AFAIK, that's useful if you need the file to be under the Foo/Bar path but not necessarily in the Foo/Bar namespace.
Id recommend you to use the "namespace=path" approach that PSR-0 describes.
Try to rename Foo_Bar_AuthProvider.php to AuthProvider.php then put this file into /lib/Foo/Bar. With this done you'll be able to register this lib like this:
$di->set('authProvider', 'Foo\Bar\AuthProvider');
Related
So, I'm converting some old code into a namespace, and trying to get autoload working. I've managed to follow the many good answers on this site about how to account for the namespace part of an autoloaded class (How do I use PHP namespaces with autoload?) - no problem.
Here's a different wrinkle, though. How do I autoload classes within the same namespace?
My autoload function (defined in a global include) is something like this:
function app_autoload($class)
{
$path = __DIR__.'/'.str_replace("\\", DIRECTORY_SEPARATOR, $class).'.php';
if (file_exists($path))
{
require_once($path);
}
}
spl_autoload_register('app_autoload');
If I have a class defined in the namespace app\nstest, I can autoload it just fine from most of my system:
namespace app\nstest;
class Test1
{
function hello()
{
echo "Hello world";
}
}
However, another class in the same namespace has issues:
namespace app\nstest;
class Test2
{
function callMe()
{
$test1 = new Test1();
}
}
If I explicitly include/require the Test1 file at the top of Test2, no problems, but the autoloader doesn't seem to be aware of the namespace, so it's loading "Test1.php" instead of "app/nstest/Test1.php".
I also tried checking the __NAMESPACE__ inside the autoloader, but it's empty.
I have a problem with Codeception/AspectMock.
When using custom autoloader and try to create an instance of a class which has parent form the same custom namespace I have this error:
PHP Fatal error: Uncaught InvalidArgumentException: Class [parent
class name] was not found by locator in
vendor/goaop/parser-reflection/src/ReflectionEngine.php:112
I have very simple setup:
<?php
require_once __DIR__ . '/vendor/autoload.php';
$kernel = AspectMock\Kernel::getInstance();
$kernel->init([
'debug' => true,
'includePaths' => [__DIR__. '/lib'],
]);
$kernel->loadFile(__DIR__ . '/autoload.php'); // custom autoloader
$b = new \lib\B();
Class \lib\B:
namespace lib;
class B extends A {}
Class \lib\A:
namespace lib;
class A
{
public function getName()
{
return static::class;
}
}
Class B is loaded via my custom autoloader, but then the locator tries to load parent class A via composer autoloader and returns this error. Is this a bug, or I'm doing something wrong?
The topic starter has already got an answer on GitHub.
In order to use custom autoloader you should re-init ReflectionEngine with composite class locator that will be able to locate your classes or you can use CallableLocator with closure for resolving paths.
Or, even better you could switch your code base to the PSR0/PSR-4
For example:
$kernel->loadFile(__DIR__ . '/autoload.php'); // custom autoloader
\Go\ParserReflection\ReflectionEngine::init(
new class implements \Go\ParserReflection\LocatorInterface {
public function locateClass($className) {
return (new ReflectionClass($className))->getFileName();
}
}
);
$b = new \lib\B(); // here you go
If you can easily do a find and replace on your codebase, maybe you could refactor your code to PSR-4 autoloading standards and do away with the need for a custom autoloader altogether.
This is the spec https://www.php-fig.org/psr/psr-4/. I'll try and explain it as simply as possible.
Imagine changing your lowercase namespace lib to Lib, and setting that namespace to the src/ directory in your composer.json:
"autoload": {
"psr-4": {
"Lib\\": "src/"
}
}
After setting that, run composer dumpautoload. Then all you need to do is search and replace namespace lib;, replacing with namespace Lib;.
An example class located in src/Form.php would have namespace Lib; at the top, followed by class Form.
<?php
namepace Lib;
class Form
{
// code
}
Namespaces use the folder naming convention. All classes directly in src/ have namespace Lib;. If there are subdirectories, the directory name becomes part of the namespace. For example a file in src/Form/Field/Text.php would have namespace Lib\Form\Field; class Text {}.
<?php
namepace Lib\Form\Field;
class Text
{
// code
}
You can see the full convention in the link above, but the general rule is make any folders begin with a capital letter, as with your classname, and the autoloader should be able to find all of your classes.
This is probably the best practice solution for you, and again as I said, only requires a little bit of file renaming and namespace tweaking. Good luck!
I created a class at Controller folder of Cake project like this:
<?php
class Hi
{
function __construct(){ }
public function hi()
{
echo "hi!";
exit;
}
}
Then in a controller, I tried to include it:
<?php
namespace App\Controller;
use App\Controller\AppController;
include_once "Hi.php";
class MyController extends AppController
{
public function sayHi()
{
$a = new Hi();
$a.hi();
}
}
Here is the error I'm having:
Fatal error: Cannot declare class Hi, because the name is already in use in path\api\src\Controller\Hi.php on line 2
What's going on?
MyController.php and Hi.php are in the same folder. I'm using PHP 7.
Including a file won't make the classes in that file part of the current namespace, as namespaces are a per-file functionality.
http://php.net/...namespaces.importing.php#language.namespaces.importing.scope
Your Hi class will be declared in the global namespace, and your new Hi() will cause PHP to look for it in the current namespace, ie it will look for App\Controller\Hi, which doesn't exist, hence the composer autoloader kicks in, and will map this via a PSR-4 namespace prefix match to src/Controller/Hi.php, which will include the file again, and that's when it happens.
http://www.php-fig.org/psr/psr-4/
Long story short, while using new \Hi() would fix this, you better not include class files manually, or declare them in paths where they do not belong. Instead declare your files and classes in a proper autoloading compatible fashion, that is for example with a proper namespace in a path that matches that namespace, like
namespace App\Utils;
class Hi {
// ...
}
in
src/Utils/Hi.php
I basically have the following directory structure
MiniCrawler
Scripts/
htmlCrawler.php
index.php
This is the index.php
use Scripts\htmlCrawler;
class Main
{
public function init()
{
$htmlCrawler = new htmlCrawler();
$htmlCrawler->sayHello();
}
}
$main = new Main();
$main->init();
And this is the /Scripts/htmlCrawler.php
namespace Scripts;
class htmlCrawler
{
public function sayHello()
{
return 'sfs';
}
}
The code throws the following error
Fatal error: Class 'Scripts\htmlCrawler' not found in
/mnt/htdocs/Spielwiese/MiniCrawler/index.php on line 9
You forgot to include the file /Scripts/htmlCrawler.php in your index.php file.
require_once "Scripts/htmlCrawler.php";
use Scripts\htmlCrawler;
class Main
{
public function init()
{
$htmlCrawler = new htmlCrawler();
$htmlCrawler->sayHello();
}
}
$main = new Main();
$main->init();
Your index file cannot find the definition of the htmlCrawler file if you never provide the file defining this class, and the use of namespaces doesn't automatically include the required classes.
The reason why frameworks don't require you to include manually the file and you can simply add the use statement is because they're handling the inclusion of required classes for the developer. Most of the frameworks are using composer to handle the automatic inclusion of the files.
You can obtain a somewhat similar functionality using autoloading.
I use symfony 2.4.0. I want to use my custom class as discussed here: Autoloading a class in Symfony 2.1. I have created subfolder in src:
namespace Yur;
class MyClass {
public go() {
var_dump('hello!! 32');
}
}
In my controller, I made this:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Yur\MyClass;
class WelcomeController extends Controller
{
public function indexAction()
{
$my = new MyClass();
$my->go();
die();
...
but it makes an exception:
ClassNotFoundException: Attempted to load class "MyClass" from namespace "Yur" in /var/www/shop.loc/src/Acme/DemoBundle/Controller/WelcomeController.php line 12. Do you need to "use" it from another namespace?
After I have got this exception, I decided consciously to make syntax error exception in my class to see if it loaded. I changed class MyClass ... to class4 MyClass ..., but doesnt got asyntax error` exception. And I decided, that my class is not loaded.
Is anyone knows why? And what I must to do to resolve?
A few things. First, in your code sample above, you have
public go() {
var_dump('hello!! 32');
}
which should be
public function go() {
var_dump('hello!! 32');
}
The former raises a parser error in PHP. and probably isn't what you want.
Second, the error
ClassNotFoundException: Attempted to load class "MyClass" from namespace "Yur" in /var/www/shop.loc/src/Acme/DemoBundle/Controller/WelcomeController.php line 12. Do you need to "use" it from another namespace?
is the error Symfony uses when it attempt to autoload a class, but can't find the file. This usually means your file is named incorrectly, or in the wrong folder. I'd tripped check that you have a file in the directory you think you do.
$ ls src/Yur/MyClass.php
You can also add some debugging to the composer autoload code to see what path it's cooking up for your custom class
#File: vendor/composer/ClassLoader.php
public function findFile($class)
{
//...
$classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
//start your debugging code
if($class == 'Yur\MyClass')
{
//dump the generated path
var_dump($classPath);
//dump the default include paths
var_dump($this->fallbackDirs);
}
//...
}