MVC not opening links how it should "controller/action" - php

So i am new to MVC and im trying to understand how this works.
As i understood this is how the autoloder should open
http://www.example.com/controller/action.
I have this is my indexAction
public function indexAction()
{
$this->view->setVars([
'name' => 'Stefan',
]);
}
And this in my index.phtml
<h1>Hello <?php echo $name ?></h1>
But when i call my local-host this is how he tried to open it.
Page not found: \Mvc\ControllerInterface\IndexControllerInterface::IndexAction
Here is my full autoloader
<?php
// simple autoloader
spl_autoload_register(function ($className)
{
if (substr($className, 0, 4) !== 'Mvc\\')
{
// not our business
return;
}
$fileName = __DIR__.'/'.str_replace('\\', DIRECTORY_SEPARATOR, substr($className, 4)).'.php';
if (file_exists($fileName))
{
include $fileName;
}
});
// get the requested url
$url = (isset($_GET['_url']) ? $_GET['_url'] : '');
$urlParts = explode('/', $url);
// build the controller class
$controllerName = (isset($urlParts[0]) && $urlParts[0] ? $urlParts[0] : 'index');
$controllerClassName = '\\Mvc\\ControllerInterface\\'.ucfirst($controllerName).'ControllerInterface';
// build the action method
$actionName = (isset($urlParts[1]) ? $urlParts[1] : 'index');
$actionMethodName = ucfirst($actionName).'Action';
//var_dump($url,$urlParts,$controllerName,$actionMethodName);
//
try {
if (!class_exists($controllerClassName)) {
throw new \Mvc\Library\NotFoundException();
}
$controller = new $controllerClassName();
if (!$controller instanceof \Mvc\Controller\ControllerInterface || !method_exists($controller, $actionMethodName)) {
throw new \Mvc\Library\NotFoundException();
}
$view = new \Mvc\Library\View(__DIR__.DIRECTORY_SEPARATOR.'views', $controllerName, $actionName);
$controller->setView($view);
$controller->$actionMethodName();
$view->render();
} catch (\Mvc\Library\NotFoundException $e) {
http_response_code(404);
echo 'Page not found: '.$controllerClassName.'::'.$actionMethodName;
} catch (\Exception $e) {
http_response_code(500);
echo 'Exception: <b>'.$e->getMessage().'</b><br><pre>'.$e->getTraceAsString().'</pre>';
}
And here is my folder structure.
I tried playing around with the folder Structure and with my xxamp httpd conf.
But nothing relly works.
What am i missing here.

There are a couple of things I can see wrong.
First you are ending $controllerClassName with "ControllerInterface" whereas your class according to your directory structure your index controller is IndexController.
Secondly when checking if an object is not an instance of a particular class you need to wrap the negated check in parenthesis e.g. if (!($controller instanceof \Mvc\Controller\ControllerInterface)){} see example 3 here http://php.net/manual/en/language.operators.type.php

Related

Getting Undefined index error and Method name must be a string in ..error in PHP

