Skip to content

14. Ejercicio de aplicación – version 6

Image

Acabamos de implementar la siguiente estructura en capas:

Image

El SGBD utilizado en los ejemplos era MySQL. En el apartado «enlace» habíamos observado que nada en la clase que implementa la capa [dao] hacía suponer que se estuviera utilizando un SGBD concreto. Esto es lo que vamos a comprobar ahora utilizando otro SGBD, el SGBD PostgreSQL. La arquitectura por capas queda así:

Image

14.1. Instalación del SGBD PostgreSQL

Las distribuciones de SGBD y PostgreSQL están disponibles en URL y [https://www.postgresql.org/download/] (mayo de 2019). A continuación mostramos la instalación de version para Windows de 64 bits:

Image

Image

  • en [1-4], se descarga el instalador de SGBD;

Ejecutamos el instalador descargado:

Image

  • en [6], indique una carpeta de instalación;

Image

  • en [8], el option [Stack Builder] no es necesario para lo que queremos hacer aquí;
  • en [10], deje el valor que se le mostrará;

Image

  • En [12-13], hemos introducido aquí la contraseña [root]. Esta será la contraseña del administrador de SGBD, que se llama [postgres]. PostgreSQL también lo llama superusuario;
  • en [15], deje el valor por defecto: es el puerto de escucha de SGBD;

Image

  • en [17], deje el valor por defecto;
  • en [19], el resumen de la configuración de la instalación;

Image

Image

En Windows, SGBD PostgreSQL se instala como un servicio de Windows que se inicia automáticamente. En la mayoría de los casos, esto no es deseable. Vamos a modificar esta configuración. Escriba [services] en la barra de búsqueda de Windows [24-26]:

Image

  • en [29], vemos que el servicio de SGBD PostgreSQL está en modo automático. Cambiamos esto accediendo a las propiedades del servicio [30]:

Image

  • en [31-32], configure el inicio en modo manual;
  • en [33], detenga el servicio;

Cuando desee iniciar manualmente el SGBD, vuelva a la aplicación [services], haga clic con el botón derecho del ratón en el servicio [postgresql] (34) e inícielo (35).

14.2. Activación de la extensión PDO de SGBD PostgreSQL

Vamos a modificar el archivo [php.ini] que configura PHP (véase el párrafo del enlace):

Image

  • en [2], compruebe que la extensión PDO de PostgreSQL está activada. Una vez hecho esto, guarde el cambio y reinicie Laragon para asegurarse de que el cambio se aplique. A continuación, compruebe la configuración de PHP directamente desde Laragon [3-5].

14.3. Administre PostgreSQL con la herramienta [pgAdmin]

Inicie el servicio de Windows de SGBD PostgreSQL (véase el párrafo del enlace). A continuación, del mismo modo que inició la herramienta [services], inicie la herramienta [pgadmin], que permite administrar SGBD, PostgreSQL y [1-3]:

Image

Es posible que en algún momento se le solicite la contraseña del superusuario. Esta se llama [postgres]. Usted definió su contraseña durante la instalación de SGBD. En este documento, hemos asignado la contraseña [root] al superusuario durante la instalación.

  • en [4], [pgAdmin] es una aplicación web;
  • en [5], la lista de servidores PostgreSQL detectados por [pgAdmin], aquí 1;
  • en [6], el servidor PostgreSQL que hemos iniciado;
  • en [7], las bases de datos de SGBD, aquí 1;
  • en [8], la base [postgresql] es gestionada por el superusuario [postgres];

En primer lugar, creemos un usuario [admimpots] con la contraseña [mdpimpots]:

Image

Image

  • en [17], hemos puesto [mdpimpots];

Image

  • en [21], el código SQL que emitirá la herramienta [pgAdmin] hacia el SGBD PostgreSQL. Es una forma de aprender el lenguaje SQL, propiedad de PostgreSQL;
  • en [22], tras la validación del asistente [Save], se ha creado el usuario [admimpots];

Ahora creamos la base [dbimpots-2019]:

Image

Hacemos clic con el botón derecho en [23] y, a continuación, en [24-25] para crear una nueva base de datos. En la pestaña [26], definimos el nombre de la base de datos [27] y su propietario [admimpots] [28].

Image

  • en [30], el código SQL de creación de la base;
  • en [31], tras la validación del asistente [Save], se crea la base [dbimpots-2019];

Ahora vamos a crear la tabla [tbtranches] con las columnas [id, limites, coeffr, coeffn]. Una particularidad de PostgreSQL es que los nombres de las columnas distinguen entre mayúsculas y minúsculas, lo que no suele ser el caso con los demás SGBD. Así, con MySQL, la orden [select limites, coeffR, coeffN from tbtranches] funcionará aunque las columnas reales de la tabla [tbtranches] sean [LIMITES, COEFFR, COEFFN]. Con PostgreSQL, la orden SQL no funcionará. Entonces se podría escribir [select LIMITES, COEFFR, COEFFN from tbtranches], pero seguirá sin funcionar, ya que PostgreSQL ejecutará la orden [select limites, coeffr, coeffn from tbtranches]: por defecto, pasa los nombres de las columnas en minúsculas. Para que no lo haga, hay que escribir: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], es decir, hay que proteger los nombres de las columnas con comillas. Por estas razones, vamos a dar a las columnas nombres en minúsculas. Los nombres de los objetos de una base de datos pueden ser una fuente de incompatibilidad entre SGBD, ya que algunos nombres son palabras reservadas en ciertos SGBD y no en otros.

