DEV Community

Chris López
Chris López

Posted on

Actualizando la versión de PHP al proyecto Pokedex.

Hace ya tiempo, hice una publicación en la cual desarrolle un Pokedex con simple y llano PHP, solo usando ciertas librerías que sirvieron como herramientas para agregar funcionalidad al proyecto.

Como contexto general, es como un mini framework donde solo tiene funcionalidad de crear rutas, hacer requests y responses y crear controllers.

El repositorio del proyecto lo puedes consultar aquí:
https://github.com/krsrk/pokedex-vanilla-php

Nos vamos a dar a la tarea de actualizar la versión de PHP a la mas actual, que es la 8.2(actualmente la versión que maneja el pokedex es la 7.2).

Después de actualizar las librerías y la versión de PHP, refactorizaremos código basados en las nuevas características de la versión 8.2 de PHP.

Pre requisitos

  • Tener PHP 8.2 instalado en CLI
  • Tener GIT instalado en CLI
  • Tener la versión mas actual de composer instalado en CLI

Manos a la obra

Los primeros pasos que debemos hacer, es actualizar el composer.json con las versiones de las librerías mas actuales y la versión o versiones de PHP que vamos a manejar.

Entonces actualizamos nuestro composer.json con lo siguiente:

"require": {
        "php": "^8.1||^8.2",
        "twig/twig": "^v3.6.0",
        "illuminate/database": "^v10.10.1",
        "vlucas/phpdotenv": "^v5.5.0"
    },
Enter fullscreen mode Exit fullscreen mode

Después ejecutamos el siguiente comando:

composer install
Enter fullscreen mode Exit fullscreen mode

Con estos pasos ya tendríamos actualizadas las librerías que se usaron, así como también la versión de PHP requerida para el proyecto.

Si quieres revisar estos cambios puedes ver el Pull Request en el siguiente link:
https://github.com/krsrk/pokedex-vanilla-php/pull/2

Actualizando código

Instalando nuestra suite de pruebas

Para este paso necesitaremos la librería de PEST, que es una librería o super set de PHPUnit que nos ayuda hacer pruebas unitarias y pruebas de integración. Con esto crearemos las pruebas unitarias del actual código(src/Utils) para poder validar y probar los cambios el código actual.

Instalamos la librería:

composer require pestphp/pest --dev --with-all-dependencies
Enter fullscreen mode Exit fullscreen mode

Despues ejecutamos el siguiente comando:

./vendor/bin/pest --init
Enter fullscreen mode Exit fullscreen mode

El comando anterior nos inicializa la configuración de PEST para empezar a crear nuestros tests.

Ahora, probamos la instalación de PEST con el siguiente comando:

./vendor/bin/pest
Enter fullscreen mode Exit fullscreen mode

Creando nuestra primer prueba

Ya teniendo PEST instalado, vamos a hacer cambios en la clase Response. Estos cambios aplicarían lo siguiente:

  • Implementar el contrato de la clase(crear la interface de la clase Response)
  • Agregar tipado de datos en métodos, propiedades y parámetros(esto se puede hacer desde la versión 8.1).
  • Refactorización de procesos internos de la clase.

Con estos requerimientos definidos, creamos nuestra prueba:

<?php
//tests/Unit/ResponseTest

test('it can create a response object', function (){
    $response = new \Utils\Response();

    expect($response)->toBeObject();
});
Enter fullscreen mode Exit fullscreen mode

Actualizando nuestra clase Response

Como anteriormente mencionamos, el primer requerimiento es hacer el contrato de la clase o interface:

<?php

namespace Utils;

interface ResponseInterface
{
    public function setClosure(): void;

    public function send(Request $request): void;

    public function getClosure(): mixed;

    public function json(mixed $dataResponse, int $codeResponse = 200): void;
}
Enter fullscreen mode Exit fullscreen mode

Implementamos la interface en nuestra clase Response:

class Response implements ResponseInterface
{
  ...
Enter fullscreen mode Exit fullscreen mode

Cambiamos la definición de nuestros métodos:

// Antes
public function send($request)
{

// Después
public function send(Request $request): void
{
  ...
Enter fullscreen mode Exit fullscreen mode

Cambiamos también el constructor de la clase:

// Antes
protected $closure;

public function __construct($closure = null)
{
        if(!is_null($closure)) {
            $this->setClosure($closure);
        }
}

// Después
public function __construct(protected mixed $closure = null)
{
        if(!is_null($this->closure)) {
            $this->setClosure();
        }
}
Enter fullscreen mode Exit fullscreen mode

Cuando inicializamos la propiedad "closure" en el método "setClosure", se le asignan varios valores de "closure types" que son de tipo string; actualmente están escritos sin ninguna asignación directo en el código, o lo que se conoce en el ambiente dev como valores "hardcodeados", para arreglar esto usaremos Enums con los valores de los closure types:

Creamos la clase Enum para implementar los valores:

<?php

namespace Utils\ValueObjects;

enum ClosureTypes: string
{
    case CLOSURE_STRING = 'string';

    case CLOSURE_ARRAY = 'array';

    case CLOSURE_CONTROLLER = 'controller';

    case CLOSURE_FUNCTION = 'function';
}
Enter fullscreen mode Exit fullscreen mode

En nuestro metodo "setClosure" cambiamos esos valores:

...

if (is_array($closure)) {
    // Antes: $closureType = 'array'

    // Después
    $closureType = ClosureTypes::CLOSURE_ARRAY->value;
}

if (is_callable($closure)) {
    // Antes: $closureType = 'closure'

    // Después
    $closureType = ClosureTypes::CLOSURE_FUNCTION->value;
}
Enter fullscreen mode Exit fullscreen mode

Modificamos nuestro archivo de test con lo siguiente:

test('it can create a response object with closure', function (){
    $response = new \Utils\Response('Hello World');

    expect($response)->toBeObject();
});

test('it can create a response object with string closure', function (){
    $response = new \Utils\Response('Hello World');
    $closure = $response->getClosure();

    expect($closure['closure_type'])->toBe(\Utils\ValueObjects\ClosureTypes::CLOSURE_STRING->value);
});

test('it can create a response object with array closure', function (){
    $response = new \Utils\Response(['pokemon' => 'Bulbasaur']);
    $closure = $response->getClosure();

    expect($closure['closure_type'])->toBe(\Utils\ValueObjects\ClosureTypes::CLOSURE_ARRAY->value);
});

test('it can create a response object with function closure', function (){
    $response = new \Utils\Response(fn() => 'Hello World');
    $closure = $response->getClosure();

    expect($closure['closure_type'])->toBe(\Utils\ValueObjects\ClosureTypes::CLOSURE_FUNCTION->value);
});

test('it can create a response object with controller closure', function (){
    $response = new \Utils\Response('PokemonController@index');
    $closure = $response->getClosure();

    expect($closure['closure_type'])->toBe(\Utils\ValueObjects\ClosureTypes::CLOSURE_CONTROLLER->value);
});

test('it expects "unknown" if there is an unknown closure', function (){
    $response = new \Utils\Response(1);
    $closure = $response->getClosure();

    expect($closure['closure_type'])->toBe('unknown');
});
Enter fullscreen mode Exit fullscreen mode

Ejecutamos nuestro test y deberíamos ver algo como lo siguiente:

Image description

Refactorizando el método send()

Una vez que tengamos nuestros test validados, debemos refactorizar el código de nuestra clase.

Para efectos prácticos lo vamos hacer con el método send(), que es el que se encargará de ejecutar el response dependiendo su tipo de closure(string, array, json, function, controller).

El código original del método es el siguiente:

public function send($request)
{
        if ($this->closure['closure_type'] == 'string') {
            echo $this->closure['closure'];
        } elseif ($this->closure['closure_type'] == 'array') {
            echo json_encode($this->closure['closure']);
        } elseif ($this->closure['closure_type'] == 'closure' || $this->closure['closure_type'] == 'controller') {
            $this->_executeClosure($request);
        } else {
            header("HTTP/1.0 404 Not Found");
            exit('<h3>404 - Not Found</h3>');
        }
}
Enter fullscreen mode Exit fullscreen mode

Para refactorizar las sentencias "if" implementaremos un tipo factory para poder instanciar una clase dependiendo algún parámetro de funcionalidad o comportamiento.

Entonces, si por ejemplo tengo un closure_type = 'string' el Factory va instanciar la clase ClosureTypeString y va a ejecutar su método execute().

De esta forma quedaría el código refactorizado del método send():

if (is_null(ClosureTypes::tryFrom($this->closure['closure_type']))) {
    header("HTTP/1.0 404 Not Found");
    exit('<h3>404 - Not Found</h3>');
}

ClosureType::from($this->closure['closure_type'], $this->closure['closure'])->execute($request->getParams());
Enter fullscreen mode Exit fullscreen mode

Agregamos el test de este método ya refactorizado:

test('it can send a string response', function (){
    $_SERVER['REQUEST_URI'] = '/orders/products/1';
    $response = new \Utils\Response('Hello World');
    $response->send(request());
})->expectNotToPerformAssertions();

test('it can send an array response', function (){
    $_SERVER['REQUEST_URI'] = '/orders/products/1';
    $response = new \Utils\Response(['message' => 'Hello World']);
    $response->send(request());
})->expectNotToPerformAssertions();

...

Enter fullscreen mode Exit fullscreen mode

Ejecutamos las pruebas y vemos que todas pasaron:

Image description

Ahora, iniciamos el servidor y ejecutamos nuestro proyecto con alguna ruta que venga registrada en el routes/web.php, deberíamos de ver lo siguiente:

Image description

Ejecutamos todas nuestras pruebas con el siguiente comando:

 ./vendor/bin/pest
Enter fullscreen mode Exit fullscreen mode

Y deberíamos de ver algo como esto:

Image description

Con estos pasos, podríamos decir, que tenemos cubiertos los cambios al integrar las pruebas al código. Es muy importante, que en cambios mayores al código se tengan integradas este tipo de pruebas, ya que estas son las que nos pueden salvar de varios dolores de cabeza y ahorrar tiempo a la hora de hacer algún deploy.

Los cambios los puedes consultar en el siguiente PR:

https://github.com/krsrk/pokedex-vanilla-php/pull/5

Top comments (0)