Functions autoloading in PHP

Estándar

Please excuse me for the typos, but I’m a Spanish-speaking person.

Intro

A few days ago I was seeing news of PHP 5.3 (too late, I know), and I saw some that I thought were important: namespaces and classes autoloading.

On these two issues I thought to investigate how to remake my old projects, in order to reduce the amount of code lines occupying the file import statements.

As usual, I usually create a class MyClass in a file MyClass.php, and a function my_function in a file my_function.php, making me life easy to recognize at first glance what that file is and getting an understandable and organized project.

What I’m trying to say? In this article I will use a similar file structure, in order to assist in the organization and see how using namespaces and classes autoloading can be much easier to do our work as PHP programmers.

But why the title says functions autoloading if you’re going to teach me classes autoloading? Well, because we are going to use classes autoloading and apply it to load functions through a class.

Hands on!

Now that you have more or less a vision of what is to be done, let’s start with the code.

fs-afnThe next image shows the file system structure that I will use throughout the article. Show as a picture facilitates our understanding, avoiding mistakes then.
As we can see, we have a main file index.php, in which we will carry out the tests. All PHP code required in this article is located within php folder, and as you might guess, inside it will be our classes, functions, and helpers folder haves the PHP script that will allow us to make functions autoloading.

I will start to show code for each file, to see the why I organized them in such manner.

<?php

namespace php\classes;
use php\helpers\Fn;

class Class1 {
    
    public $msg = "Hello, I'm first class.";
    
    public function say() {
        Fn::say($this->msg);
    }
    
}

?>
<?php

namespace php\classes;

class Class2 extends Class1 {
    
    public function __construct() {
        $this->msg = "Hi, I'm second class.";
    }
    
}

?>
<?php

namespace php\functions;

function say($var) {
    if (is_string($var) || is_numeric($var)) {
        echo $var;
    } elseif (is_bool($var)) {
        echo $var ? 'true' : 'false';
    } elseif (is_object($var) && method_exists($var, '__toString')) {
        // echo $var->__toString();
    } elseif (is_array($var)) {
        $s = array();
        foreach ($var as $k => $v) {
            $s[] = $k . ' = ' . $v;
        }
        echo implode('&', $s);
    } elseif (is_null($var)) {
        echo 'null';
    }
}

?>
<?php

namespace php\helpers;

/**
 * Autoloading functions in PHP.
 * 
 * @author Bryan Horna <bryanjhv@gmail.com>
 * @version 0.2
 */
class Fn {
    
    private function __construct() {}
    private function __wakeup() {}
    private function __clone() {}
    
    public static function __callStatic($fname, $args) {
        if (!function_exists($fn)) {
            $fn = "php\\functions\\$fn";
            require str_replace('\\', '/', $fn) . '.php';
        }
        return call_user_func_array($fn, $args);
    }
    
}

?>
<?php

# include_once 'php/autoload.php';
spl_autoload_register();

use php\helpers\Fn;
use php\classes\Class2;

# Using the created class
(new Class2())->say();

# Custom functions
Fn::say("Hello, world!");

# PHP built-in functions
echo Fn::str_replace(" ", "", "This is a text.");

?>

Am I missing something? Ah, yes, sorry. I forgot to include the code of php/autoload.php, but do you need that file? It’s not, but if you want to complicate things, you can create it and write <?php spl_autoload_register(); ?> and ready.

Code explanation

Now I will explain the code, but only the parts that are neccessary to explain. I.e., do not explain how inheritance works in PHP, just explain why the simplicity of the code.

  • Namespaces: Namespaces in PHP were introduced in PHP 5.3, and like that in other programming languages, they act encapsulating the code we have in "packages". You can find lots of info on the Internet about it.
  • Classes autoloading: The class autoloading is available in PHP since version 5.1.2 (the spl_autoload_register function). But the good news say that when we work with namespaces equal to our filesystem structure, becomes useless to create an autoload function, because PHP does this process automatically.
  • Fn class: This class will help us to dynamically load functions. The only change we make in our code is to add Fn:: before the call to one of our defined functions. Then I made __construct, __wakeup and __clone because they wouldn’t be needed in the class that we created. Moreover, the __callStatic method does it all.

