Calling various methods of a controller from the loader - php

I am trying to build a loader to load various controller with their methods (as needed) within a controller. I sketched out a simple code on my home controller to call the LeftController (now a dummy controller but I intend to use this controller as menu).
require 'controller/LeftController.php';
$LeftController = new LeftController();
$LeftController->index();
This works within the HomeController. It loads the LeftController controller and displays the method index().
Basing my Loader on the above code this is what I have done till now
class Loader
{
public function controller($controller)
{
$file = 'controller/' . $controller . '.php';
$class = $controller;
if (file_exists($file)) {
require($file); // require 'controller/LeftController.php';
$controller = new $class(); //$LeftController = new LeftController();
var_dump($controller);
}
}
}
This works too and the controller is instantiated. I see result using the var_dump().
Now, I need to call the method, as we see at the top most code $LeftController->index(); but on the Loader class this time.
One way of doing this is if I add $controller->index() right after the $controller = new $class(); but this will always call the index() method of the controller.
How do I code this method part as such that I can call any method associated with the controller and not just the index().

You can pass a method argument with your controller:
public function controller($controller, $method)
and then call it on your newly created object:
$controller->$method()
However,
it seems you are trying to reinvent the wheel. The part where you verify if a files exists, include it and instantiate the class, is called autoloading.
The code could look like this:
public function controller($controller, $method)
{
$instance = new $controller();
return $instance->$method();
}
While the autoloading part makes use of spl_autoload_register() to manage finding and including files.
The spl_autoload_register() function registers any number of autoloaders, enabling for classes and interfaces to be automatically loaded if they are currently not defined.
So you can use the code you already have, and abstract it from the action of instantiating the class:
spl_autoload_register(function autoloader($controller) {
$file = 'controller/' . $controller . '.php';
if (file_exists($file)) { require($file); }
});

Related

PHP How to create helper function for class method

I would like to create some global functions that make use of an existing class method. I don't have all of the supporting functionality for the getConfig() method shown, but it's irrelevant to my question:
class Init {
public function getConfig($type) {
if (isset($this->config[$type])) {
return $this->config[$type];
}
$file = dirname(__FILE__, 3).'/app/config/'.$type.'.php';
if (!file_exists($file)) {
return false;
}
$contents = include($file);
$this->config[$type] = $contents;
return $this->config[$type];
} // End getConfig
}
// class Init gets instantiated in my bootstrap file
Now, in my helpers.php file:
function app_config($key) {
$init = new Init(); // Is there any better way of doing this?
return $init->getConfig('app')[$key];
}
So, the helper function app_config may be used in 20 places throughout the app for any given request. Not to mention other helper functions that utilize the Init class as well as other classes.
What is the best way to setup these helper functions? Should they be instantiating these classes over and over again? (This makes it extremely difficult with classes which have specific parameters passed to the constructor)

Slim 3 render method not valid

I want to make simple template rendering in Slim3 but I get an error:
Here is my code :
namespace controller;
class Hello
{
function __construct() {
// Instantiate the app
$settings = require __DIR__ . '/../../src/settings.php';
$this->app = new \Slim\App($settings);
}
public function index(){
return $this->app->render('web/pages/hello.phtml'); //LINE20
}
}
This is the error I get :
Message: Method render is not a valid method
The App object doesn't handle any rendering on its own, you'll need a template add-on for that, probably this one based on your template's .phtml extension. Install with composer:
composer require slim/php-view
Then your controller method will do something like this:
$view = new \Slim\Views\PhpRenderer('./web/pages');
return $view->render($response, '/hello.phtml');
You'll eventually want to put the renderer in the dependency injection container instead of creating a new instance in your controller method, but this should get you started.
I handle this by sticking my renderer in the container. Stick this in your main index.php file.
$container = new \Slim\Container($configuration);
$app = new \Slim\App($container);
$container['renderer'] = new \Slim\Views\PhpRenderer("./web/pages");
Then in your Hello class's file.
class Hello
{
protected $container;
public function __construct(\Slim\Container $container) {
$this->container = $container;
}
public function __invoke($request, $response, $args) {
return $this->container->renderer->render($response, '/hello.php', $args);
}
}
To clean up this code, make a base handler that has this render logic encapsulated for you.

How to include model/view and call classes in simple php mvc example

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.

Dynamic class name in PHP

