domingo, 21 de octubre de 2018

Coalesce C#

En programación es bastante común que se compruebe si una variable "tiene valor" y si es así, se utiliza, y en caso contrario se usa un valor predeterminado.

Por ejemplo:

int rolActual;
if (rolUsuario != 0){
    rolActual = rolUsuario;
} else {
    rolActual = ROL_DEFAULT;
}

Podemos utilizar el operador condicional para este tipo de operaciones:

int rolActual = rolUsuario != 0 ? rolUsuario : ROL_DEFAULT;

Supongamos, que si el usuario no tiene rol asignado, comprobamos el rol de su grupo, y si tampoco está asignado, utilizamos un valor por defecto:

int rolActual;

if (rolUsuario != 0){
    rolActual = rolUsuario;
} else if (rolGrupo != 0)
    rolActual = rolGrupo;
} else {
    rolActual = ROL_DEFAULT;
}

Si utilizamos el operador condicional, sería así:

int rolActual = rolUsuario != 0 ? rolUsuario : (rolGrupo != 0 ? rolGrupo : ROL_DEFAULT);

Al añadir una segunda comprobación, es más difícil la lectura de esta expresión.

Como vemos, conforme aumenta el número de variables a comprobar, la legibilidad del código es peor. El operador condicional aquí no es una solución.

El estándar SQL-92 define la función COALESCE(), que admite un número de parámetros variable y devuelve el primer valor no nulo de los parámetros que se le pasan.

Podemos reproducir este comportamiento creando un método que admita un número variable de argumentos. Para ésto, utilizamos la palabra clave "params".

En lugar de valores nulos, vamos a considerar que una variable int está vacía si tiene valor cero. (Podríamos hacerlo con nulos, pero no es el caso que buscamos).

public static int Coalesce(params int[] integers)
{
    for (int i = 0; i < integers.Length; i++)
    {
        if (integers[i] != 0)
        {
            return integers[i];
        }
    }
    return 0;
}

La función va iterando por el array de valores que recibe, y devuelve el primero que no sea cero. Si no encuentra ninguno (todos los valores que recibe son cero),  devolverá cero.

Utilizando este método, la expresión anterior quedaría así:

int rolActual = Coalesce(rolUsuario, rolGrupo, ROL_DEFAULT);

El código es más legible y tiene la ventaja podemos utilizar tantos valores como queramos.

En el ejemplo, el último parámetro será el valor por defecto que queremos que tome rolActual, en el caso de que rolUsuario y rolGrupo "estén vacíos" (tengan valor cero).

Como la función recibe los valores de los parámetros en un array, podemos simplificar la función utilizando el método Enumerable.FirstOrDefault() que pertenece a System.Linq.

public static int Coalesce(params int[] integers)
{
    return integers.FirstOrDefault(i => i != 0);
}

Por supuesto, no tenemos que limitarnos a valores enteros. Podemos utilizar el mismo método para cualquier tipo de variable. Por ejemplo con cadenas de texto:

static string Coalesce(params string[] strings)
{
    return strings.FirstOrDefault(s => !string.IsNullOrEmpty(s)) ?? string.Empty;
}

Si todos los valores que recibe son null o cadenas vacías, el método FirstOrDefault() devolvería null, que es el valor por defecto del tipo string.

En este método, usamos el operador ?? para que el método, devuelva una cadena vacía.

Un ejemplo:
string texto1 = null;
string texto2 = string.Empty;
string texto3 = "Contenido";
string texto4 = null;

string resultado = Colaesce(texto1, texto2, texto3, texto4);

A la variable resultado se le asignará el valor de texto3  ("Contenido") que es la primera cadena de texto no vacía ni nula.

viernes, 9 de junio de 2017

Encriptación Rijndael con PHP y .NET

Según la Wikipedia, "AES es uno de los algoritmos más populares usados en criptografía simétrica".

Trabajando en un proyecto PHP, tuve que acceder a un web service desarrollado en .NET que utiliza AES para encriptar la petición y la respuesta.

