Class not found when successfully included - php

I get this fatal error for class not being found:
Fatal error: Uncaught Error: Class 'HomeController' not found
But the file is included.
require_once self::CONTROLLERS_PATH . $this->controller . '.php';
var_dump(file_exists(self::CONTROLLERS_PATH . $this->controller . '.php'));
new $this->controller;
I debugged it by var_dumping file_exists which returns true. I checked the namespaces and everything looks good. If I var dump $this->controller, its value is as intended - string(14) "HomeController". If I hard code it like new HomeController();, the class is initialized. Why is it not working from the variable, though? You can see my code below. This is my file structure:
App.php
<?php
namespace App\Core;
class App
{
protected $controller = 'HomeController';
protected $method = 'index';
protected $params = [];
const CONTROLLERS_PATH = '../App/Controllers/';
public function __construct ()
{
$url = $this->parseUrl();
if(file_exists(self::CONTROLLERS_PATH . ucfirst($url[0]) . '.php'))
{
$this->controller = ucfirst($url[0]); //Update $this->controller value
unset($url[0]);
echo 'file exists';
}
require_once self::CONTROLLERS_PATH . $this->controller . '.php';
var_dump(file_exists(self::CONTROLLERS_PATH . $this->controller . '.php'));
var_dump($this->controller);
new $this->controller;
if(isset($url[1]))
{
if(method_exists($this->controller, $url[1]))
{
$this->method = $url[1];
unset($url[1]);
echo 'method exists';
}
}
$this->params = $url ? array_values($url): [];
call_user_func_array([$this->controller, $this->method], $this->params);
var_dump($url);
}
public function parseUrl()
{
if(isset($_GET['url']))
{
return $url = explode('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
}
}
}
HomeController.php
<?php
namespace App\Controllers;
class HomeController
{
public function index()
{
echo 'asd';
}
}

When instantiating a class via a variable name you always have to include the whole namespace. And this has to be stored as a string first.
An example:
namespace Api;
Class test {
public function __construct() {
echo "test";
}
}
$className = 'test';
$classNameNamespaced = "\\Api\\".$className;
$a = new $classNameNamespaced; // echoes "test"
in your case:
$className = "\\App\\Controllers\\".$this->controller;
new $className;

Because the code is in a namespace, new $this->controller is different than new HomeController even if the value of $this->controller is HomeController.
The expression new HomeController is evaluated at the compile time and, because of the namespace, it is expanded to new \App\Controllers\HomeController (rule #6 on the Name resolution rules). The interpreter needs the fully qualified class name in order to find it.
The expression new $this->controller is evaluated during runtime and it leads to new HomeController which is the same as new \HomeController. There isn't any class HomeController defined in the root namespace, hence the error you get.
You can make it easily work by building the full class name into a string:
$class = 'App\\Controllers\\'.$this->controller;
new $class;
Find more examples in the documentation page "Namespaces and dynamic language features".
But the best way to load classes is to use the autoloader generated by Composer.

Related

PHP - Autoload only needed classes

I have an autoloader that is placed as a php file above all other sub directories in my project.
What it does is it loads all possible classes at once for any specific server request. After further thought I concluded I need to autoload only the required classes.
What do I need to do to avoid loading other classes not needed?
If I need to post the relevant code snippets of the class files in my subdirectories, I can.
<?php
namespace autoloader;
class autoloader
{
private $directoryName;
public function __construct($directoryName)
{
$this->directoryName = $directoryName;
}
public function autoload()
{
foreach (glob("{$this->directoryName}/*.class.php") as $filename)
{
include_once $filename;
}
foreach (glob("{$this->directoryName}/*.php") as $filename)
{
include_once $filename;
}
}
}
# nullify any existing autoloads
spl_autoload_register(null, false);
# instantiate the autoloader object
$classes = [
new autoloader('request'),
new autoloader('config'),
new autoloader('controllers'),
new autoloader('models'),
new autoloader('data')
];
# register the loader functions
foreach ($classes as $class)
spl_autoload_register(array($class, 'autoload'));
All registered autoloader functions will be called when you try to instantiate a new class or until it finally loads the class or throws an error. The way you have it now, you're registering the same autoloader function again and again for each directory, and file.
What you'd want to do is something along the lines of this.
namespace autoloader;
class autoloader
{
public function __construct()
{
spl_autoload_register([$this, 'autoload']);
}
public function autoload($classname)
{
if (! file_exists("{$classname}.class.php")) {
return;
}
include_once "{$classname}.class.php";
}
}
new autoloader();
Every autoloader function gets the class FQCN passed into it, and from there you'll have to parse it and figure out if you can load the file where that class exists. For instance, if I do the following.
use Some\Awesome\ClassFile;
$class = new ClassFile();
The autoloader we've registered will get the string Some\Awesome\ClassFile passed in as an argument, which we can then parse and see if we have a file for that class, if we don't we return out of the function and let the next registered autoloader function try and find the class.
You can read more about autoloaders in the documentation, I also wrote a blog post about it like 2 months ago that might interest you.
I had to refactor the code and remove the unnecessary load all functionality that I mistakenly thought would lazy load my classes on request.
Here is what I came up with:
Entry Point
<?php
require_once 'enums.php';
require_once 'api.class.php';
spl_autoload('AutoLoader\AutoLoader');
use App\API;
class MyAPI extends API
{
public function __construct($request){
parent::__construct($request);
}
}
$api = new MyAPI($_REQUEST);
echo $api->processRequest();
AutoLoader Implementation (located under subdirectory Autoloader/Autoloader.php and inaccessible via browser by using .htaccess)
<?php
namespace Autoloader;
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
spl_autoload_register("AutoLoader\AutoLoader::RequestLoader");
class Autoloader
{
public static function ClassLoader(String $fileName)
{
foreach ([".Class.php", ".php"] as $extension)
if (file_exists($fileName.$extension))
include $fileName.$extension;
}
public static function RequestLoader()
{
self::ClassLoader('Request');
}
}
Snippet for processRequest() (located in api.class.php - my request router)
public function processRequest()
{
$id1 = $this->requestObj->id1;
$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$goto = $this->requestObj->goto;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("No Endpoint: " . $endpoint2 ?? $endpoint));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "\\Controllers\\$className";
if (class_exists($class))
{
$method = strtolower($this->method);
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}();
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}

Confusion understanding PHP namespaces

I am learning about and implementing PHP namespaces.
Please refer to the following code:
<?php
namespace Xicor\core;
class App {
public function __construct() {
$registry = Registry::getInstance();
//TODO: init router
//TODO: call controller#action
$controllerName = 'Xicor\app\controllers\\'.'Index';
$action = 'show';
$controller = new $controllerName();
$controller->$action();
}
}
The above code works perfectly.
If I add throw new Exception('Lorem Ipsum') within the constructor, I'll get an error as expected. To make it work, I must use throw new \Exception('Lorem Ipsum') so that we are referring to the global namespace.
But, why does
$controllerName = 'Xicor\app\controllers\\'.'Index'; successfully import the right class.
Why do I not have to use
$controllerName = '\Xicor\app\controllers\\'.'Index'; (with \ prefixed)?
If it affects anything, here's my autoloader:
<?php
spl_autoload_register(function($name) {
//replace \ with DIRECTORY_SEPARATOR
$path = str_replace('\\', DS, $name);
//replace Xicor with root
$path = str_replace('Xicor', __DIR__, $path); // __DIR__ doesn't return a trailing slash
//add .php at end
$path .= '.php';
if(file_exists($path)) {
require_once($path);
}
});
As I understand it. PHP will work in the current namespace within a class unless specified otherwise (a preceding \).
Example
namespace Bar;
class Foo {
function __construct()
{
// references current namespace, looks for Bar\Baz;
$baz = new Baz();
}
}
class Baz {
function __construct()
{
try {
// do stuff
// references global namespace
} catch(\Exception $e) {
var_dump($e->getMessage());
}
}
function foo() {
// This will prepend the current namespace to the class, in actual fact it will first look for "Bar\Bar\Foo"
// When it isnt found, it will move on and look at spl_autoload_register to try to resolve this class,
// Failing that you will get a ClassNotFoundException
$foo = new Bar\Foo();
}
}
Please see. https://www.php.net/manual/en/language.namespaces.rules.php
and https://www.php.net/manual/en/language.namespaces.faq.php#language.namespaces.faq.full for reference

Application is loading only one (default) controller in php mvc project?

I am building custom mvc framework from scratch and when I put something something to print in my PageController, application is working, but when I create some other controller and try to print something, it just prints data from PageController.
I think that the problem is on where I put variable protected $currentController = 'PageController'; and $this->currentController = new PageController();.
Now I tried putting $this->currentController = new $this->currentController; and I get this error:
'Fatal error: Uncaught Error: Class 'PageController' not found in C:\xampp\htdocs\php\App\Libraries\Core.php:20 Stack trace: #0 C:\xampp\htdocs\php\public\index.php(7): App\Libraries\Core->__construct() #1 {main} thrown in C:\xampp\htdocs\php\App\Libraries\Core.php on line 20 '.
Also I'm using composer and autoloading with psr-4 and namespacing.
Core.php
namespace App\Libraries;
use App\Controllers\PageController;
class Core
{
protected $currentController = 'PageController';
protected $currentMethod = 'index';
protected $params = [];
public function __construct()
{
$url = $this->getUrl();
if (file_exists('../App/Controllers/' . ucwords($url[0]) . '.php')) {
$this->currentController = ucwords($url[0]);
unset($url[0]);
}
require_once '../App/Controllers/' . $this->currentController . '.php';
$this->currentController = new PageController();
}
public function getUrl()
{
if (isset($_GET['url'])) {
$url = rtrim($_GET['url'], '/');
$url = filter_var($url, FILTER_SANITIZE_URL);
$url = explode('/', $url);
return $url;
}
}
}
PageController.php
namespace App\Controllers;
class PageController
{
public function __construct()
{
echo "Page Loaded!!!";
}
}
PostController.php
namespace App\Controllers;
class PostController
{
public function __construct()
{
echo "Post Controller Loaded!!!";
}
}
index.php (in public folder)
use App\Libraries\Core;
require_once '../App/bootstrap.php';
$init = new Core;
bootstrap.php (in App folder)
<?php
require dirname(__DIR__) . '..\vendor\autoload.php';
I expect when I make some other controller to be able to write something in it and get output without errors or the same data as in PageController.
I pulled your repository down and there were a few things I had to change.
Make sure that your RewriteBase in your .htaccess goes from the correct document root. If you're not using virtual hosts, then you need to use the path from the localhost to the project.
Also, you are still using a require statement before calling the object. You do not need that. Remove it.
At the top, add the namespace to the $currentController property. If $url[0] is set, prefix the namespace like this:
$this->currentController = '\App\Controllers\\' . ucwords($url[0]);
That got the posts controller and page controllers working.

Oriented Object PHP autoloader issue

I encounterd a little problem with my classes : they simply do not load through my autoloader.
I get this message error :
Warning:
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php):
failed to open stream: No such file or directory in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Fatal error: require(): Failed opening required
'C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php'
(include_path='.;C:\php\pear') in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Factory class :
use Core\config;
use Core\Database\MysqlDatabase;
class App {
public $title = "My super site";
private $db_instance;
private static $_instance;
public static function getInstance()
{
if (is_null(self::$_instance))
{
self::$_instance = new App();
}
return self::$_instance;
}
public static function load()
{
session_start();
require ROOT . '/app/Autoloader.php';
App\Autoloader::register();
require ROOT .'/core/Autoloader.php';
Core\Autoloader::register();
}
public function getTable($name)
{
$class_name = '\\App\\Table\\' . ucfirst($name) .'Table';
return new $class_name($this->getDb());
}
public function getDb()
{
$config = Config::getInstance(ROOT . '/config/config.php');
if (is_null($this->db_instance)) {
$this->db_instance = new MysqlDatabase($config->get('db_name'), $config->get('db_user'), $config->get('db_pass'), $config->get('db_host'));
}
return $this->db_instance;
}
}
Namespace App autoloader class :
<?php
namespace App;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Namespace Core autoloader class :
<?php
namespace Core;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Empty PostTable
namespace App\Table;
use Core\Table\Table;
class PostsTable extends Table
{
}
Index page :
define('ROOT', dirname(__DIR__));
require ROOT . '/app/App.php';
App::load();
$app = App::getInstance();
$posts = $app->getTable('Posts');
var_dump($posts->all());
How to make it works please?
AS I said in the comments check this path
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php)
Doesn't look right to me
require(C:\wamp64\www\blog\ [appAutoloader.php] /Table/PostsTable.php)
What's that bit doing there....
Also namespace of App is not app for the folder its App because this may work on Windows but you will find it does not work on Linux. Because Linux paths are case sensitive, and windows are not.
Further this makes little to no sense
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
Require 2 files? Paths don't work that way, not that I am aware of at least.
On top of that your implementation ignores the _ Typically underlines will be part of the class name but are replaced by directory, this allows a shorter namespace. So for example instead of having a namespace like this
Namespace \APP\Table;
class PostsTable ..
You could have a class in the same place Like so
Namespace \APP;
class Table_PostsTable ..
With a shorter namespace but still located in the App/Table/PostsTable.php file. However, that's just how I read the spec for PSR autoloaders.
PRO TIP
Take this path C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php open the file browser on you desktop and see if it pulls up the file by pasting it into the navigation bar. It wont, but you can be sure your path is wrong by eliminating the code.