Now, the million-dollar question: and what would help me this? Well, I tell you what I did with this. "I was doing a users system, when suddenly it ocurred to me to use sha1 and md5 to encrypt user passwords. When I gave a look at the PHP site, I realized that I should not use these functions for that. I thought: what do I then need? When I see the PHP site, there is a nice function called password_hash, which does this job for you. There are other functions that help this. If you clicked the link above, you’ll see that it was introduced in PHP 5.5, and my server does not have that version, haves PHP 5.4. I looked for a polyfill which convinced me. But how do you know if password_hash is in the PHP version you are using, because I don’t want to make a version check and also be checking if it exists whenever you need it. It was there where I decided to make Fn::password_hash and ready! If I run it in PHP 5.4, it will call the polyfill; if it is PHP 5.5 it will call PHP 5.5’s built-in function.".

I hope this has been helpful. As always, you can download the code used from here, and if you have any doubts, do not forget to comment in order to resolve them. Happy coding!

Anuncios

Autocarga de funciones en PHP

Estándar

Introducción

Hace algunos días anduve viendo las novedades de PHP 5.3 (muy tarde, lo sé), y vi algunas que me parecieron importantes: los espacios de nombres (namespaces) y la autocarga de clases (autoload).

De estos dos temas pensé en investigar cómo rehacer mis proyectos antiguos, para así reducir en cantidad las líneas de código que ocupaban las sentencias de importación de archivos.

Como es usual, acostumbro crear una clase MiClase en un archivo MiClase.php, y una función mi_funcion en un archivo mi_funcion.php, haciéndome así la vida fácil al reconocer a primera vista de qué se trata dicho archivo y obteniendo un proyecto entendible y organizado.

¿Qué estoy queriendo decir? Que en este artículo usaré una estructura de archivos similar, a fin de ayudar en la organización y ver cómo, usando los espacios de nombres y la autocarga de clases se puede hacer mucho más fácil nuestro trabajo como programadores PHP.

Pero, ¿por qué el título dice autocarga de funciones si me vas a enseñar autocarga de clases? Bien, porque vamos a usar la autocarga de clases y aplicarlo para cargar funciones mediante una clase.

¡Manos a la obra!

Ahora que ya se tiene más o menos una visión de lo que se va a hacer, comencemos con el código.

fs-afnLa imagen de al lado muestra la estructura del sistema de archivos que usaré a lo largo del artículo. Mostrarlo como imagen facilita nuestro entendimiento, evitando cometer errores luego.
Como podremos ver, tenemos un archivo principal index.php, en el cual realizaremos las pruebas. Todo el código PHP necesario para el artículo se encuentra dentro de la carpeta php, y como es de adivinarse, dentro de ella estarán nuestras clases (carpeta classes), funciones (carpeta functions) y en la carpeta helpers tenemos el script PHP que nos permitirá hacer la autocarga de funciones.

Comienzo a mostrar el código de cada archivo, para así ver el por qué los organicé de dicha forma.

<?php

namespace php\classes;
use php\helpers\Fn;

class Class1 {
    
    public $msg = "Hello, I'm first class.";
    
    public function say() {
        Fn::say($this->msg);
    }
    
}

?>
<?php

namespace php\classes;

class Class2 extends Class1 {
    
    public function __construct() {
        $this->msg = "Hi, I'm second class.";
    }
    
}

?>
<?php

namespace php\functions;

function say($var) {
    if (is_string($var) || is_numeric($var)) {
        echo $var;
    } elseif (is_bool($var)) {
        echo $var ? 'true' : 'false';
    } elseif (is_object($var) && method_exists($var, '__toString')) {
        // echo $var->__toString();
    } elseif (is_array($var)) {
        $s = array();
        foreach ($var as $k => $v) {
            $s[] = $k . ' = ' . $v;
        }
        echo implode('&', $s);
    } elseif (is_null($var)) {
        echo 'null';
    }
}