Creamos la tabla [tbtranches]:

Image

  • utilice el botón [40] para crear columnas;

Image

Image

  • una vez finalizado el asistente de creación mediante [Save], se crea la tabla [tbtranches] [52-53];

Debemos indicar a SGBD que debe generar por sí mismo la clave primaria [id] al insertar una fila en la tabla:

Image

  • en [56] se accede a las propiedades de la clave primaria [id];
  • en [59], se indica que la columna es de tipo [Identity]. Esto hará que SGBD genere los valores de la clave primaria;

Image

  • en [62], el código SQL generado para esta operación;

La tabla [tbtranches] ya está lista.

Repetimos los mismos pasos para crear la tabla [tbconstantes]. A continuación, se muestra el resultado que se debe obtener:

Image

Image

Image

La base [dbimpots-2019] ya está lista. Vamos a rellenarla con datos.

Al igual que hicimos con MySQL, es posible exportar la base de datos [dbimpots-2019] a un archivo SQL. A continuación, podemos importar este archivo SQL para recrear la base de datos si la hemos perdido o se ha dañado. Aquí solo exportaremos la estructura de la base de datos y no sus datos:

Image

Image

El archivo generado es el siguiente:


--
-- volcado de la base de datos PostgreSQL
--

-- Volcado desde la base de datos version 11.2
-- Volcado por pg_dump version 11.2

-- Iniciado el 04/07/2019 a las 08:20:31

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_with_oids = false;

--
-- TOC entrada 198 (clase 1259 OID 16408)
-- Name: tbconstantes; Tipo: TABLE; Esquema: public; Propietario: postgres
--

CREATE TABLE public.tbconstantes (
    plafond_qf_demi_part double precision NOT NULL,
    id integer NOT NULL,
    plafond_revenus_celibataire_pour_reduction double precision NOT NULL,
    plafond_revenus_couple_pour_reduction double precision NOT NULL,
    valeur_reduc_demi_part double precision NOT NULL,
    plafond_decote_celibataire double precision NOT NULL,
    plafond_decote_couple double precision NOT NULL,
    plafond_impot_celibataire_pour_decote double precision NOT NULL,
    plafond_impot_couple_pour_decote double precision NOT NULL,
    abattement_dix_pourcent_max double precision NOT NULL,
    abattement_dix_pourcent_min double precision NOT NULL
);