I'm trying to create a system that it has a GeneralObj. The GeneralObj allows user to initiate special objects for database's tables with a String of the table name. So far, it works perfect.
class GeneralObj{
function __construct($tableName) {
//...
}
}
However, it is too tired to type new GeneralObj(XXX) every time.
I am wondering is that possible to simplify the process to like new XXX(), which is actually running the same as new GeneralObj(XXX)?
I spot PHP provided __autoload method for dynamic loading files in the setting include_path but it requires a the actually definition file existing. I really don't want to copy and copy the same definition files only changing a little.
For cause, eval is not an option.
Maybe you can just auto-create the files in the autoloader:
function __autoload($class_name) {
// check for classes ending with 'Table'
if (preg_match('/(.*?)Table/', $class_name, $match)) {
$classPath = PATH_TO_TABLES . '/' . $match[1] . '.php';
// auto-create the file
if (!file_exists($classPath)) {
$classContent = "
class $class_name extends GeneralObj {
public __construct() {
parent::__construct('{$match[1]}');
}
}";
file_put_contents($classPath, $classContent);
}
require_once $classPath;
}
}
Use inheritance. Make GeneralObj the superclass of the table specific classes. This way you can dynamically derive class names and instantiate objects. Example:
class someTable extends GeneralObj {
}
$tableName = 'some';
$className = $tableName . 'Table';
$obj = new $className;
No, this is not possible.
The runkit extension allows programmatic manipulation of the PHP runtime environment, but it cannot do this. Even if it could, it would IMHO be a very bad idea, greatly impacting the requirements and complexity of the application in exchange for saving a few keystrokes.
In an unrelated note, your GeneralObj class has functionality that sounds suspiciously like that of a dependency injection container. Perhaps you should consider replacing it with one?
Something like this autoloader:
myAutoloader::Register();
class myAutoloader
{
/**
* Register the Autoloader with SPL
*
*/
public static function Register() {
if (function_exists('__autoload')) {
// Register any existing autoloader function with SPL, so we don't get any clashes
spl_autoload_register('__autoload');
}
// Register ourselves with SPL
return spl_autoload_register(array('myAutoloader', 'Load'));
} // function Register()
/**
* Autoload a class identified by name
*
* #param string $pClassName Name of the object to load
*/
public static function Load($pClassName){
if (class_exists($pClassName,FALSE)) {
// Already loaded
return FALSE;
}
$pClassFilePath = str_replace('_',DIRECTORY_SEPARATOR,$pClassName) . '.php';
if (file_exists($pClassFilePath) === FALSE) {
// Not a class file
return new GeneralObj($pClassName);
}
require($pClassFilePath);
} // function Load()
}
And it's up to GeneralObj to throw an exception if the table class can't be instantiated

How do you add abstract class library in the Codeigniter framework?