Cannot find dynamic class reference PHP

My routing.php (auto-prepend file)
<?php
if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
return false;
} else {
include 'index.php';
}
My index.php
<?php
namespace MVC;
require_once "controllers/AdminController.php";
use MVC\controllers\AdminController as AdminController;
/*
* Break the requested URI down into an array
*/
$request = trim($_SERVER["REQUEST_URI"]);
$request = explode("/", $request);
$request = array_filter($request);
$additional = array();
$singles = array();
/*
* Get the method/controller from the requested URL
*/
foreach($request as $key=>$item) {
switch($key) {
case 1:
$controller = $item;
break;
case 2:
$method = $item;
break;
default:
if($key > 2) {
$item = explode("=", $item);
if(isset($item[1])) {
//key item pair, data
$pairs[$item[0]] = $item[1];
/*
* Make the assumption that any value passed in
* as an assigned value is data and not a route
*
* Append this to the pairs array as its own key/item
*/
} else if(isset($item[0])) {
echo "<b>WARNING: \"" . $item[0] . "\" has no key/value!</b>";
}
}
break;
}
}
/*
* Handle the fact that these might not have been requested
*/
if(!isset($controller))
$controller = 'index';
$controller = ucfirst($controller) . "Controller";
if(!isset($method))
$method = 'index';
$controller = new $controller();
My AdminController.php (found in controllers/AdminController.php)
<?php
namespace MVC\controllers;
require_once "libs/Controller.php";
use MVC\libs\Controller;
class AdminController extends Controller {
}
Finally my Controller superclass found in libs/Controller.php
<?php
namespace MVC\libs;
class Controller {
}
My issue is this line
$controller = new $controller();
I'm aware that this is a variable - that's the intention, I'm trying to dynamically load this class based on the URI request.
Gives this error:
127.0.0.1:50342 [500]: /admin/users/id=555/action=ban - Class 'AdminController' not found in /home/jack/PhpstormProjects/mvc/index.php on line 64
I've checked that it's requiring the class, using it as "AdminController", yet it still can't find a reference to it in the relevant namespace - I'm thinking that because it's dynamic it might have some issues?
Cheers all!
EDIT: My folder structure
controllers
AdminController.php
libs
Controller.php
index.php
routing.php
This is a namespace problem. You can't use variable class name and use statement at the same time like you do. Your variable gets its value at runtime, while use imports are done at compile time (use can't be used in block scope for the same reason). See:
use foo as bar;
class foo {
public function __construct() {
echo "OK\n";
}
}
new foo(); // OK
new bar(); // OK
$foo = 'foo';
$bar = 'bar';
new $foo(); // OK
new $bar(); // Fatal error: Class 'bar' not found
Solution: use full class name (with absolute namespace) in your variable.

Categories