I'm trying to write my own little MVC framework for my projects, something I can just drop in and get up and running quickly, mostly for learning purposes. Every request gets routed through index.php which has this code:
<?php
// Run application
require 'application/app.php';
$app = new App();
$app->run();
This is my application class:
<?php
class App {
public function run() {
// Determine request path
$path = $_SERVER['REQUEST_URI'];
// Load routes
require_once 'routes.php';
// Match this request to a route
if(isset(Routes::$routes[$path])) {
} else {
// Use default route
$controller = Routes::$routes['/'][0];
$action = Routes::$routes['/'][1];
}
// Check if controller exists
if(file_exists('controllers/' . $controller . '.php')) {
// Include and instantiate controller
require_once 'controllers/' . $controller . '.php';
$controller = new $controller . 'Controller';
// Run method for this route
if(method_exists($controller, $action)) {
return $controller->$action();
} else {
die('Method ' . $action . ' missing in controller ' . $controller);
}
} else {
die('Controller ' . $controller . 'Controller missing');
}
}
}
and this is my routes file:
<?php
class Routes {
public static $routes = array(
'/' => array('Pages', 'home')
);
}
When I try loading the root directory (/) I get this:
Controller PagesController missing
For some reason the file_exists function can't see my controller. This is my directory structure:
/application
/controllers
Pages.php
/models
/views
app.php
routes.php
So by using if(file_exists('controllers/' . $controller . '.php')) from app.php, it should be able to find controllers/Pages.php, but it can't.
Anyone know how I can fix this?
You are using relative path's for your includes. As your application grows, it will become a nightmare.
I suggest you
write a autoloader class, that deals with including files. Use some mapping mechanism that translates namespaces / class names into path's.
use absolute paths. See the adjusted code below.
Example:
// Run application
define('ROOT', dirname(__FILE__) );
require ROOT . '/application/app.php';
$app = new App();
$app->run();
And later:
// Check if controller exists
if(file_exists(ROOT . '/application/controllers/' . $controller . '.php')) {
// Include and instantiate controller
require_once ROOT. '/application/controllers/' . $controller . '.php';
$controller = new $controller . 'Controller';
Related
I want to add Middleware to my Slim project that checks the ip of the user before allowing them access.
My middleware class:
<?php
namespace App\Middleware;
Class IpFilter
{
protected $request_ip;
protected $allowed_ip;
public function __construct($allowedip = array('127.0.0.1'))
{
$this->request_ip = app()->request()->getIp();
$this->allowed_ip = $allowedip;
}
public function call()
{
$checkit = checkIp();
$this->next->call();
}
protected function checkIp()
{
if (!in_array($this->request_ip, $this->allowed_ip))
$app->halt(403);
}
}
My Bootstrap index.php:
<?php
// To help the built-in PHP dev server, check if the request was actually for
// something which should probably be served as a static file
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
return false;
}
require __DIR__ . '/../vendor/autoload.php';
require '../app/middleware/ipfilter.php';
// Instantiate the app
$settings = require __DIR__ . '/../app/settings.php';
$app = new \Slim\App($settings);
$app->get('/test', function() {
echo "You look like you're from around here";
});
// Set up dependencies
require __DIR__ . '/../app/dependencies.php';
// Register middleware
require __DIR__ . '/../app/middleware.php';
// Register routes
require __DIR__ . '/../app/routes.php';
$app->add(new IpFilter);
// Run
$app->run();
I am using a slim skeleton project for my project setup. I get the following error when I run this code.
Fatal error: Class 'IpFilter' not found in
/Applications/XAMPP/xamppfiles/htdocs/slimtest/my-app/public/index.php
on line 34
I still don't properly understand how to add custom classes for middleware in slim. I've seen several tutorials that just make the class and use $app->add('new class) to add the middleware but I can't figure it out. Is there a file I need to update and I am just missing it?
It's been a long weekend with slim and not a lot of resources out there so any help would be greatly appreciated.
UPDATE:
When I remove the namespace App\Middleware from ipfilter.php I don't get the same error. This time I get
Fatal error: Call to undefined method IpFilter::request() in /Applications/XAMPP/xamppfiles/htdocs/slimtest/my-app/app/middleware/ipfilter.php on line 15
Which I understand why but I thought it might help troubleshoot and get to the root of the problem.
Okay, Finally got it to work.
Index.php
<?php
// To help the built-in PHP dev server, check if the request was actually for
// something which should probably be served as a static file
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
return false;
}
use App\Middleware\IpFilter;
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../app/middleware/ipfilter.php';
// Instantiate the app
$settings = require __DIR__ . '/../app/settings.php';
$app = new \Slim\App($settings);
$app->get('/test', function() {
echo "You look like you're from around here";
});
// Set up dependencies
require __DIR__ . '/../app/dependencies.php';
// Register middleware
require __DIR__ . '/../app/middleware.php';
// Register routes
require __DIR__ . '/../app/routes.php';
$app->add(new IpFilter);
// Run
$app->run();
ipfilter.php
<?php
namespace App\Middleware;
Class IpFilter
{
private $whitelist = arrray('127.0.0.1')
protected $request_ip;
public function __invoke($request, $response, $next)
{
$request_ip = $request->getAttribute('ip_address');
return $next($request, $response);
}
public function call()
{
$checkit = checkIp();
$this->next->call();
}
protected function checkIp()
{
if (!in_array($this->request_ip, $this->whitelist)
$app->halt(403);
}
}
KEY: Using App\Middleware\Ipfilter in the index.php. I though using require to add the class would be enough but apparently no.
Shout out to codecourse.com, really helped.
I have the following code (trying to simulate a factory pattern):
CoinConnectorFactory.php
public static function build($connector) {
$connector = "CoinConnector" . ucwords($connector);
if (class_exists($connector)) {
return new $connector();
} else {
throw new InvalidConnectorType($connector);
}
}
And the following folder structure:
/app
/Plugin
/CoinConnector
/Lib
CoinConnectorFactory.php
CoinConnectorGogulski.php
So the problem is, I pass to the build method as a $connector variable this value gogulski but when get into class_exists like this:
class_exists('CoinConnectorGogulski')
Never find the class (that have the same name that the file) and always throw the exception.
Only if I add this line before check if the class exist CakePHP is able to find the class
include_once APP . 'Plugin' . DS . 'CoinConnector' . DS . 'Lib' . DS . $connector . '.php';
That is how I fix my problem at the end:
public static function build($connector) {
$connector = "CoinConnector" . ucwords($connector);
App::uses($connector, 'CoinConnector.Lib');
if (class_exists($connector)) {
return new $connector();
} else {
throw new InvalidConnectorType($connector);
}
}
I made a simple autoloader using spl_autoloader_register function, it works fine on my virtual server, but in the server I only got "Fatal Error: Class 'X' not found". Im running it on a mac with PHP 5.4, but it also works in windows/ubuntu with 5.3 version, which is the same as my physic server. I don't have SSH access to it. Here is my autoload code:
class Load
{
public static function autoload($class)
{
$class = strtolower($class);
$lib = $_SERVER['DOCUMENT_ROOT'] . BASENAME . "/libs/{$class}.php";
$model = $_SERVER['DOCUMENT_ROOT'] . BASENAME . "/models/{$class}.class.php";
$controller = $_SERVER['DOCUMENT_ROOT'] . BASENAME . "/controllers/{$class}.php";
if(is_readable($lib)){
require_once $lib;
}elseif (is_readable($model)) {
require_once $model;
}elseif (is_readable($controller)){
require_once $controller;
}
}
}
spl_autoload_register("Load::autoload");
I always used spl for local apps, but its the first time I'm trying it on the server.
Any advice for better practices will be helpful.
Thanks
A good practice can be to add your own include path. Then you can disclaim $_SERVER['DOCUMENT_ROOT'].
For example..
// Define path to library
define('MY_LIBRARY_PATH', realpath(dirname(__FILE__) . '/../insert_path_here_relativly'));
// Ensure library is on include_path
set_include_path(
get_include_path() . PATH_SEPARATOR . MY_LIBRARY_PATH
);
Then your autoloader..
class Load
{
public static function autoload($class)
{
$class = strtolower($class);
$lib = MY_LIBRARY_PATH . "/libs/{$class}.php";
$model = MY_LIBRARY_PATH . "/models/{$class}.class.php";
$controller = MY_LIBRARY_PATH . "/controllers/{$class}.php";
if(is_readable($lib)){
require_once $lib;
}elseif (is_readable($model)) {
require_once $model;
}elseif (is_readable($controller)){
require_once $controller;
}
}
}
spl_autoload_register("Load::autoload");
I have been fiddling around with Namespace in PHP and was trying to make it work, but it fails
Let me show the example code:
test\views\classes\MainController.php
<?php
namespace test\views\classes;
class MainController
{
public function echoData()
{
echo 'ECHOD';
}
}
test\views\index.php
<?php
require_once '..\autoloader\autoloader.php';
use test\views\classes\MainController;
$cont = new MainController();
$cont->echoData();
test\autoloader\autoloader.php
<?php
spl_autoload_register(null, FALSE);
spl_autoload_extensions('.php');
function classLoader($class)
{
$fileName = strtolower($class) . '.php';
$file = 'classes/' . $fileName;
if(!file_exists($file))
{
return FALSE;
}
include $file;
}
spl_autoload_register('classLoader');
Throws an error:
Fatal error: Class 'test\views\classes\MainController' not found in ..\test\views\index.php on line 6
Am Im missing something!
EDIT: The code works fine when both the index.php and maincontroller.php are in the same directory without using autoloader but using require_once('maincontroller.php');
Does not work if they are in different directories and with autoloader function. Can anyone sort this out.
Thanks
Multiple problems in your code:
The namespace separator (\) is not a valid path separator in Linux/Unix. Your autoloader should do something like this:
$classPath = str_replace('\\', '/', strtolower($class)) . '.php';
if (!#include_once($classPath)) {
throw new Exception('Unable to find class ' .$class);
}
Plus, the paths are all relative. You should set your include path. If your site structure is like this:
bootstrap.php
lib/
test/
views/
index.php
classes/
maincontroller.php
autoloader/
autoloader.php
Your bootstrap.php should look similar to:
$root = dirname(__FILE__);
$paths = array(
".",
$root."/lib",
get_include_path()
);
set_include_path(implode(PATH_SEPARATOR, $paths));
include 'lib/test/autoloader/autoloader.php';
Now, in your test/views/index.php you can just include the bootstrap:
include '../../bootstrap.php';
Add a die statement to your class loader:
$file = 'classes/' . $fileName;
die('File ' . $file . "\n");
And you get
File classes/test\views\classes\maincontroller.php
Is that really where your main controller class lives?
I need to create a simple file overloading system like symfony does with php files and templates. I will give an example to explain what I need:
Given this folder structure:
- root_folder
- modules
-module1
-file1.php
-file2.php
-file3.php
- specific_modules
-module1
-file2.php
I would like to find a way that automatically loads a file if it is found inside the specific_modules folder (file2.php) when called, if it is not found, it should load file2.php normally from the modules directory.
I would like to do it unobstrusively for the programmer, but not sure if it's possible!!
Any help or advice is welcome, thanks in advance!
skarvin
If the files contain only objects with the same name, then you can write your own autoloader function and register it with spl_autoload_register(). Perhaps something like
function my_loader($class)
{
// look in specific_modules dir for $class
// if not there, look in modules dir for $class
}
spl_autoload_register('my_loader');
This will allow you to code simply as:
$obj = new Thing();
And if Thing is defined in specific_modules, it will use that one, else the default one.
$normal_dir = 'modules';
$specific_dir = 'specific_modules';
$modules = array('module1' => array('file1.php','file2.php','file3.php'));
foreach($modules as $module => $files)
{
foreach($files as $file)
{
if(!file_exists("$specific_dir/$module/$file"))
{
include("$normal_dir/$module/$file");
}
else
{
include("$specific_dir/$module/$file");
}
}
}
This code will work as simply for you as possible, it makes it easy to add new files to your modules and change the directory names. By "load" I am making the assumption you mean include, but that part is easy enough to change.
Similarly to Alex's answer, you could also define an __autoload function:
function __autoload($class_name) {
if (file_exists(__DIR__ . '/specific_modules/' . $class_name . '.php')) {
require __DIR__ . '/specific_modules/' . $class_name . '.php';
}
elseif (file_exists(__DIR__ . '/modules/' . $class_name . '.php')) {
require __DIR__ . '/modules/' . $class_name . '.php';
}
else {
// Error
}
}
Then if you do $obj = new Thing(); it will try to load Thing.php from those two directories.