PHP:MVC - Model View Controller | Loader Class #2
[h2]Introduction:
[/h2]
We’re continuing on coding our framework. In this part of the tutorial, I’ll proceed with the loader and the blocking of the possible creation of new instances for the class App (or actually any class we might happen for someone or something to create an instance of). So let’s witness that below.
The mechanism we want to make is to be able to obtain the name of the class along with its namespace, to locate where the class is located in the file system and to include it. So we need to find a way to ‘connect’ the namespace string with a psychical address of the server.
<?php
namespace KF;
final class Loader {
private function __construct() {
}
public static function registerAutoLoad() {
spl_autoload_register(array("\KF\Loader", 'autoload'));
}
public static function autoload($class) {
echo $class;
}
}
We need one variable that would actually contain an array and will store the registered namespaces. [This namespace, for that directory]:
<?php
namespace KF;
final class Loader {
private static $namespaces = array();
private function __construct() {
}
public static function registerAutoLoad() {
spl_autoload_register(array("\KF\Loader", 'autoload'));
}
public static function autoload($class) {
echo $class;
}
}
The framework will have one namespace, controllers another, libraries another and so on. This will give us an enormous flexibility during the coding process of the future parts of our MVC framework. So now we need a method which will write to the array that is appended to the variable $namespace.
<?php
namespace KF;
final class Loader {
private static $namespaces = array();
private function __construct() {
}
public static function registerAutoLoad() {
spl_autoload_register(array("\KF\Loader", 'autoload'));
}
public static function autoload($class) {
echo $class;
}
public static function registerNamespace($namespace, $path) {
$namespace = trim($namespace);
if (strlen($namespace) > 0) {
if (!$path) {
throw new \Exception('Invalid Path');
}
$_path = realpath($path);
if ($_path && is_dir($_path) && is_readable($_path)) {
self::$namespaces[$namespace] = $_path . DIRECTORY_SEPARATOR;
} else {
throw new \Exception('Namespace directory read error:' . $path);
}
} else {
throw new exception \Exception('Invalid namespace:' . $namespace);
}
}
Basically, our method is the following. It accepts two parameters: the namespace itself and the psychical path. Then we begin the checks. First of all, of course, we trim the namespace because intervals in the beginning of them can cause immense trouble and we check the length of the namespace just to validate whether a valid namespace has been given/supplied.
public static function registerNamespace($namespace, $path) {
$namespace = trim($namespace);
if (strlen($namespace) > 0) {
if (!$path) {
throw new \Exception('Invalid Path');
}
$_path = realpath($path);
if ($_path && is_dir($_path) && is_readable($_path)) {
self::$namespaces[$namespace] = $_path . DIRECTORY_SEPARATOR;
} else {
throw new \Exception('Namespace directory read error:' . $path);
}
} else {
throw new exception \Exception('Invalid namespace:' . $namespace);
}
}
If so, we throw an exception because obviously this is something we wouldn’t want to happen. If there is a valid namespace we check for the path now. If there is a path supplied, we use realpath() function to get it. It both checks whether the path is correct and also returns its value which we could later on use. So then the variable $_path could be either true or false as a value. We check whether it’s a directory and whether it is readable by PHP. If everything is alright, we append the value as a key to the array and for the path we include it itself.
Now we need to actually register the namespace so that we may use it accordingly.
<?php
namespace KF;
include_once 'Loader.php';
class App {
private static $_instance=null;
private function __construct() {
\KF\Loader::registerNamespace('KF', dirname(__FILE__).DIRECTORY_SEPARATOR);
\KF\Loader::registerAutoLoad();
}
public function run(){
echo 'Devoted to Antagonism and Infamous';
}
/**
*
* [@return](/profile/return) \KF\App
*/
public static function getInstance(){
if (self::$_instance == null) {
self::$_instance = new \KF\App();
}
return self::$_instance;
}
}
So instead of hardcoding it in a configuration file, we use this little ‘trick’ with the dirname() function. In that case the framework could be located anywhere and registers by itself. Executing the code we have so far like so, we end up with the following error message:
Fatal error: Call to undefined method KF\Loader::loadClass() in ...
The loader class is not located because we haven’t yet written the method for it.
Let’s see how this would look like.
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
}
}
}
It accepts one parameter which is actually the class that we want to load. When we have a situation when one method is calling another, it’s better to make the implementation in that very method. When we have that sort of call-back function, (in our case autoload), we should call a new method and make the implementation there. So now we loop the namespaces to see whether any matches one of the so far registered. Let’s put it simpler.
\KF\
\KF\Router\RPC\Router.php
\KF\Router
\KF\Router\RPC\Router.php
Let’s say we have a registered namespace \KF\ and try to load a class that is located in that directory that I pointed out above. Then we need to check whether the namespace is in the beginning of the class that we’re trying to load. It’s also vital for it to be in the beginning in order to avoid collisions with the names. That’s why we use the function strpos().
Now let’s run it to see what we get and elaborate on the situation a bit more:
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
echo $k;
}
}
}
KF
Fatal error: Class 'KF\Test' not found in ...
Since we need to specify the delimiter, that’s obviously the backwards slash and we do it like so in the foreach() loop:
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
echo $k;
}
}
}
However, it would be much more wiser to append that to the method because every time where you register once but use many times, we should make the changes there, where we register not where we use. So this will result in the following:
<?php
namespace KF;
final class Loader {
private static $namespaces = array();
private function __construct() {
}
public static function registerAutoLoad() {
spl_autoload_register(array("\KF\Loader", 'autoload'));
}
public static function autoload($class) {
echo $class;
}
public static function registerNamespace($namespace, $path) {
$namespace = trim($namespace);
if (strlen($namespace) > 0) {
if (!$path) {
throw new \Exception('Invalid Path');
}
$_path = realpath($path);
if ($_path && is_dir($_path) && is_readable($_path)) {
self::$namespaces[$namespace.'\\'] = $_path . DIRECTORY_SEPARATOR;
} else {
throw new \Exception('Namespace directory read error:' . $path);
}
} else {
throw new exception \Exception('Invalid namespace:' . $namespace);
}
}
Now let’s echo out some of the contents from the Loader.php file to see what we’ve got so far:
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
echo $k.'<br/>'.$v.'<br />'.$class;
}
}
}
We end up with the following output:
KF\
/store/work/www/kf/
KF\Test
Fatal error: Class 'KF\Test' not found in ...
Those are the namespace itself, the path of the namespace and the class that we want to load. So all we need to do now is to replace the namespace with the directory because we now know it. Obviously, we can use the str_replace() function and say that the delimiter \ should be replaced with the OS delimiter (because that varies):
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
echo $k.'<br/>'.$v.'<br />'.$class;
$f=str_replace('\\', DIRECTORY_SEPARATOR, $class);
echo $f;
}
}
}
Resulting in:
KF/Test
Let’s now do the actual replacement of the delimiters so that we may end up with the desired output.
public static function loadClass($class) {
foreach (self::$namespaces as $k => $v) {
if (strpos($class, $k) === 0) {
echo $k.'<br/>'.$v.'<br />'.$class;
$f=str_replace('\\', DIRECTORY_SEPARATOR, $class);
$f=substr_replace($f, $v, 0, strlen($k));
echo $f;
}
}
}
Now when we execute that code we get the full path of the namespace what we aimed to do in this part of the series (in particular of this tutorial). And, of course force a .php extension:
$f=substr_replace($f, $v, 0, strlen($k)) . '.php';
Conclusion:
I’m overall trying to keep all of the tutorials from these series in a certain length so as to be easily accessible my the members and to be read with bigger interest and expectation for the next part. Thanks for reading!