While upgrading to PHP7, I encountered these problem.
The issue is am trying to create codes that I can reuse like a mini-framework and the RUN function where the problem is used to load the relevant template and supplying the variables. It complains about
undefined index
of these 2 variables
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
and it also complained about this line
$page = $controller->$action();
which displayed
Fatal error: Uncaught Error: Method name must be a string in...
public function run() {
$routes = $this->routes->getRoutes();
$authentication = $this->routes->getAuthentication();
if (isset($routes[$this->route]['login']) && !$authentication->isLoggedIn()) {
header('location: /login/error');
}
else if (isset($routes[$this->route]['permissions']) && !$this->routes->checkPermission($routes[$this->route]['permissions'])) {
header('location: /login/permissionserror');
}
else {
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
$page = $controller->$action();
$title = $page['title'];
if (isset($page['variables'])) {
$output = $this->loadTemplate($page['template'], $page['variables']);
}
else {
$output = $this->loadTemplate($page['template']);
}
echo $this->loadTemplate('layout.html.php', ['loggedIn' => $authentication->isLoggedIn(),
'output' => $output,
'title' => $title
]);
}
This is the index.php
try {
include __DIR__ . '/../includes/autoload.php';
$route = ltrim(strtok($_SERVER['REQUEST_URI'], '?'), '/');
$entryPoint = new \Ninja\EntryPoint($route, $_SERVER['REQUEST_METHOD'], new \Ijdb\IjdbRoutes());
$entryPoint->run();
}
catch (PDOException $e) {
$title = 'An error has occurred';
$output = 'Database error: ' . $e->getMessage() . ' in ' .
$e->getFile() . ':' . $e->getLine();
include __DIR__ . '/../templates/layout.html.php';
}
The code is much, so, I can't display the whole code here since am using MVC pattern, but if there is anything you still want to know, I will gladly post it here
This code is runnable in php 7.2.7 (MAMP and LAMP), your way of dynamic function calling is invalid and your two variables are empty. This is not exact as yours but you can take logic form this demo.
Ok i am just providing a very simple example of reflection with mapping url to class along with functions. I make folder structure like below-
Here .htaccess is used to redirect all the url to index.php (if no file exists).
index.php include all code that could initialize code(for now only three files were there - uri.php, urlMapping.php and actions.php)
URI.php - have function that provide values like basepath, baseurl, uri
urlMappig.php - that allows you to provide which url hit which class along with method
actions.php will call dynamic class and function (reflection)
now look into code of index.php
<?php
include_once('URI.php');
include_once('urlMapping.php');
include_once('actions.php');
?>
aNow code insise uri.php file -
<?php
// all function should be accessible to all file loaded now
// return full url
function full_url (){
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
}
// returns current directory
function directory() {
$parts = explode("/", __DIR__);
return $parts[count($parts)-1];
}
// return base url
function base_url(){
$dir = directory();
return substr(full_url(), 0, strpos(full_url(), $dir)+strlen($dir));
}
// return uri
function uri() {
return substr(full_url(), strlen(base_url())+1);
}
?>
Now code in urlMapping.php
Note - file name and name of the class must be same as you map url in
this file so that you can make call to dynamic classes and function on actions.php. If don't this will not work
<?php
// this $urlMap will be accessed in actions.php
$urlMap = [
// here we use only uri so .. https://example.com/login hit LoginController class along with login function, this is just only the mapping
'login' => ['class'=>'LoginController',
'function'=>'login'],
];
?>
Now actions.php code
<?php
// if call is not like example.com/ means no uri is there
if(uri()!='')
{
// if your uri exists in route collection or urlmapping collection then make call to your dynamic class and methods
if(array_key_exists(uri(), $urlMap))
{
// include the class file dynamically from controllers folder
include_once('controllers/'.$urlMap[uri()]['class'].'.php');
// making references for dynamic class
$controlerObject = new $urlMap[uri()]['class']();
// call function dynaically from the referece
$controlerObject->{$urlMap[uri()]['function']}();
}
else
{
// you can make 404 page not
echo 'No routing found';
}
}
// call for home page
else
{
include_once('index.html.php');
}
?>
Now controllres/LoginController.php class,
Note - As mentioned above file name and name of the class as in urlMapping.php
<?php
class LoginController{
public function login()
{
// .... something your code goes here
echo 'hello from the login function of login controller';
}
//...... other function you can define
}
?>
Before calling $page = $controller->$action(); check if controller and action are exists:
if (isset($routes[$this->route][$this->method]['controller'])
&& isset($routes[$this->route][$this->method]['action'])) {
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
$page = $controller->$action();
// ...
} else {
// return 404 Page not found
}

AltoRouter - mapping/routing home class as default

I get the part how you call your controllers here but how to set 'Home' as default controller and 'index' as default action in AltoRouter
this is wrong but something like
$router->map('GET', '/', function($controller, $action) {
$controller = 'Home';
$action = 'index';
});
Depends a little on what you mean by 'default action'.
If you mean "how do I make the '/' route go to the index() method on my HomeController class", then a simplified version of the linked github issue (and the AltoRouter website) would apply:
$router = new AltoRouter();
$router->setBasePath('/example.com');
$router->map('GET','/', 'HomeController#index');
$match = $router->match();
if ($match === false) {
header($_SERVER["SERVER_PROTOCOL"].' 404 Not Found');
} else {
list($controller, $action) = explode('#', $match['target']);
if ( is_callable([$controller, $action]) ) {
$obj = new $controller();
call_user_func_array([$obj, $action], [$match['params']]);
} else {
// here your routes are wrong.
// Throw an exception in debug, send a 500 error in production
}
}
The # here is completely arbitrary, it's just a delimiter to separate the controller name from the method being called. laravel uses an # for a similar kind of router-to-controller notation (i.e. HomeController#index).
If you meant "if in doubt, show the home page as a default action", then it would look fairly similar to the above, the only difference would be that the 404 path would be simply:
if ($match === false) {
$obj = new HomeController();
$obj->index();
} else {
// etc.
}

Is it possible that __destruct is called before __construct is finished?

I have a project leash and in JSON-RPC service I'm writing to a file in __destruct, but sometimes the config file is cleared (it look like this {"sessions":[],"users":[]}) it happen from time to time and also when I refresh the page fast before ajax request is finished, it look like the __constructor didn't finished before __destruct is called, I have code like this:
class Service {
protected $config_file;
protected $config;
const password_hash = 'h'; // function use for password on installation
const password_regex = '/([A-Za-z_][A-Za-z0-9_]*):(.*)/';
function __construct($config_file, $path) {
$this->path = $path;
$this->config_file = $config_file;
$full_path = $path . "/" . $this->config_file;
if (file_exists($full_path)) {
try {
$this->config = json_decode(file_get_contents($full_path));
} catch (Exception $e) {
$this->config = new stdClass();
}
// it had no write permission when first created while testing
if (!is_writable($full_path)) {
chmod($full_path, 0664);
}
} else {
$this->config = new stdClass();
}
if (!isset($this->config->sessions) || !is_array($this->config->sessions)) {
$this->config->sessions = array();
} else {
$this->config->sessions = array_map(function($session) {
return Session::cast($session);
}, array_filter($this->config->sessions, function($session){
return isset($session->token) && isset($session->username);
}));
}
if (!isset($this->config->users) || !is_array($this->config->sessions)) {
$this->config->users = array();
}
}
// ------------------------------------------------------------------------
function __destruct() {
$path = $this->path . "/" . $this->config_file;
$this->__write($path, json_encode($this->config));
}
I have other methods that modify config object like login that add new session to $this->config->sessions array. Here is full file
I've tried to add flag $this->corrupted that's set to true when I've get exception but it didn't fix the issue, it must be something with time of __destruct being called.
I've also try to add flag $this->safe_to_save that get set to true in last line of constructor but that also didn't work. What may be the problem?

Load configuration file just once

I'm working on my script to convert legacy links to seo friendly urls.
index.php
require 'AltoRouter.php';
$router = new AltoRouter();
$router->setBasePath('/router');
$urls = [
'index.php?option=com_index&task=articles&id=1',
'index.php?option=com_index&task=articles&slug=1-article-title',
'index.php?option=com_index&task=articles.category&cid=100-category1',
'index.php?option=com_shop&task=products&slug=100-amazing-product',
];
foreach($urls as $i=>$url) {
echo $router->getSefUrl($url);
}
AltoRouter.php
...
public function getSefUrl($url) {
$url_clean = str_replace('index.php?', '', $url);
parse_str($url_clean, $output);
$component = empty($output['option']) ? 'com_index' : $output['option'];
$task = empty($output['task']) ? 'index' : $output['task'];
$path = 'components/'.$component.'/routes/routes.json';
$data = json_decode(file_get_contents($path));
if (!empty($data)) {
foreach($data as $route) {
$this->map($route[0], $route[1], $route[2], $route[2]);
}
}
$route_info = $this->findUrlFromRoutes($task);
return empty($route_info) ? $url : $this->generate($route_info->task, $output);
}
...
My question: Every time when I'm using getSefUrl method I'm loading routes from external file. Is it ok? Or can I optimize code above some kind? If yes - how to?
Thanks!
You could avoid multiple fetches and decodes in your loop by breaking that out.
In AltoRouter.php
private $routes = array();
function getComponentRoutes($component)
{
if(! isset($this->routes[$component])) {
$path = 'components/'.$component.'/routes/routes.json';
$this->routes[$component] = json_decode(file_get_contents($path));
}
return $this->routes[$component];
}
You can replace that require with require_once or better use autoloading :
You may define an __autoload() function which is automatically called
in case you are trying to use a class/interface which hasn't been
defined yet. By calling this function the scripting engine is given a
last chance to load the class before PHP fails with an error.
Create a folder and put all your required classs in this folder:
function __autoload($class) {
require_once "Classes" . $class . '.php';
}

Symfony 1.4 Tasks Cannot Access Classes

I'm trying to run a Symfony task but when i try to instantiate a class i get a fatal error.
The class is located in
Apps
MyApp
lib
My code is as follows
protected function execute($arguments = array(), $options = array())
{
$this->reloadAutoload();
// initialize the database connection
$databaseManager = new sfDatabaseManager($this->configuration);
$connection = $databaseManager->getDatabase($options['connection'])->getConnection();
// Get file from dir
if(!in_array($arguments['filename'] . $this->fileExt, scandir($this->path))) {
$this->logSection('Import CSV', 'File doesn\'t exist.', null, 'ERROR');
return;
}
$path = $this->path . '/' . $arguments['filename'] . $this->fileExt;
if(class_exists('CsvReader')) {
$csvReader = new CsvReader($path);
} else {
$this->logSection('Import CSV', 'Class doesn\'t exist.', null, 'ERROR');
return;
}
// add your code here
$this->logSection('Import CSV', 'All content imported.');
}
If there is anything i'm missing then let me know and i'll amend my question.
Thanks.
There are a couple things that might be preventing you from calling a class in your app:
You need to refresh your cache with php symfony cc
Your Symfony autoloader fails to find your class because the filename does not end with .class.php
Source: Documentation

Categories