Curso de Refactoring en PHP

Este es un post un poco especial, voy a impartir este curso sobre refactoring en PHP y me gustaría contar un poco como ha sido todo este proceso desde que empecé como alumno en geekshubs academy hasta que nos pusimos manos a la obra para grabar un curso.

Aunque Internet es una fuente inagotable de información. En la red hay miles de artículos, blogs, wikis, listas de distribución sobre programación, testing, docker,… no terminaba de encontrar un curso que de verdad me aportase algo más. Estaba buscando mejorar mis habilidades como programador, que fuese en español, con comunicación directa con los profesores.

rrss.jpg

Un día casi por casualidad, encontré un post de Xavi Gost hablando sobre el curso “De refactoring a patrones” impartido en GeeksHubsAcademy, la verdad es que era escéptico sobre los cursos en video, pero me convenció el hecho de hacer hangouts para poder resolver dudas y además @islomar me dio un empujón en forma de descuento 😉

Seguir leyendo “Curso de Refactoring en PHP”

Anuncios

Mejorando la kata FizzBuzz

Hace unos días hicimos nuestro primer video haciendo la kata FizzBuzz, esto fue un primer acercamiento de como empezar con TDD y buenas prácticas. Ahora reflexionamos sobre la solución de la kata y veremos que introducimos ciertos code smells que en una segunda iteración intentaremos ir solucionándolos para hacer llegar a un código más robusto.

Enunciado de FizzBuzz

Escribe un programa que imprima los números del 1 al 100, pero aplicando las siguientes normas:
Devuelve Fizz si el número es divisible por 3.
Devuelve Buzz si el número es divisible por 5.-
Devuelve FizzBuzz si el número es divisible por 3 y por 5.

fizzbuzz.png
FizzBuzz in CSS

El código de la primera iteración es este.

Seguir leyendo “Mejorando la kata FizzBuzz”

Refactorizando servicio después del testing

Hace unos días empezamos con la kata TripServiceKata y conseguimos hacer tests unitarios de la clase TripService, pero ¿de verdad vamos a conformarnos con eso? Hemos conseguido tener test utilizando una clase recubrimiento, ahora vamos a refactorizar el proyecto TripService para que no sea necesario utilizar recubrimientos.

Seguir leyendo “Refactorizando servicio después del testing”

Haciendo testing unitario al 100

Haciendo testing unitario al 100%. Hace unos días estuvimos hablando de como refactorizar controladores para separar responsabilidades. Ahora llega la hora de dar un paso más y testear unitariamente las clases que creamos.

La idea detrás de este post es que modifiquemos una clase ya construida y funcional, llenarla de tests y refactorizarla para que los test solo testeemos una clase y no necesiten más que phpunit, ni base de datos ni más clases de las necesarias.

Seguir leyendo “Haciendo testing unitario al 100”

Refactorizando controladores: Separando responsabilidades

Una de las cosas más complicadas para los programadores es tomar consciencia sobre las como modelar situaciones y objetos del mundo real y aplicarlos al mundo de la programación. De vez en cuando, es necesario dar un paso atrás y pensar en lo que se ha estado haciendo.

Así es como al final, acabamos dándonos cuenta de que llenar un controlador de lógica es como crear un monstruo. Últimamente he leído mucho sobre refactoring y más de un post hablando sobre que llenar el controlador de código es algo malo, así que hoy vamos a ver de primera mano como solucionarlo.

Pongamos como ejemplo un controlador como este:

Seguir leyendo “Refactorizando controladores: Separando responsabilidades”

Skinny Controller: moviendo la lógica del controller

Cuando empecé a utilizar PHP, yo incluía bastante cantidad de lógica en el controller, entonces empecé a leer sobre refactoring y me topé con skinny controller fat models, el código de mis controlllers se reducía bastante y la lógica estaba en los modelos, pero lo único que hacía era mover el problema de sitio. Ahora tenía modelos muy grandes. Cuando una action interactúa con más de un modelo, me encontraba en que no sabía en que model escribir esa lógica…

El código en los controller no puede reutilizarse, por lo que en algunos casos como en ‘pedidos/Order’ en los que hay que lidiar con los descuentos, los artículos del la orden, la dirección,… escribir toda lógica de este proceso en un controlador no es la solución ideal, ya que modificar toda esa lógica hará que tengamos código duplicado.

Así que lo mejor es abstraer toda la lógica del proceso en unas clases de servicio. Así estaba el controller antes de crear el servicio:

<?php 
  class OrdersController extends Controller {

    public function actionCreate() {
      try {
        $orderData  = Yii::app()->request->getParam('order');

        if(empty($orderData['items'])) {
          $this->_sendResponse(403, array(
            'status' => 'error', 'message' => 'Can\'t save order without items'
          ));
        }
        $items = $orderData['items'];
        unset($orderData['items']);
        try {
          $order = new Orders;
          $orderTransaction = $order->dbConnection->beginTransaction();
          if($order) {
            $address = Addresses::createIfDidntExist($orderData);
            unset($orderData['address']);
            $orderData['address_id'] = $address->id;
            $amount = 0;
            foreach ($items as $key => $item) {
              $amount += $item['total'];
            }
            $amount += $orderData['extra_charge'];
            $orderData['amount'] = $amount;
            $order->attributes = $orderData;
            if($order->save()) {
              if(OrderItems::batchSave($items, $order->id)) {
                $orderTransaction->commit();
                $this->sendMail($order->id);
                $this->_sendResponse(200, array(
                  'status' => 'success', 'message' => 'Order placed successfully.'
                ));
              }
              $orderTransaction->rollback();
              $this->_sendResponse(403, array(
                'status' => 'error', 'message' => 'Order creation failed'
              ));
            }
            else {
              $orderTransaction->rollback();
              $this->_sendResponse(403, array(
                'status' => 'error', 'errors' => $order->getErrors()
              ));
            }
          }
        }
        catch(Exception $e) {
          $orderTransaction->rollback();
          $this->_sendResponse(403, array(
            'status' => 'error', 'message' => $e->getMessage()
          ));
        }
      }
      catch(Exception $e) {
        $this->_sendResponse(403, array(
          'status' => 'error', 'message' => $e->getMessage()
        ));
      }
    }

  public function sendMail($order_id) {
    // logic to send email after placing an order successfully
  }

?>

Toda la lógica y las excepciones están en el controlador y no es posible volver a utilizarla en otra acción. También tenemos un código muy difícil de probar, así que lo mejor es mudar a una clase OrderService:

<?php

class OrdersService {
  public function create($orderData) {

    if(empty($orderData['items'])) {
      throw new OrdersServiceException('Order items can\'t be empty.');
    }
    $items = $orderData['items'];
    unset($orderData['items']);
    try {
      $order = new Orders;
      $orderTransaction = $order->dbConnection->beginTransaction();

      $address = Addresses::createIfDidntExist($orderData);
      unset($orderData['address']);
      $orderData['address_id'] = $address->id;
      $amount = 0;
      foreach ($items as $key => $item) {
        $amount += $item['total'];
      }
      $amount += $orderData['extra_charge'];
      $orderData['amount'] = $amount;
      $order->attributes = $orderData;
      if($order->save()) {
        if(OrderItems::batchSave($items, $order->id)) {
          $orderTransaction->commit();
          $this->sendMail($order->id);
          return array('status' => 'success');
        }
        $orderTransaction->rollback();
        throw new OrdersServiceException("Failed to save the items.", 1);
      }
      else {
        // handle validation errors
        $orderTransaction->rollback();
        return array('status' => 'error', 'errors' => $order->getErrors());
      }
    }
    catch(Exception $e) {
      $orderTransaction->rollback();
      throw new Exception('Something wrong happened');
    }
  }

  public function sendMail($order_id) {
    // logic to send email after placing an order successfully
  }
}

class OrdersException extends Exception {

}
?>

Ahora se está lanzando una excepción para todos los errores. Así que se puede capturar la excepción en cualquier lugar en el que utilicemos el servicio y mostrar los errores dependiendo de acción (un mensaje de error, un JSON,…)

Una vez que hemos mudado todo a la clase de servicio, tenemos un controlador como este:

<?php 
  class OrdersController extends Controller {
    public function actionCreate() {
      $orderData = Yii::app()->request->getParam('order');
      try {
        $order = new OrdersService();
        $res = $order->create($orderData);
        if(isset($res['status']) && $res['status'] == 'success') {
            $res['message'] = 'Order placed successfully.';
            $this->_sendResponse(200, $res);
        }
        $this->_sendResponse(403, $res);
      }
      catch(OrdersServiceException $e) {
        $this->_sendResponse(403, array(
          'status' => 'error', 'message' => $e->getMessage()
        ));
      }
      catch(Exception $e) {
        $this->_sendResponse(403, array(
          'status' => 'error', 'message' => $e->getMessage()
        ));
      }
    }
  }
?>

Ahora el controller solo tiene el código necesario que necesita esta acción. No tenemos que reutilizar nada de esto, ya que la forma de mostrar el error depende de la action particular.

Referencias

Esta entrada esta basada en el artículo http://blog.revathskumar.com/2015/08/php-service-classes.html

Refactorizando el cliente de “Cat API” – Parte 3 –

Esto es una traducción libre de Refactoring the Cat API client – Part III

En la primera y segunda parte hemos estado trabajando en separar las preocupaciones que teníamos al principio combinadas en una sola función.

Los principales “personajes” en el escenario ya han sido identificados: un httpClient y una caché, utilizadas por diferentes implentaciones de CatApi para poder testar y realizar un cliente para The Cat Api

Seguir leyendo “Refactorizando el cliente de “Cat API” – Parte 3 –”