?>
<?php

namespace php\helpers;

/**
 * Autoloading functions in PHP.
 * 
 * @author Bryan Horna <bryanjhv@gmail.com>
 * @version 0.2
 */
class Fn {
    
    private function __construct() {}
    private function __wakeup() {}
    private function __clone() {}
    
    public static function __callStatic($fname, $args) {
        if (!function_exists($fn)) {
            $fn = "php\\functions\\$fn";
            require str_replace('\\', '/', $fn) . '.php';
        }
        return call_user_func_array($fn, $args);
    }
    
}

?>
<?php

# include_once 'php/autoload.php';
spl_autoload_register();

use php\helpers\Fn;
use php\classes\Class2;

# Using the created class
(new Class2())->say();

# Custom functions
Fn::say("Hello, world!");

# PHP built-in functions
echo Fn::str_replace(" ", "", "This is a text.");

?>

¿Me estoy olvidando de algo? Ah, sí, lo siento. Olvidé incluir el código de php/autoload.php, pero ¿es necesario ese archivo? No lo es, pero si quieres complicarte la cosa, puedes crearlo y escribir <?php spl_autoload_register(); ?> y listo.

Explicación del código

Ahora pasaré a explicar el código, sólo en las partes que son necesarias explicar. Es decir, no explicaré cómo trabaja la herencia en PHP, sólo explicaré el por qué de la simplicidad del código.

  • Espacios de nombres: Los espacios de nombres en PHP fueron introducidos en PHP 5.3, y al igual que en otros lenguajes de programación, estos actúan encapsulando el código que tenemos en "paquetes". Puedes encontrar mucha información en Internet sobre ello.
  • Autocarga de clases: La autocarga de clases está disponible en PHP desde su versión 5.1.2 (la función spl_autoload_register). Pero las buenas nuevas dicen que cuando trabajamos con espacios de nombres iguales a nuestra estructura del sistema de archivos, se hace inútil crear una función autoload, pues PHP hace este proceso de forma automática.
  • La clase Fn: Esta clase es la que nos ayudará a cargar dinámicamente las funciones. La única modificación que deberemos hacer en nuestro código es agregar Fn:: antes de la llamada a una de nuestras funciones definidas. Luego, se hizo privados __construct, __wakeup y __clone porque no serían necesarios en la clase que creamos. Por lo demás, el método __callStatic lo hace todo.

Ahora, la pregunta del millón: ¿y para qué me serviría esto? Pues te cuento lo que hice con esto. "Estaba haciendo un sistema de usuarios, cuando de pronto se me ocurrió usar sha1 y md5 para encriptar las contraseñas de los usuarios. Cuando di un vistazo al sitio de PHP, me di cuenta que no debo usar estas funciones para eso. Pensé ¿qué es lo que entonces necesito? Cuando veo por el sitio de PHP, hay una función bella llamada password_hash, la cual hace este trabajo por ti. Además, hay otras funciones que ayudan a esta. Si entraste al enlace anterior, verás que fue introducida en PHP 5.5, y mi servidor no cuenta con dicha versión, pero sí con la 5.4. Busqué un polyfill el cual me convenció. Pero ¿cómo saber si password_hash está en la versión que esté usando de PHP, pues no quiero hacer un chequeo de versiones y tampoco estar verificando si existe cada vez que la necesite. Fue allí donde decidí hacer Fn::password_hash y listo. Si lo corro en PHP 5.4, llamará al polyfill; si es PHP 5.5, a la función que PHP incluye."

Espero esto te haya sido de utilidad. Como siempre, puedes descargar el código usado desde aquí, y si tienes dudas, no olvides comentar para así poder resolverlas. Happy coding!