ALTER TABLE public.tbconstantes OWNER TO postgres;

--
-- TOC entrada 199 (clase 1259 OID 16411)
-- Name: tbconstantes_id_seq; Tipo: SEQUENCE; Esquema: público; Propietario: postgres
--

ALTER TABLE public.tbconstantes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.tbconstantes_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- TOC entrada 196 (clase 1259 OID 16399)
-- Name: tbtranches; Tipo: TABLE; Esquema: público; Propietario: admimpots
--

CREATE TABLE public.tbtranches (
    limites double precision NOT NULL,
    id integer NOT NULL,
    coeffr double precision NOT NULL,
    coeffn double precision NOT NULL
);


ALTER TABLE public.tbtranches OWNER TO admimpots;

--
-- TOC entrada 197 (clase 1259 OID 16404)
-- Name: tbimpots_id_seq; Tipo: SEQUENCE; Esquema: público; Propietario: admimpots
--

ALTER TABLE public.tbtranches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
    SEQUENCE NAME public.tbimpots_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);


--
-- TOC entrada 2694 (clase 2606 OID 16429)
-- Name: tbconstantes tbconstantes_pkey; Tipo: CONSTRAINT; Esquema: public; Propietario: postgres
--

ALTER TABLE ONLY public.tbconstantes
    ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);


--
-- TOC entrada 2692 (clase 2606 OID 16403)
-- Name: tbtranches tbimpots_pkey; Tipo: CONSTRAINT; Esquema: public; Propietario: admimpots
--

ALTER TABLE ONLY public.tbtranches
    ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);


--
-- TOC entrada 2821 (clase 0 OID 0)
-- Dependencias: 198
-- Name: TABLE tbconstantes; Tipo: ACL; Esquema: public; Propietario: postgres
--

GRANT ALL ON TABLE public.tbconstantes TO admimpots;


-- Finalizado el 04/07/2019 a las 08:20:32

--
-- Volcado de la base de datos PostgreSQL completado
--

14.4. Relleno de la tabla [tbtranches]

Ya hemos realizado este trabajo con los archivos SGBD y MySQL en el apartado del enlace. Solo tenemos que modificar el archivo [database.json] que describe la base de datos:

Image

El archivo [database.json] queda así:


{
    "dsn": "pgsql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "public.tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "public.tbconstantes",
    "colPlafondQfDemiPart": "plafond_qf_demi_part",
    "colPlafondRevenusCelibatairePourReduction": "plafond_revenus_celibataire_pour_reduction",
    "colPlafondRevenusCouplePourReduction": "plafond_revenus_couple_pour_reduction",
    "colValeurReducDemiPart": "valeur_reduc_demi_part",
    "colPlafondDecoteCelibataire": "plafond_decote_celibataire",
    "colPlafondDecoteCouple": "plafond_decote_couple",
    "colPlafondImpotCelibatairePourDecote": "plafond_impot_celibataire_pour_decote",
    "colPlafondImpotCouplePourDecote": "plafond_impot_couple_pour_decote",
    "colAbattementDixPourcentMax": "abattement_dix_pourcent_max",
    "colAbattementDixPourcentMin": "abattement_dix_pourcent_min"
}
  • línea 2: el DSN ha cambiado, [pgsql] indica que se trata del SGBD Postgres;
  • líneas 5 y 9: se ha antepuesto al nombre de las tablas el nombre del esquema al que pertenecen, [public]. Esto no era imprescindible, ya que [public] es el esquema utilizado por defecto cuando no se especifica ningún esquema en el nombre de la tabla;
  • líneas 6-8, 10-19: se han cambiado los nombres de las columnas;

El script [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] para rellenar la base de datos [dbimpots-2019] es el siguiente:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores por PHP
// ini_set("display_errors", "0");
// Inclusión de interfaz y clases
require_once __DIR__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../../version-05/Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// definición de constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // creación de la capa [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // transferencia de datos a la base de datos
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;

Comentarios

