As part of my learning process, I'm building my own MVC framework which not only helps me learn, but hopefully it will help me keep my future code cleaner and more maintainable. Currently I'm trying to get my head around dependency injection.
Here's what I'm doing:
My ultra simple MVC framework explodes $_SERVER['REQUEST_URI'] and expects the first element to be the class, the second element to be the method, and any remaining elements to be an array of parameters. This all works splendidly.
I have a base Controller class which all my core classes extend. The sole functionality of this Controller class is to facilitate the requiring of and instantiating of Model classes and Views, like so:
class Controller {
protected $modelsPath = ROOT . 'app' . DS . 'models' . DS;
protected $viewsPath = ROOT . 'app' . DS . 'views' . DS;
public function model($model) {
$model = ucfirst(strtolower($model));
if (file_exists($this->modelsPath . $model . ".model.php")) {
require_once $this->modelsPath . $model . ".model.php";
return new $model;
} else {
return false;
}
}
public function view($view, $data = []) {
$view = strtolower($view);
if (file_exists($this->viewsPath . $view . DS. $view . "View.php")) {
require_once $this->viewsPath . $view . DS . $view . "View.php";
} else {
die('View does not exist');
}
}
This all works as anticipated.
Then it occurred to me that I could use the get_called_class() function in my Controller class to in essence auto-load a supporting Model class if it exists. So I added this code as a __construct function in my Controller class:
protected $modelToCall = null;
public function __construct() {
$modelToCall = ucfirst(strtolower(get_called_class()));
if (file_exists($this->modelsPath . $modelToCall . ".model.php")) {
require_once $this->modelsPath . $modelToCall . ".model.php";
$this->modelToCall = new $modelToCall;
}
}
Because this is called in my base controller which is extended by whatever class I've just called, this allows me to use my Model's methods like so from my parent class:
class Tests extends Controller {
public function test() {
$this->modelToCall->testsModelMethod();
}
}
Now my question:
Is this considered dependency injection? If not, is there a way to properly accomplish dependency injection from a model that has been dynamically loaded in such a way?
Perhaps there's no benefit in automatically instantiating in my base controller, versus simply instantiating from my parent controller?
As I said, I'm learning, and I guess in this case I'm simply seeking guidance from people FAR more experienced and knowledgeable than me so I hopefully get my head around this subject and avoid grave programming mistakes that will bite me later.
Thank you in advance for your consideration and your patience.
Ok, I think I've answered my own question.
It's perfectly normal and expected to call a model from the model's controller, which is what my Controller is automatically doing. But this is NOT dependency injection, as #yivi pointed out.
However, what follows IS dependency injection, I think.
I stepped back to the boostrap of my simple MVC framework and instantiated my Database class at the top. I was then able to pass this into my Controller class and thru that I am able to inject it into each model that is dynamically loaded, like so:
public $database;
protected $modelToCall;
public function __construct($database) {
$this->database = $database;
$tmpClass = strtolower(get_called_class());
$modelToCall = rtrim(ucfirst($tmpClass), 's');
if (file_exists($this->modelsPath . $modelToCall . ".php")) {
require_once $this->modelsPath . $modelToCall . ".php";
$this->modelToCall = new $modelToCall($database);
}
}
Now, from within any controller, I'm able to call that controller's model methods by simply using $this->modelToCall and I have full access to my Database class from each model, and I only had to instantiate it once.
It works, but do I have my terminology correct now?
Thanks in advance.
Related
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); }
});
I'm developing an MVC framework and I have a problem with making a flexible code/Structure in declaring helper classes
class Controller {
public $helper = [];
public function load_helper($helper) {
require_once(DIR_HELPER . $helper . '.php');
$lc_helper = StrToLower($helper);
$helper_arr[$lc_helper] = new $helper;
$this->helper[$lc_helper] = $helper_arr[$lc_helper];
}
}
//I'm calling the function in my controllers like this
Class Home Extends Controller {
$this->load_helper('Form');
$this->helper['form']-><class function>;
}
I want to call the function like this:
$this->form-><class function>;
I can't use extract for public functions but I've seen frameworks that can do it.
I hope someone has an idea and that someone can understand my question, thanks in advance.
Take a look at the magic __get method. From the documentation:
Overloading in PHP provides means to dynamically "create" properties
and methods. These dynamic entities are processed via magic methods
one can establish in a class for various action types.
The overloading methods are invoked when interacting with properties
or methods that have not been declared or are not visible in the
current scope. The rest of this section will use the terms
"inaccessible properties" and "inaccessible methods" to refer to this
combination of declaration and visibility.
This could be implemented for example this way:
class Controller {
public $helper = [];
public function load_helper($helper) {
require_once(DIR_HELPER . $helper . '.php');
$lc_helper = StrToLower($helper);
$helper_arr[$lc_helper] = new $helper;
$this->helper[$lc_helper] = $helper_arr[$lc_helper];
}
public function __get($property) {
//Load helper if not exists
if (!isset($this->helper[$property])) {
$this->load_helper($property);
}
//Return the helper
return $this->helper[$property];
}
}
Side note:
Controller::$helper and Controller::load_helper() in my understanding should be private or protected instead of public.
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
I'm writing API implementation and my main API class has __call() magic method:
public function __call($name, $params)
{
if (in_array($name, $this->resources))
{
require_once APPPATH . 'resources' . DIRECTORY_SEPARATOR . $name . '.php';
$class_name = ucfirst($name);
return new $class_name($params);
}
}
So basically in my application if I write
$api->product()->get($product_id);
// or
$api->product()->post($product);
resources/product.php file is included, Product object created and appropriate method called. Is this a correct way to do lazy loading and is there a better way to implement an API?
You can add your own autoloader afterwards. If "first" autoloader does not find file you have maybe a second logic for it or a third...
spl_autoload_register(); #http://www.php.net/manual/en/function.spl-autoload-register.php
In that situation you dont have to care about your frameworks/applications autoloader.
They say that eval() is evil. I want to avoid the use of the eval() line using proper PHP5 functionality. Given a class name in a static class method, how do I make it return a real object?
class Model {
public static function loadModel($sModelPath) {
if (!(strpos(' ' . $sModelPath, '/')>0)) {
$sModelPath .= '/' . $sModelPath;
}
$sModelName = str_replace('/','_',$sModelPath);
// P is a global var for physical path of the website
require_once(P . '_models/' . $sModelPath . '.php');
eval("\$oObject = new $sModelName" . '();');
return $oObject;
}
}
return new $sModelName();
You can call functions by a dynamic name as well:
$func = "foobar";
$func("baz"); //foobar("baz")
Yep, Kenaniah beat me to it. Gotta type faster...
More info here: http://php.net/manual/en/language.oop5.php, see the first user note.
I know this is a 2 year old question, but I just want to point out, you all missed the question here! All your functions do not return an instantiated class, but a new class. This includes the initial function posted from the questioner!
If you want to return an instantiated class, you have to keep track of your classes and their properties in an array, and return them from there when you require an instance of the class, instead of a re-initialised class.
This method also saves a lot of processing time if your classes do a lot of processing when they are constructed. It's always better to get an instance than to create a new class, unless you really want the class-properties re-initialized. Pay attention!
Try:
$m = new Model();
$m = $m->makeModel();
class Model {
public static function loadModel($sModelPath) {
if (!(strpos(' ' . $sModelPath, '/')>0)) {
$sModelPath .= '/' . $sModelPath;
}
$sModelName = str_replace('/','_',$sModelPath);
// P is a global var for physical path of the website
require_once(P . '_models/' . $sModelPath . '.php');
function makeModel(){
$model = new $sModelName;
return $model;
}
}
}