Lazy loading, am I doing this right? - php

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.

Related

Dependency Injection of Dynamically Called Class

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.

Calling various methods of a controller from the loader

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); }
});

spl_autoload_register loads class twice

I keep trying to know what the problem is with this very simple class loader script.
The class loader looks like this:
#src/vendors/Autoloading/lib/ClassLoader.php
namespace App\Vendors\Autoloading;
class ClassLoader
{
private $path;
function __construct($path)
{
$this->path = $path;
}
public function load($class)
{
if(file_exists( $class = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $this->path) . '.php')){
require $class;
return true;
}
}
public function register()
{
return spl_autoload_register([$this, 'load']);
}
}
The initial class loader had more methods and some functions to validate the file names ...
but, in the process of debugging I had to narrow it to that.
So, that class loader is being required inside an autoload.php file, as you can see below.
#src/vendors/autoload.php
namespace App\Vendors;
require 'Autoloading/lib/ClassLoader.php';
$autoload = new Autoloading\ClassLoader('path/Foo/FooClass');
$autoload->register();
The FooClass.php is located in src/Foo/FooClass.php
namespace App\Foo;
class FooClass{}
and there is actually no problem with the autoloading part, the class gets loaded just fine, but it is done twice which shows me the below error. I am calling it from an index.php file
<?php
use \App\Foo\FooClass;
FooClass::somefunction();
Just using that generates this error.
Fatal error: Cannot redeclare class path\foo\FooClass in /path/to/index.php on line 4
Your autoloading function is wrong:
public function load($class)
{
if (
file_exists(
$class = str_replace(
array('\\', '_'),
DIRECTORY_SEPARATOR,
$this->path)
. '.php')
) {
require $class;
return true;
}
}
The function is calles with the name of the class to be loaded (if impossible, the function should do nothing).
What you do is ignoring the class name, and create a new one based on the path the autoloader is created with. This will always load the same file, with the same class, even if there are different classes to be loaded.
And this explains why you get the error, because no matter which class name gets passed, you always include the one file that is related to the path.
You probably want to use a proper PSR-0 or PSR-4 autoloader. I would recommend using the one that comes with Composer, as you are likely to be using Composer sooner or later yourself. Why not starting today?
Check if the class already exists using class_exists before checking for the file
Try using require_once instead of require. Your autoloader won't keep track of what files have been loaded so far.
Here's how I set mine up. I don't use a class. Maybe this will help
function PlatformAutoloader($classname) {
try {
// Change \ to / so namespacing will work
$classname = strtolower(str_replace('\\', DIRECTORY_SEPARATOR, $classname));
if(!#include_once(DIR_CLASSES . DIRECTORY_SEPARATOR . $classname . '.php')) return false;
} catch(Exception $e) {
return false;
}
}
spl_autoload_register('PlatformAutoloader');

Autoloading both Classes & Models

I know it's common practice to autoload your controllers when using an MVC framework. I have made my own mini-framework where controllers are autoloaded fine.
Are there any security/bad issues with having the same autoload function load the models too?
I.e.
function __autoload($className) { // Autoload both controllers and models.
if(stristr($className, 'Model'))
{
if (is_readable(Ms . $className . '.php')) {
include Ms . $className . '.php';
}
} else {
if (is_readable(Cs . $className . '.php')) {
include Cs . $className . '.php';
}
}
}
You could use namespaces and spl_autoload_register() in order to get such an autoloader. There's no specific security issues regarding a multi autoloader (an autoloader for multi classes of classes) rather than a controller-only autoloader.
I usually works with namespaces like:
$home = new controller\home;
$home->actionIndex();
$users = new model\users;
$post = new view\post;
from there it's easy to replace a \ in the class name with a / to get the specific paths for the file (obviously doing the needed security checking as always).

From performance perpective do registry pattern is more efficient or autoloader for class instantiation?

In registry pattern we just create objects that we need for example:
public function createAndStoreObject( $object, $key )
{
require_once( $object . '.class.php' );
$this->objects[ $key ] = new $object( $this );
}
And then access it via its key.
In autoloader we do something like this:
<?php
class autoloader {
public static function moduleautoloader($class) {
$path = $_SERVER['DOCUMENT_ROOT'] . "/modules/{$class}.php";
if (is_readable($path)) {
require $path;
}
}
public static function daoautoloader($class) {
$path = $_SERVER['DOCUMENT_ROOT'] . "/dataobjects/{$class}.php";
if (is_readable($path))
require $path;
}
public static function includesautoloader($class) {
$path = $_SERVER['DOCUMENT_ROOT'] . "/includes/{$class}.php";
if (is_readable($path))
require $path;
}
}
spl_autoload_register('autoloader::includesautoloader');
spl_autoload_register('autoloader::daoautoloader');
spl_autoload_register('autoloader::moduleautoloader');
?>
And then it's used as below:
When we want to create an object it should go through includes, modules OR dataobjects folder to find the class and then if it exists, object should be created.
For a huge application do we have to go though registry approach or there are some benefit using autoloader here?
Using require_once is arguably faster than having it automatically load, especially when it's only called once.
But having an autoloader frees you from explicitly loading all the classes you need, it will just be there. And it's on-demand, so if you decide to change your code and no longer need a particular class you don't have to remember to stop including the class definition file.
Btw, based on your code I would recommend putting them into one auto loader function:
function my_autoload($class) {
static $docroot = $_SERVER['DOCUMENT_ROOT'];
// alternative to goto block
do {
$path = "$docroot/modules/$class.php";
if (file_exists($path)) {
break;
}
$path = "$docroot/dataobjects/$class.php";
if (file_exists($path)) {
break;
}
$path = "$docroot/daoobjects/$class.php";
if (file_exists($path)) {
break;
}
// we can't find the file, so return
return;
} while (false);
// a path exists, so load it
require_once($path);
}
spl_autoload_register('my_autoload');
This would perform somewhat better than having three separate functions.
To make it more efficient you could consider using namespaces; they form a logic directory structure and make the auto loader simpler to make.

Categories