Solo cambian las líneas 12-21, que cargan los archivos necesarios para la ejecución de la aplicación. Cambian porque cambia el valor [__DIR__]: ahora hace referencia a la carpeta [version-07/Main].

Al ejecutar este script, se obtiene el siguiente resultado en la tabla [tbtranches]:

Image

  • hacemos clic con el botón derecho en [1] y, a continuación, en [2-3];
  • en [4], se observan correctamente los datos de los tramos impositivos;

Repetimos el mismo proceso para la tabla de constantes [tbconstantes]:

Image

Image

Image

Cabe señalar que, para la ejecución del script, no es necesario que la aplicación Laragon esté activa: no se necesita ni el servidor Apache, ni el SGBD MySQL. Solo se necesita el SGBD PostgreSQL, cuyo servicio de Windows se ha iniciado.

14.5. Cálculo del impuesto

Image

Las capas [dao] (3) y [métier] (2) ya se han escrito. Ya hemos escrito el script principal para SGBD MySQL en el apartado enlace. Solo tenemos que retomar el script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] y adaptarlo a SGBD y PostgreSQL. Ahora se llama [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Image

El script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] es el siguiente:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores por PHP
//ini_set("display_errors", "0");
// Inclusión de la interfaz y las clases
require_once __DIR__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../../version-05/Métier/InterfaceMetier.php";
require_once __DIR__ . "/../../version-05/Métier/Metier.php";
//
// definición de constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";

try {
  // creación de la capa [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // creación de la capa [métier]
  $métier = new Metier($dao);
  // cálculo de impuestos en modo por lotes
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;

Comentarios

Solo cambian las líneas 12-22, que cargan los archivos necesarios para la ejecución de la aplicación. Cambian porque cambia el valor [__DIR__]: ahora apunta a la carpeta [version-07/Main].

Resultados de la ejecución

Los mismos que los obtenidos en versiones anteriores.

14.6. Pruebas [Codeception]

Al igual que en las versiones anteriores, validamos este version con las pruebas [Codeception]:

Image

14.6.1. Prueba de la capa [dao]

La prueba [DaoTest.php] es la siguiente:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// directorios raíz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// Inclusión de interfaces y clases
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";

// bibliotecas de terceros
require_once VENDOR . "/autoload.php";

// definición de constantes
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // pruebas
  public function testTaxAdminData() {

  }

}

Comentarios

  • líneas 9-28: definición del entorno de la prueba. Utilizamos el mismo, sin la capa [métier], que el utilizado por el script principal [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descrito en el párrafo enlace;
  • líneas 34-39: construcción de la capa [dao];
  • línea 38: el atributo [$this→taxAdminData] contiene los datos que se van a probar;
  • líneas 42-44: el método [testTaxAdminData] es el descrito en el apartado enlace;

Los resultados de la prueba son los siguientes:

Image

14.6.2. Prueba de la capa [métier]

La prueba [MetierTest.php] es la siguiente:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// directorios raíz
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// Inclusión de interfaces y clases
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/../version-05/Métier/InterfaceMetier.php";
require_once ROOT . "/../version-05/Métier/Metier.php";
// bibliotecas de terceros
require_once VENDOR . "/autoload.php";
// definición de constantes
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";

class MetierTest extends \Codeception\Test\Unit {
  // capa de negocio
  private $métier;

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // creación de la capa [métier]
    $this->métier = new Metier($dao);
  }

  // pruebas
  public function test1() {

  }
--------------------------------------------------------------------
  public function test11() {

  }

}

Comentarios

  • líneas 9-28: definición del entorno de la prueba. Utilizamos el mismo que el utilizado por el script principal [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase] descrito en el párrafo enlace;
  • líneas 34-40: construcción de las capas [dao] y [métier];
  • línea 39: el atributo [$this→métier] hace referencia a la capa [métier]
  • líneas 43-49: los métodos [test1, test2…, test11] son los descritos en el apartado enlace;

Los resultados de la prueba son los siguientes:

Image