I have the following code in file called AbstractClass.php in the libraries folder
abstract class AbstractClass {
abstract protected doSomething ();
}
class ConcreteClass extends AbstractClass {
public function doSomething () {};
}
When I try to load the AbstractClass from controllers as follows:
$this->load->library('AbstractClass');
I get Unable to load the requested class: AbstractClass error.
What am I doing wrong? Should I just include the file rather than loading it?
Thanks
Well obviously you cannot load an abstract class directly as this goes against the point of an abstract class.
You can put an abstract class in a file along with another library, but that is a bit pointless and goes against the "one class one file" standard that CI (and all good standards) suggest.
You can include this file with an include() in your library files, or set up an __autoload() function to do it for you. Best place for an __autoload() is the bottom of config.php.
I use abstract classes with CodeIgniter libraries because I have common methods which I want all inherited classes to use which are meaningless on their own. I don't know if what I'm about to suggest is best practise. I suspect it's not, but I personally find it useful. Here's how I do it:
Create a new classes folder in the CodeIgniter application folder.
Add this folder to the path. (I usually do this in the controller.)
if (!strstr(get_include_path(), APPPATH . 'classes')) {
ini_set('include_path', get_include_path() . ':' . APPPATH . 'classes');
}
Create the abstract classes, or other classes, in the classes folder.
Create an extended CodeIgniter library:
require_once('an_abstract_class.php');
class concrete_library extends an_abstract_class {
Use the library as normal:
$this->load->library('concrete_library');
That should do the trick. I hope this is useful.
Ok. I know this is WAY late, but I'm sure many people are having questions about this.
This is actually a limitation of the core Loader class, as it attempts to instantiate each of the items defined by the first parameter. As we all know, Abstract Classes by their very definition are Abstract and CANNOT be instantiated. So how do we get around this?
But most importantly: How do we get around this while conforming to the CodeIgniter Standards?
Since I've only just started using CodeIgniter, I can't really say for certain how Core Extensions were handled in the past. However, in the most recent version, the CodeIgniter framework will allow you to extend and override its core classes by Prefixing the filename with your defined Subclass Prefix (in most cases "MY_") followed by the name of the file you plan on extending.
*/application/core/MY_Loader.php*
<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Loader extends CI_Loader{
public function __construct(){
parent::__construct();
}
}
?>
Chances are good that if you know how to utilize Abstract Classes, you know what this does. Basically, this Class now inherits all properties and methods of the original CI_Loader class. The code snippet above is technically an exact duplicate of the original Loader Class, but the most important thing here is that now this class will take over all load methods rather than the original.
Now all we need to do is provide the loader class with the means to know if it's loading and instantiating a concrete class, or if it's simply including an abstract class.
There are two methods that handle the loading of any Libraries:
Method 1) public function library
Method 2) protected function _ci_load_class
Method 1 handles the processing of all parameters passed to it by iterating through itself if the first parameter is an array, makes sure the data provided is clean, and prevents any actions from being taken if certain criteria aren't met.
Method 2 handles the actual loading of the necessary assets, error handling, etc.
We can override the behavior of methods 1 and 2 by redefining them within our new MY_Loader Class. I've done this by creating almost exact replicas of the original methods, but with the addition of a 4th parameter that - when true - will prevent the Loader from instantiating the defined Library Class in the second method. I've also included an additional method public function abstract_library that will allow you to explicitly define the Library as Abstract in shorthand fashion.
The following is the MY_Loader.php class in its entirety. This will not affect any existing calls to the library method.
Hope this helps!
*/application/core/MY_Loader.php*
<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Loader extends CI_Loader{
public function __construct(){
parent::__construct();
}
public function library($library = '', $params = NULL, $object_name = NULL, $is_abstract=false){
if(is_array($library)){
foreach ($library as $class){
$this->library($class, $params);
}
return;
}
if($library == '' OR isset($this->_base_classes[$library])){
return FALSE;
}
if(!is_null($params) && ! is_array($params)){
$params = NULL;
}
$this->_ci_load_class($library, $params, $object_name, $is_abstract);
}
public function abstract_library($library=''){
$this->library($library, NULL , NULL, true);
}
protected function _ci_load_class($class, $params = NULL, $object_name = NULL, $is_abstract=false)
{
$class = str_replace('.php', '', trim($class, '/'));
$subdir = '';
if(($last_slash = strrpos($class, '/')) !== FALSE){
$subdir = substr($class, 0, $last_slash + 1);
$class = substr($class, $last_slash + 1);
}
foreach(array(ucfirst($class), strtolower($class)) as $class){
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php';
if(file_exists($subclass)){
$baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php';
if (!file_exists($baseclass)){
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
if(in_array($subclass, $this->_ci_loaded_files)){
if(!is_null($object_name)){
$CI =& get_instance();
if(!isset($CI->$object_name)){
return $is_abstract ? true : $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($baseclass);
include_once($subclass);
$this->_ci_loaded_files[] = $subclass;
return $is_abstract ? true : $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
$is_duplicate = FALSE;
foreach ($this->_ci_library_paths as $path){
$filepath = $path.'libraries/'.$subdir.$class.'.php';
if(!file_exists($filepath)){
continue;
}
if(in_array($filepath, $this->_ci_loaded_files)){
if(!is_null($object_name)){
$CI =& get_instance();
if(!isset($CI->$object_name)){
return $is_abstract ? true : $this->_ci_init_class($class, '', $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($filepath);
$this->_ci_loaded_files[] = $filepath;
return $is_abstract ? true : $this->_ci_init_class($class, '', $params, $object_name);
}
} // END FOREACH
if($subdir == ''){
$path = strtolower($class).'/'.$class;
return $this->_ci_load_class($path, $params, $is_abstract);
}
if($is_duplicate == FALSE){
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
}
}
?>
Loading an abstract library:
<?php
$this->load->library("My_Abstract_Library", NULL, NULL, true);
/* -- OR -- */
$this->load->abstract_library("My_Abstract_Library");
?>
I haven't seen any examples around the web of Abstract classes with CI so I wanted to confirm that you can have an Abstract library. There are very good reasons which are fundamental to OOP as to why Abstract classes are useful. Fundamentally for me to ensure the child classes follow certain consistencies.
Let me know if you need an example, as you manually have to include the abstract class make sure you only do it once so you don't get issues with redeclaring classes.
Also don't forget if you have a static function or variable in the Abstract class you can still access it directly without having to load the class as in the below
AbstractClass::static_method_you_want_to_call();
I have found a simple way to use an abstract classes in Codeigniter. Just follow these steps. Go to libraries in system folder
Create an abstract class and dont link it with CI just name it with simple word without CI_
Create an another class name it with the same word as you give name to your file. As shown in code below
Extend that class with abstract class.
Then call abstract class function through your CI_class.
System -> libraries -> lib.php
abstract class B
{
public function lib1()
{
echo "This is library 1";
}
public function lib2()
{
echo "This is library 1";
}
}
class CI_lib extends B
{
public function libs(){
$this->lib1();
}
}
Then call that lib from Controller.

Categories