Como sólo tenía un texto original y el encriptado, comencé por hacer un proyecto en .NET para verificar el funcionamiento.
Hasta ahí todo bien.

Para la implementación en PHP, como no tenía ni idea de cómo hacerlo, lo primero que hice fue pasarme un par de días rebuscando en Internet, probando distintas soluciones y, al final, utilizando un trozo de aquí y otro de allá, dí con la solución.
Conseguí que la encriptación / desencriptación entre PHP y .NET cuadraran.

El principal problema fue el modo de relleno (el webservice utiliza PKCS #7) que se utiliza para rellenar totalmente los bloques.

De hecho, la función que utilizo para el relleno de los bloques, es una copia de una función que encontré en Stack Overflow. Los únicos cambios que le hice son para utilizarla adaptarla a una clase y poco más.

Todo iba bien hasta que actualicé a PHP 7.1. Las funciones mcrypt_xxx han desaparecido. Se declararon obsoletas en PHP 5.5 y en PHP 7.1 se han eliminado.

Si hubiera leído tranquilamente la documentación, en lugar de ir directamente a la descripción de parámetros y ejemplos... me hubiera ahorrado problemas. Me tocó volver a rehacerlo todo utilizando OpenSSL.

No fue tan sencillo como cambiar unas funciones por otras.

Como Rijndael no es exactamente igual que AES, casi todo lo que encontré eran consultas de gente pidiendo ayuda porque no conseguían cuadrar el cifrado de PHP con .NET
Hay cientos de páginas con el mismo problema con este algoritmo. Muchas implementaciones funcionan correctamente, pero casi todo lo que encontré está basado en Mcrypt.

Al final, con la documentación de PHP en la mano, en lugar de intentar que todo funcionara de una vez, fui partiendo el problema en trocitos y conseguí echarlo a andar.
Una vez que todo funcionaba, lo metí todo en una clase, revisé todo el código, y se quedó en menos de la mitad de líneas.

He publicado en GitHub un par de clases, en C# y PHP, por si pudieran servirte para algo.

Las dos clases solo tienen un par de métodos para encriptar / desencriptar:

La clase de C# sólo tiene dos métodos estáticos:
string original = "This is a text to encrypt!";
string password = "ThisIsMyPassword";

string encriptado = SimpleRijndael.Encrypt(original, password);
string desencriptado = SimpleRijndael.Decrypt(encriptado, password);

La clase de PHP también utiliza solamente un par de métodos:
include 'RijndaelOpenSSL.php';

$original = 'This is a text to encrypt!';
$pass ='ThisIsMyPassword';

$rijndael = new RijndaelOpenSSL();
$encriptado = $rijndael->encrypt($original, $pass);
$desencriptado = $rijndael->decrypt($encriptado, $pass);

No soy experto en seguridad y algoritmos de cifrado; no sabría decirte si tiene algún fallo de seguridad.
A mí me ha servido para solucionar el enlace con el web service.

viernes, 19 de mayo de 2017

Conversión de números a texto

Hace bastante tiempo necesité un conversor para representar un importe en euros en texto; principalmente para imprimir importes de cheques, recibos, etc.

Como no encontré ninguno que se adaptara a lo que necesitaba, partiendo de uno que tenía hecho en C, lo hice en C#.

Luego decidí "traducirlo" a PHP. Partiendo de .NET es relativamente sencillo, ya que básicamente se trata de analizar un número y traducirlo.

Tienes las dos versiones disponibles en GitHub y en PHP Classes.

Se trata de una sola clase con un método estático que realiza la conversión.

Su uso es muy sencillo:

C#

string txt1 = Alp3476.A2Num2Txt.ToString("3528.25");
Console.WriteLine(txt1);
// tres mil quinientos veintiocho con veinticinco

string txt2 = Alp3476.A2Num2Txt.ToString(123456);
Console.WriteLine(txt2);
// ciento veintitres mil cuatrocientos cincuenta y seis

En la versión de C#, el método está sobrecargado, por lo que admite como parámetro un double, o un string.

PHP

include 'Num2Txt.php';

$o = new  \Alp3476\Num2Txt();
echo $o->toString(3527.25) . PHP_EOL;
// tres mil quinientos veintisiete con veinticinco

echo $o->toString(12342245281.890) . PHP_EOL;
// doce mil trescientos cuarenta y dos millones doscientos cuarenta y cinco mil ...
// ... doscientos ochenta y uno con ochenta y nueve

En PHP, el parámetro se convierte a float utilizando floatval(), por lo que admite cualquier parámetro que se pueda convertir a un número de punto flotante.

Los números se redondean a dos dígitos.
Como he comentado, su uso inicial es para imprimir importes en euros (de ahí que se utlicen dos decimales)


Por supuesto que hay muchas clases similares y seguramente más optimizadas, pero si crees que te pueden servir, ahí las tienes.

Son muy sencillas y las podrás adaptar a tus necesidades.



lunes, 15 de mayo de 2017

Cálculos con fechas

Todos los lenguajes de programación (o casi todos) disponen de un tipo de datos para representar fechas y/o  funciones para trabajar con ellas.
Sin embargo, no es raro que nos encontremos con código, (a veces antiguo, a veces no) que trata las fechas "a mano".

Este es un un ejemplo real de una función que ví en una aplicación PHP:

function sumaMes($fecha,$mesesSumar,$formatoDMA=false)
{
  $dia = $this->separaFecha($fecha,"D");
  $mes = $this->separaFecha($fecha,"M");
  $ano = $this->separaFecha($fecha,"A");
  $mesResult = $mes;

  if (!empty($mes))
  {
    for ($n = $mes; $n<($mes + $mesesSumar); $n++)
    {
      if ($n == 12)
      {
        $mesResult = 1;
        $ano++;
      }
      else
        $mesResult++;
    }
  }

  $mes = $this->cerosLeft($mesResult, 2);

  if ($formatoDMA)
    $resp = "$dia-$mes-$ano";
  else
    $resp = "$ano-$mes-$dia";

  return $resp;
}

Esta función está tal y como la encontré. En apariencia, el código parece correcto, y funciona bien hasta que hacemos operaciones como sumar un mes a un 30 de enero, lo que nos devuelve un 30 de febrero.
Este tipo de errores se detectan cuando utilizamos una base de datos y protesta cuando intentamos grabar una fecha no válida.
Si estamos creando un fichero tipo XML o similar, tenemos un problema; tarde o temprano lo descubriremos, o más bien, lo descubrirán los clientes por nosotros.

Esto de despotricar del código de otros es fácil; hay gente a la que le parece divertido.
Sin embargo, cuando heredas un código como éste y te toca mantenerlo, o tienes que hacer un traspaso de datos que ha pasado por este código, créeme, no es divertido.

La función sumaMes() lá encontré en una aplicación que utilizaba una versión antigua de MySQL que, sorprendentemente, se tragaba fechas como el 31 de abril. El traspaso de esos datos a PostgreSQL fue toda una aventura.

El tratar las fechas como texto, descomponerlas y hacer operaciones, no es lo más adecuado.
Una cosa es analizar y descomponer un texto que representa una fecha, y otra hacer operaciones con la fecha.

Vamos a ver un par de ejemplos de tratamiento de fechas:
  • Añadir / restar intervalos
  • Cálculo del último día del mes

Añadir / Restar intervalos

Vamos a ver varios ejemplos de cómo añadir 30 días, y restar un año en fechas en PHP, C#, JavaScript y PostgreSQL:

PHP

/* Sumar 30 días */
$f = new DateTime();
$f->add(new DateInterval('P30D'));
echo $f->format('d-M-Y');

/* Restar 1 año */
$f = new DateTime();
$f->sub(new DateInterval('P1Y'));
echo $f->format('d-M-Y'); 

La clase DateInterval es la que nos permite definir el intervalo con el que operamos sobre la fecha actual. Hay que tener en cuenta que se modifica el objeto original, lo que puede ser una ventaja o un inconveniente.

C#

/* Sumar 30 días */
var f= DateTime.Today;
var f2 = f.AddDays(30);
Console.WriteLine($"Original {f}  Nueva: {f2}");

/* Restar 1 año */
var f= DateTime.Today;
var f2 =f.AddYears(-1);
Console.WriteLine($"Original {f}  Nueva: {f2}");

En C#, la clase Datetime nos proporciona métodos para operar con la fecha.
A diferencia de PHP, estos métodos devuelven un nuevo objeto de tipo DateTime, manteniendo el objeto original intacto.

JavaScript

/* Sumar 30 días */
var f= new Date();
f.setDate(f.getDate() + 30);
console.log(f);

/* Restar 1 año */
var f= new Date();
f.setFullYear(f.getFullYear() - 1);
console.log(f);

En JavaScript lo que hacemos, es modificar las propiedades del objeto Date().

PostgreSQL

-- Sumar 30 días
SELECT current_date + 30
SELECT cast(current_date + INTERVAL '1 day' AS date)

-- Restar 1 año
SELECT cast(current_date - INTERVAL '1 year' AS date)

En PostgreSQL, podemos sumar o restar días directamente con un entero. También podemos utilizar INTERVAL
Al utilizar INTERVAL, nos devuelve un timestamp. Si lo que queremos es un tipo 'date', hacemos el casting.

Último día del mes

Esta es una operación muy frecuente.
Seguramente que habrás visto código en el que tenemos un array de 12 elementos que contiene los días de los meses; luego selecciona el elemento correspondiente al mes, y si es febrero, se comprueba si el año es bisiesto y devuelve 29 en lugar de 28.

Dependiendo del lenguaje, hay formas más o menos sencillas de calcular el último día del mes.

PHP

echo date('t'); // último día del mes actual

/* Ultimo día del mes de una fecha concreta */
$fecha = new DateTime('2016-02-10');
echo  $fecha->format('t'); // 29
echo $día;

En PHP es muy sencillo,podemos pasar el parámetro 't' la la función date() (utiliza la fecha del sistema), o bien, utilizar el método format() en un objeto DateTime.

C#

class Fechas {
 public static DateTime UltimoDiaMes(DateTime fechaOrigen)
 {
   DateTime fecha = new DateTime(fechaOrigen.Year, fechaOrigen.Month, 1);
   return fecha.AddMonths(1).AddDays(-1);
 }
}

En C#, lo más sencillo es crear un objeto DateTime con el primer día del mes; Luego, sumamos un mes (la fecha se "posiciona" en el día 1 del mes siguiente) y restamos un día.
Esta función de ejemplo recibe como parámetro un objeto DateTime del que partimos para hacer el cálculo.
Si queremos calcular el último día del mes actual llamamos a esta función así:

DateTime fechaUDM = Fechas.UltimoDiaMes(new DateTime());
La variable fechaUDM es un objeto DateTime que almacena al último día del mes actual.

Si sólo queremos el día, lo extraemos del objeto DateTime.
int día = Fechas.UltimoDiaMes(new DateTime()).Day;

JavaScript

var fecha = new Date();
fecha.setMonth(fecha.getMonth() + 1);
fecha.setDate(0);

En JavaScript, el método es similar a C#:
Primero, incrementamos el mes, y luego llamamos a setDate() con el valor 0; esto nos sitúa en el último día del mes anterior. Si el valor es -1 pasaríamos al penúltimo día del mes anterior, y así.

PostgreSQL

SELECT cast(date_trunc('month', current_date) + interval '1 month - 1 day' AS date)

En PostgreSQL, la función date_trunc() con el parámetro 'month' devuelve una fecha correspondiente al primer día del mes. En realidad, lo que hace es recortar la fecha al nivel / precisión que indicamos. En este caso, recorta a nivel de mes, con lo que mantiene el año y el mes del segundo parámetro. Este cálculo lo encontré aquí: PostgreSQL: Date LastDay

Una aclaración: estos ejemplos de código no me los he inventado yo, los podréis encontrar en foros o en la documentación de los distintos lenguajes, más o menos con la misma apariencia.

Coalesce C#

En programación es bastante común que se compruebe si una variable "tiene valor" y si es así, se utiliza, y en caso contrario se u...