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.
Related
I try to build test project with PatrickLouys/no-framework-tutorial
My Controllers/BaseController.php:
<?php
namespace Example\Controllers;
class BaseController
{
public function test(){
echo 'test';
}
}
And index.php file:
<?php
namespace Example;
require __DIR__.'/vendor/autoload.php';
// come code with routes
case \FastRoute\Dispatcher::FOUND:
$className = $routeInfo[1][0];
$method = $routeInfo[1][1];
$vars = $routeInfo[2];
$class = new $className; <-- load the class specified in the routes
$class->$method($vars);
break;
// some code
When I tried run this code I got error:
Class 'Example\Controllers\BaseController' not found
Can you please tell me where I made a mistake?
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.
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.
I am currently learning MVC pattern and I have a problem with understanding how to pass data from controller to model and then from model to view.
My structure looks like this
/controllers
index.php
/libs
controller.php
main.php
view.php
/models
index_model.php
/views
index.php
.htaccess
index.php
First I always create an main object and include files that i need
include 'libs/Main.php';
require 'libs/Controller.php';
require 'libs/view.php';
$main = new Main();
then in main class i reroute the url so correct controller is called
class Main {
public function __construct() {
$url = (isset($_GET['url']) ? $_GET['url'] : null);
$url = rtrim($url, '/');
$url = explode('/', $url);
if(empty($url[0])){
require'controllers/index.php';
$controller = new Index();
}else{
$file = 'controllers/'. $url[0] .'.php';
if(file_exists($file)){
require $file;
} else
die("Error");
}
}
}
This is the controller for index page
class Index extends Controller{
public function __construct() {
parent::__construct(); //Creates new view object from parents class
$this->model = parent::loadModel('index');
//there should probably exists some method that will pass the data to model
$this->view->render('index'); //renders view, but also it should have another argument that contains data, i think
if(isset($_POST['SubmitCosmonautForm'])){
var_dump($_POST);
}
}
}
This is main controller that every other controller will inherit, so all have created view object
class Controller {
public function __construct(){
$this->view = new view();
}
protected function loadModel($name){
$path = 'models/'.$name.'_model.php';
if(file_exists($path)){
require $path;
$modelName = $name.'_model';
$this->model = new modelName();
}else{
echo "Model for" .$name."doesn't exists";
}
}
}
and this is the view in library, which contains render function so far
class View{
public function render($name){
require 'views/' .$name. '.php';
}
}
For example i want to save form data to database, so when i am thinking about it i should probably pass only data to model, and inside that model i should call methods that will handle saving/loading/changing DB, these methods that operates over DB could be in database class. Is this the right approach ? i read about mvc last 2 hours but i don't know if i understand it correctly.
My question is how to dynamically include and call the model and view classes in a simple MVC model? I have no problem calling the controller, I have found various ways to do that, but I can't find a good solution for calling and passing in the model and view.
I have setup a .htaccess file to read the url as "www.domain.com/controller/method/id".
I was previously trying to do a check if a file exists for the model and view the same way I am doing the controller using the $cont variable, and then trying to load the model and pass it into the controller, then the view. The issue I had is that all the includes are using the $cont variable to call instantiate their classes and could not tell each other apart. I tried adding a suffic $cont . 'Controller', but then I couldn't load the class at all, let alone pass in the model or view.
Here is my latest example without model or view.
<?php
//===============================================
// Debug
//===============================================
ini_set('display_errors','On');
error_reporting(E_ALL);
//===============================================
// Includes
//===============================================
require('coremvc.php');
//===============================================
// Constants & Globals
//===============================================
define('BASE_PATH', dirname(realpath(__FILE__)));
$GLOBALS['var'] = "";
//===============================================
// Session
//===============================================
session_start();
//===============================================
// Router
//===============================================
if ($_SERVER['REQUEST_URI'] !== '/') {
$uri = $_SERVER['REQUEST_URI'];
$uri = ltrim($uri, '/');
$request = explode('/', $uri);
foreach ($request as $key => $val) {
if(empty($request[$key])) {
unset($request[$key]);
}
}
$request = array_values($request);
if (isset($request[0])) {
$cont = $request[0];
}
if (isset($request[1])) {
$action = $request[1];
}
} else {
$cont = "home";
}
if (FILE_EXISTS('/controllers/' . $cont . 'Controller.php')) {
require '/controllers/' . $cont . 'Controller.php';
} else {
$cont = "home";
require '/controllers/homeController.php';
}
//===============================================
// Start the controller
//===============================================
$controller = new $cont;
I have made the following changes to the example above, posted it below, as well as my super easy bake oven simple controller.
<?php
if (FILE_EXISTS('/controllers/' . $cont . 'Controller.php')) {
require '/controllers/' . $cont . 'Controller.php';
} else {
$cont = "home";
$cont = ucfirst($cont) . 'Controller';
require '/controllers/homeController.php';
}
//===============================================
// Start the controller
//===============================================
$controller = new $cont('World');
$controller->world();
Controller, it is just extending an empty class which I was thinking I could use if I wanted to extend a common method to every class. That is what the coremvc.php is in the index.php above.
<?php
Class HomeController extends Controller
{
function __construct($world) {
echo "Hello ";
$this->world = $world;
}
function world() {
echo $this->world;
}
}
You want to load and call classes easily. I dynamically load classes that end in ".class.php". This makes things easy for me.
I put this in my index.php... where /app/ is where I have my php classes:
<?php
define('CLASS_DIR', '/app/');
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . CLASS_DIR);
spl_autoload_extensions('.class.php');
spl_autoload_register();
Next, I require my routes:
require 'rte/myroute.php';
I let my routes (controllers) direct my traffic to my models, albeit some parsing, etc.
I typically develop REST based APIs in PHP, so the "view" is just a JSON response.
The HTML / JavaScript client consumes the responses.
A good framework that I like is SlimPHP. I load it up using Composer.
http://www.slimframework.com/
https://getcomposer.org/
Here's an example of calling a class as an instance and statically, since you auto-loaded, you don't need to include anything at the top:
<?php
$param1 = 1;
$param2 = 2;
MyClass::myFunc($param1);
$myObj = new MyClass;
$myObj->myFunc2($param2);
As it is already say you can have a look to use the spl_autoload_register which will require all your files in the given path. You can change this function to improve the load.
Concerning the Model with your current code you can implement it as follow:
$controllerPath = "/controllers/{$cont}Controller.php";
$modelPath = "/model/{$cont}Model.php";
if (FILE_EXISTS($controllerPath)) {
require $controllerPath;
if (FILE_EXISTS($modelPath)) {
require $modelPath;
}
else {
throw new \LogicException(
sprintf("Your controller must implement a model. No model found: %s", $modelPath)
);
}
} else {
$cont = "home";
require "/controllers/{$cont}Controller.php";
require "/model/{$cont}Model.php";
}
//===============================================
// Start the controller
//===============================================
$controller = new $cont($cont);
In this sample of code, $cont is the name of the page like home. Which require the homeController and homeModel. Then in your __construct($modelName) just set the model.
However, I don't recommand you tu use this because your controller can load many Models. Then, your controller could look like this:
<?php
namespace \App\Controller; // If you use namespace
use App\Model\homeModel, // If you use namespace
App\Model\productModel; // If you use namespace
Class HomeController extends Controller
{
private $model;
/* If this function is common to all controllers just move it
in the parent Controller class( one more time, I don't recommend to set
the model using this way). */
public function __construct($model) {
$this->model= $model;
}
public function login() {
$homeModel = new homeModel(); // If you use namespace
// Example to call the view
$myValue = 'Hello world';
$this->render(array("myVariableName" => $myValue))
}
}
In this second example $this->render can be a method in your Controller class (which by the way should be an abstract class). I'll give a last sample of code for the logic of the abstract controller.
<?php
namespace \App\Controller;
abstract class AbstractController {
/* Common code for all controllers */
public function __construct() {
}
/* View renderer */
protected function render($parameters) {
/* Call the view here maybe an another class which manage the View*/
}
}
To conclude, you can implement this MVC in many way, this code is just a suggestion and maybe its not the best way. I advise you to have a look with the spl_autoload that I put at the beginning of my answer.