PHP

PHP es el lenguaje de programación de mayor aceptación entre las aplicaciones web. La gran mayoría de los CMS orientados al mundo de la web están desarrollados sobre PHP.

Preparado para correr sobre un servidor web, aunque en mucha menor medida también se puede utilizar en entorno de terminal. Tiene una curva de aprendizaje muy baja, es muy permisivo con los errores, es interpretado, no compilado y orientado a objetos, entre otras cosas.

Aunque sea un lenguaje muy aceptado por los principiantes, es muy atacado por muchos experimientados en la programación. No obstante es uno de mis lenguajes preferidos. Y sobre todo, al desarrollar para la web, siendo prácticos, es una de las primeras y mejores soluciones balanceando entre el conjunto de lenguajes disponibles.

Magento 1: obteniendo el margen de beneficio de las ventas

2018-05-10 - Categorías: Magento / PHP
Magento margen de beneficios

Estos últimos días me han pedido sacar informes de un Magento. Ha sido muy interesante porque no me esperaba tener esta información en el CMS, pero sí, ahí estaba.

Resulta que Magento viene con un atributo de coste de los productos, que es de sistema. Dicho atributo de producto tiene el código cost. Además, en la información que se guarda en cada pedido tenemos el precio original, este coste del sistema en el momento de la venta, y más información. Con esto podremos saber el margen de beneficio que hubo en el momento de la venta.

Es importante notar que supongo que ya usas el atributo de coste del sistema, o que tienes un ERP enganchado a Magento que te lo está manteniendo actualizado con cada pedido de compra.

Continuar leyendo..

Symfony: creando landings con formularios, segunda parte

2018-05-08 - Categorías: PHP / Symfony
Landing pages con Symfony

En el post anterior escribí sobre cómo crear una landing page rápido con Symfony 4, y se me quedó pendiente el enviar por email los mensajes del formulario. Es decir, ya tenemos en un rato una rudimentaria ‘landing page’ donde un maquetador o el equipo de marketing disfrutará poniendo cualquier cosa. Será como maquetar con HTML, CSS y Javascript sin tener ningún engorroso CMS por debajo limitándonos lo que podemos hacer. Además tendremos la base de un buen Symfony para después añadir lo que queramos.

Ya tenemos la captación de contactos (o capture leads para los entendidos), que se guardan en una base de datos embebida en la propia web. Sin complicadas instalaciones ni configuraciones SQL, directamente con una base de datos SQLite embebida.

Sólo queda entonces hacer 3 cosas:

  • Instalar el módulo de Swiftmailer.
  • Configurarlo.
  • Y poner la acción de enviar el email.

Vamos al grano..

1. Instalando Swiftmailer

Para esto basta con poner en el terminal lo siguiente:

composer require symfony/swiftmailer-bundle

..esperamos, y si todo ha ido bien tenemos que ver en el terminal algo como lo siguente:

Using version ^3.2 for symfony/swiftmailer-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
 - Installing egulias/email-validator (2.1.4): Loading from cache
 - Installing swiftmailer/swiftmailer (v6.0.2): Loading from cache
 - Installing symfony/swiftmailer-bundle (v3.2.2): Loading from cache
Writing lock file
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
Symfony operations: 1 recipe (8c3c4103f30ebcc7579093a6f39dc028)
 - Configuring symfony/swiftmailer-bundle (>=2.5): From github.com/symfony/recipes:master
Executing script cache:clear [OK]
Executing script assets:install --symlink --relative public [OK]

Some files may have been created or updated to configure your new packages.
Please review, edit and commit them: these files are yours.

2. Configurar swiftmailer

Ahora las principales variables de configuración son variables de entorno. Esto es mucho más seguro ya que se pueden incluso no guardar en ningún fichero, pero la forma más sencilla es usar el fichero .env que debemos tener en el directorio raiz del proyecto. Si lo abrimos veremos que tenemos añadida la configuración de Swiftmailer:

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###

Como bien explica en los comentarios, así hacemos. Pero para evitarte problemas te recomiendo usar siempre SMTP con un buen servidor de correo electrónico. Por Internet hay muchos ejemplos, uno puede ser este:

MAILER_URL=smtp://tudominio.com:587?encryption=tls&auth_mode=login&username=tuusuario&password=tucontraseña

Estas configuraciones usando SMTP son compatibles con la mayoría de los servidores de correo electrónico, sino todos. Tales como Mandrill, Amazon SES, Gmail, etcétera.. podrás configurarlos así, sólo tienes que encontrar las configuraciones que le pondrías a tu Outlook o Thunderbird y ponerlas en el proyecto de Symfony.

3. Enviar el email

Remitiéndome al post anterior, sólo nos queda en el controlador donde recibimos el contenido del formulario el enviarnos un email informándonos de que alguien nos ha dejado un mensaje. Para esto ponemos algo como esto en el controlador, marco en negrita lo nuevo:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use App\Form\ContactType;
use App\Entity\Contact;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index(Request $request, \Swift_Mailer $mailer)
    {
        $contact = new Contact();
        $theForm = $this->createForm(ContactType::class, $contact);

        $theForm->handleRequest($request);

        if ($theForm->isSubmitted() && $theForm->isValid()) {
            // save in database
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($contact);
            $entityManager->flush();

            // send email
            $message = (new \Swift_Message('JnjSite.com:: mensaje desde la web'))
                ->setFrom(['info@jnjsite.com' => 'Web JnjSite.com'])
                ->setTo([
                    'info@jnjsite.com', ])
                ->setBody(
                    $this->renderView(
                        'main/email.html.twig',
                        array('data' => $theForm->getData())
                ),
                'text/html'
            );
            if ($mailer->send($message)) {
                // Email enviado correctamente, vamos a limpiar el formulario.
                $theForm = $this->createForm(ContactType::class);
            } else {
                // Aquí podemos hacer algo más porque no se han enviado bien el email.
            }
        }

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

Verás que no hay que complicarse mucho. Y por último necesitamos también la plantilla del correo electrónico, que la he puesto en templates/main/email.html.twig:

¡Hola!<br>
Hemos recibido un mensaje en el formulario.<br>
<br>
Nombre: <strong>{{ data.name }}</strong><br>
Email: <strong>{{ data.email }}</strong><br>
Asunto: <strong>{{ data.subject }}</strong><br>
Mensaje:<br>
<strong>{{ data.message }}</strong><br>

Terminando

Si todo ha ido bien, ahora podemos probar el formulario arrancando el proyecto en local, si ponemos esto en el terminal:

php bin/console server:start

Y visitamos http://localhost:8000/ nos enviamos un formulario y debemos de ver algo como esto:

Symfony Tutorial 2 enviando mail formulario

..sino me he explicado bien, o quieres decir algo, no dudes en dejar un comentario aquí abajo 😉


Symfony: creando landings con formularios

2018-04-21 - Categorías: PHP / Symfony
Landing pages con Symfony

Traigo hoy un HOWTO para hacer landing pages sobre Symfony 4 orientadas a captación de clientes. Un code-kata con el que en menos de 1 hora tendremos la estructura básica funcional sobre Symfony 4 para recibir contactos, guardarlos en base de datos y enviárnoslos por email.

Llevo un par de semanas bien ajetreadas: migrando un proyecto desde Symfony 3 al 4, otro desde un Joomla en mal estado a Symfony 4, otro proyecto terminándolo de poner en marcha con Symfony 4. Pequeños proyectos de poco tiempo que me han dado muchas ideas para escribir. Así que, cargado de ideas, sigo poco a poco redescubriendo Symfony con Symfony 4. Y me sigo sintiendo como un niño con juguete nuevo 😀

Tengo que decir que Symfony 4 ha mejorado mucho en simplicidad. No hay que saberse cómo funciona todo el conjunto con los 50-60 componentes, ni gran parte de ellos, para comenzar a hacer aplicaciones web robustas: https://symfony.com/components Ni tampoco hay que hacer complicados tutoriales muy largos antes de comenzar a construir con Symfony. Eso sí, tenemos que tener claro cómo funciona la línea de comandos con PHP, y tener muy claro cómo funciona Composer, esto ya lo expliqué en el primer tutorial sobre Symfony 4. Si todavía no te manejas bien con Composer y la línea de comandos.. ¡será mejor que no sigas!

Vayamos al grano..

Comenzando

Pasando de la teoría, y partiendo de que ya hamos visto los posts anteriores, vamos a seguir los siguientes pasos:

  1. Preparar los componentes que necesitamos.
  2. Preparar la base de datos que va a almacenar los contactos.
  3. Pintar el formulario en la página de aterrizaje, o landing page para los amigos.
  4. Recibir el formulario.
  5. Guardar en base de datos.
  6. Y enviarlo a nuestro email.

Comencemos..

1. Preparando los componentes

Creamos el esqueleto del proyecto, entramos al directorio y ponemos los componentes que vamos a usar:

composer create-project symfony/skeleton symfony-tutorial-2
cd symfony-tutorial-2
composer require twig
composer require form
composer require security
composer require doctrine
composer require maker
composer require server

2. Preparando la base de datos

Para simplificar el proceso, y no tener que instalarnos localmente un motor de base de datos completo propongo usar SQLite. SQLite es un tipo de base de datos embebida en la aplicación. Esto quiere decir que no necesita de instalación, simplemente la aplicación creará un fichero y manejará los datos sin ningún otro programa intermediario.

Primero vamos a crear la entidad Contact, que va a ser un objeto de PHP que va a almacenar los datos que un visitante nos deja en el formulario. A su vez, esta entidad se reutiliza para guardar automáticamente en la base de datos cada registro en una tabla que se creará automáticamente a partir de la entidad Contact. También se reutilizará para crear automáticamente el formulario. Esto de que Symfony genera automáticamente los códigos fuentes es brillante. Ejecutemos:

php bin/console make:entity

Y seguimos las instrucciones, vamos a crear los campos clásicos: nombre, email, asunto y mensaje.

Symfony Tutorial 2 landings Entity

Si te equivocas creando los campos no te preocupes, borra los ficheros y vuelve a empezar. Es la única forma de hacerlo y de aprender. Además, este comando make:entity te permite también añadir campos nuevos sobre la marcha si es que la entidad ya existe.

Ahora tenemos que decirle a la aplicación web que guarde los datos en SQLite, ya que por defecto los tratará de guardar en Mysql. Así que vamos a nuestro fichero .env y ponemos:

#DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db

Ahora las principales variables de configuración como contraseñas, parámetros de configuración, etc.. entran a la aplicación a modo de variables de entorno. Esto es más seguro y práctico, pero también es material para otro post. Ya está, ahora ejecutamos esto:

php bin/console doctrine:database:create
php bin/console doctrine:schema:create

Si todo ha ido bien, acabamos de crear el fichero data.db y la tabla Contact para guardar los contactos que nos hagan los visitantes.

3. Pintar el formulario en la página de aterrizaje

Vamos a simplificar todo, así que sólo tendremos una página que va a ser la home, en donde estará este formulario. Así creamos el controlador que recibe la visita:

php bin/console make:controller

Yo le he llamado MainController al mío y me crea los ficheros necesarios:

Symfony tutorial 2 make controller

De nuevo Symfony nos ha hecho mucho trabajo. Podemos ya arrancar el servidor de desarrollo haciendo esto:

php bin/console server:start

Y vamos a ver los resultados en http://localhost:8000/

Para poner el formulario aquí vamos a editar el fichero symfony-tutorial-2/templates/main/index.html.twig y ponemos:

{% extends 'base.html.twig' %}

{% block title %}¡Creando landings!{% endblock %}

{% block body %}
    {{ form(theForm) }}
{% endblock %}

Ahora mismo la plantilla intentará pintar el formulario llamado theForm pero no se lo hemos pasado, vamos a crearlo:

php bin/console make:form
Symfony tutorial 2 formulario

Mira en la imagen, si le decimos a Symfony al crear el formulario que cargue de una entidad que ya hemos creado, Symfony creará todos los campos del formulario automáticamente. Así que ahora queda pasar con el controlador el formulario a la plantilla Twig. Editamos el fichero symfony-tutorial-2/src/Controller/MainController.php para que quede como lo siguiente:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Form\ContactType;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index()
    {
        $theForm = $this->createForm(ContactType::class);

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

Ya podemos recargar la página en el navegador y debemos ver el formulario. Sino, algo nos falta. Ahora nos queda recibir los formularios, guardarlos en base de datos y enviarnos estos datos a nuestro email.

4. Recibir el formulario

Si nos fijamos en el formulario, todavía no tiene botón de enviar, así que vamos a editar el fichero del formulario para añadírselo. En el fichero symfony-tutorial-2/src/Form/ContactType.php añadimos esta línea en los campos:

->add('submit', SubmitType::Class)

..y en la cabecera incluimos la clase del tipo submit así:

use Symfony\Component\Form\Extension\Core\Type\SubmitType;

Refrescamos y nos sale el formulario listo para enviar los datos:

Symfony tutorial 2 formulario submit

Ahora vamos a recibir los datos en el controlador. Para esto tenemos que recibir los datos en un objeto de PHP, del tipo de objeto que hemos creado antes. Es decir, vamos a usar la entidad Contact para que Symfony se encargue de todo. Es más fácil leer el código que explicarlo, en el controlador ponemos ahora:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Form\ContactType;
use App\Entity\Contact;
use Symfony\Component\HttpFoundation\Request;

class MainController extends Controller
{
    /**
     * @Route("/", name="main")
     */
    public function index(Request $request)
    {
        $contact = new Contact();
        $theForm = $this->createForm(ContactType::class, $contact);

        $theForm->handleRequest($request);

        if ($theForm->isSubmitted() && $theForm->isValid()) {
            // save in database
            // send email
        }

        return $this->render('main/index.html.twig', [
            'theForm' => $theForm->createView(),
        ]);
    }
}

He marcado en negrita lo que acabo de añadir. Con esto se recibe y se valida que los datos son correctos automáticamente. Es una maravilla, ¡Symfony se encarga de casi todo! Ya sólo nos queda guardar el contacto en la base de datos y enviárnoslo por email.

5. Guardar en la base de datos

Para guardarlo en la base de datos simplemente añadimos lo siguiente:

// save in database
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($contact);
$entityManager->flush();

Ahora sí, probamos el formulario y nos lo enviamos. Tendremos que poder abrir el fichero de la base de datos para ver cómo se guardan estos datos en la tabla que corresponde. Tengo Sqliteman instalado para ver estos ficheros, que viene en los repositorios de Ubuntu, pero hay muchos gestores de SQLite. Con un gestor de ficheros SQLite podremos abrir el fichero symfony-tutorial-2/var/data.db y veremos algo parecido a esto:

Symfony tutorial 2 formulario recibiendo datos

Ya sólo nos quedaría enviárnoslo a nuestro email. Es sencillo pero vamos a dejarlo para el siguiente post, que este ya está quedando muy espeso. Además el envío de correos electrónicos tiene mucho juego y hoy no me queda más tiempo 🙂

¡Un saludo!


Magento 1: resolviendo conflictos entre módulos

2018-04-07 - Categorías: Magento / PHP
Magento conflictos entre modulos

Magento es muy grande, aguanta carros y carretas, pudiéndole añadir gran cantidad de módulos. Uno de los proyectos en los que he participado alcanzó la friolera de 131 módulos, siendo algunos de estos módulos mega-módulos. Ha requerido mucho esfuerzo, todo hay que decirlo. Casi nunca suele ser “copiar y pegar los ficheros y ya está” como aquel que dice. Por el camino hay que resolver los conflictos, y tratamos día a día de evitar el añadir módulos si no es bien necesario. Hacemos limpieza de todos los que no se usen, y tratamos de actualizar todos los que se puedan, depende del tiempo que haya. Siempre surgen conflictos, y supone mucho tiempo hacer funcionar todas las opciones de ciertos módulos que entran en conflicto entre sí. Esto es el día a día del mantenimiento de los Magentos.

Origen de los problemas

Los desarrolladores hacen los módulos en un Magento limpio, recién instalado, sin que se requiera ningún otro módulo para su funcionamiento. Este es el entorno ideal, un Magento recién instalado. Pero la realidad nunca es así.

Continuar leyendo..

Magento 1: flushear la caché de Magento

2018-03-27 - Categorías: Magento / PHP
Flus Magento Cache

Es habitual que cuando haces cambios en Magento necesites flushear la caché. Esto es resetear, borrar los ficheros o datos temporales, que almacena Magento para agilizar sus tareas.

Magento va guardando ficheros intermedios que se ejecutan más rápido que los originales. También guarda variables, combina ficheros de configuración de los módulos, traducciones, bloques, layouts de las plantillas, etc.. Todos estos ficheros agilizan mucho la ejecución, así que si no tienes la caché activada deberias de activarla ya mismo.

Problema

Puede ser que tengas que estar instalando y desinstalando módulos y de repente deja de funcionar Magento. Quizá has modificado en la base de datos algún dato y no se ven los cambios. O quizá incluso modificando algunos ficheros te deja de funcionar Magento.

El problema viene cuando no puedes entrar al panel de control de Magento y darle al botón de ‘Flush Magento Cache’.

Continuar leyendo..

Magento 1: listar todas las alertas por vuelta a stock de producto

2018-03-07 - Categorías: Magento / PHP

En el post anterior hablaba de cómo activar estas alertas, para que el cliente se pudiera registrar a las alertas por vuelta a stock de producto. Si tenemos activada esta opción deben de haberse apuntado los clientes interesados en ciertos productos que no tienes en stock ahora mismo.

El siguiente paso es pedir productos a los proveedores..

Ahora bien, según vuelvas a tener stock, si había algún suscrito a esta alerta, recibirá un email invitándole a venir a comprar el producto. Pero y si además de esto, ¿miras qué suscripciones y a qué productos se han suscrito para hacer las compras más a tiro hecho?

Continuar leyendo..

PrEDA: cálculo empírico del coste temporal de Binomial

2018-02-04 - Categorías: PHP / PrEDA
Números

El coeficiente binomial es una función muy utilizada en combinatoria. Sirve para saber el número de combinaciones de elementos que podemos hacer. Se utiliza mucho en computación para estudiar la estrategia de programación dinámica, porque ejemplifica muy bien la mejora que supone utilizar dicha estrategia. Por ejemplo, la forma de escoger de entre 6 elementos 2 de ellos, es igual al coeficiente binomial (6 2).

Su definición es:

Binomial(n, k) = 1, si k=0 o k=n
Binomial(n, k) = Binomial(n-1, k-1) + Binomial(n-1, k), en otro casi

Partiendo de esta definición tenemos en PHP el siguiente código:

function binomialRecursive($n, $k)
{
    if ($k == 0 or $k == $n) {
        return 1;
    } else {
        return binomialRecursive($n - 1, $k - 1) + binomialRecursive($n - 1, $k);
    }
}

Para números grandes se repiten cálculos muchas veces, con lo que reducimos su coste temporal aplicando programación dinámica:

function binomialDynamic($n, $k)
{
    $tablaNK = array();

    if ($k <= 0 or $k == $n) {
        return 1;
    } else {
        for ($i = 0; $i <= $n; ++$i) {
            $tablaNK[$i][0] = 1;
        }
        for ($i = 1; $i <= $k; ++$i) {
            $tablaNK[$i][$i] = 1;
        }
        for ($i = 2; $i <= $n; ++$i) {
            for ($j = 1; $j < $i; ++$j) {
                if ($j <= $k) {
                    $tablaNK[$i][$j] = $tablaNK[$i - 1][$j - 1] + $tablaNK[$i - 1][$j];
                }
            }
        }

        /*for ($i = 0; $i < $n; ++$i) {
            for ($j = 0; $j < $k; ++$j) {
                if ($j <= $i) {
                    echo str_pad($tablaNK[$i][$j], 3);
                }
            }
            echo PHP_EOL;
        }*/

        return $tablaNK[$n][$k];
    }
}

Entonces iremos recorriendo de izquierda a derecha y de arriba a abajo rellenando la tabla. No tendremos que recalcular de nuevo ningún binomial anterior, con lo que el coste temporal final va a ser lineal.

Igual que en el post anterior, si metemos las dos funciones recursiva y dinámica en un bucle podemos ver cómo evolucionan. El código fuente en PHP queda así:

define('MAX_N', 100);

for ($n = 3; $n <= MAX_N; ++$n) {
    $k = intval($n / 2);
    $timeStart = microtime(true);
    $theNumber = binomialRecursive($n, $k);
    //echo 'binomialRecursive('.$n.', '.$k.') = '.$theNumber.PHP_EOL;
    $timeEnd = microtime(true);
    echo 'RECURSIVE binomial('.$n.','.$k.'): Calculated in '.number_format($timeEnd - $timeStart, 9).' seconds.'.PHP_EOL;

    $timeStart = microtime(true);
    $theNumber = binomialDynamic($n, $k);
    //echo 'binomialDynamic('.$n.', '.$k.') = '.$theNumber.PHP_EOL;
    $timeEnd = microtime(true);
    echo '  DYNAMIC binomial('.$n.','.$k.'): Calculated in '.number_format($timeEnd - $timeStart, 9).' seconds.'.PHP_EOL;
}

Ponemos todo en un fichero y al ejecutarlo podremos ver claramente que tenemos que usar programación dinámica. Para números grandes, no podremos calcular sus binomiales si no es aplicando programación dinámica.

RECURSIVE binomial(6,3): Calculated in 0.000006914 seconds.
 DYNAMIC binomial(6,3): Calculated in 0.000010014 seconds.
RECURSIVE binomial(7,3): Calculated in 0.000009060 seconds.
 DYNAMIC binomial(7,3): Calculated in 0.000010014 seconds.
RECURSIVE binomial(8,4): Calculated in 0.000020981 seconds.
 DYNAMIC binomial(8,4): Calculated in 0.000010014 seconds.
RECURSIVE binomial(9,4): Calculated in 0.000045061 seconds.
 DYNAMIC binomial(9,4): Calculated in 0.000010014 seconds.
RECURSIVE binomial(10,5): Calculated in 0.000081062 seconds.
 DYNAMIC binomial(10,5): Calculated in 0.000010967 seconds.
RECURSIVE binomial(11,5): Calculated in 0.000133038 seconds.
 DYNAMIC binomial(11,5): Calculated in 0.000012159 seconds.
RECURSIVE binomial(12,6): Calculated in 0.000247002 seconds.
 DYNAMIC binomial(12,6): Calculated in 0.000014067 seconds.
RECURSIVE binomial(13,6): Calculated in 0.000452042 seconds.
 DYNAMIC binomial(13,6): Calculated in 0.000015974 seconds.
RECURSIVE binomial(14,7): Calculated in 0.000910997 seconds.
 DYNAMIC binomial(14,7): Calculated in 0.000026941 seconds.
RECURSIVE binomial(15,7): Calculated in 0.001642227 seconds.
 DYNAMIC binomial(15,7): Calculated in 0.000020027 seconds.
RECURSIVE binomial(16,8): Calculated in 0.003252029 seconds.
 DYNAMIC binomial(16,8): Calculated in 0.000024080 seconds.
RECURSIVE binomial(17,8): Calculated in 0.005996943 seconds.
 DYNAMIC binomial(17,8): Calculated in 0.000023842 seconds.
RECURSIVE binomial(18,9): Calculated in 0.013009787 seconds.
 DYNAMIC binomial(18,9): Calculated in 0.000043154 seconds.
RECURSIVE binomial(19,9): Calculated in 0.020354033 seconds.
 DYNAMIC binomial(19,9): Calculated in 0.000036001 seconds.
RECURSIVE binomial(20,10): Calculated in 0.036875963 seconds.
 DYNAMIC binomial(20,10): Calculated in 0.000036955 seconds.
RECURSIVE binomial(21,10): Calculated in 0.061520815 seconds.
 DYNAMIC binomial(21,10): Calculated in 0.000036955 seconds.
RECURSIVE binomial(22,11): Calculated in 0.120537996 seconds.
 DYNAMIC binomial(22,11): Calculated in 0.000041008 seconds.
RECURSIVE binomial(23,11): Calculated in 0.230448008 seconds.
 DYNAMIC binomial(23,11): Calculated in 0.000044107 seconds.
RECURSIVE binomial(24,12): Calculated in 0.459716797 seconds.
 DYNAMIC binomial(24,12): Calculated in 0.000044823 seconds.
RECURSIVE binomial(25,12): Calculated in 0.885799885 seconds.
 DYNAMIC binomial(25,12): Calculated in 0.000042915 seconds.
RECURSIVE binomial(26,13): Calculated in 1.761939049 seconds.
 DYNAMIC binomial(26,13): Calculated in 0.000046015 seconds.
RECURSIVE binomial(27,13): Calculated in 3.628232956 seconds.
 DYNAMIC binomial(27,13): Calculated in 0.000091076 seconds.
RECURSIVE binomial(28,14): Calculated in 7.495334148 seconds.
 DYNAMIC binomial(28,14): Calculated in 0.000072956 seconds.
RECURSIVE binomial(29,14): Calculated in 13.519285917 seconds.
 DYNAMIC binomial(29,14): Calculated in 0.000084877 seconds.
RECURSIVE binomial(30,15): Calculated in 27.332123995 seconds.
 DYNAMIC binomial(30,15): Calculated in 0.000058174 seconds.
RECURSIVE binomial(31,15): Calculated in 53.179553986 seconds.
 DYNAMIC binomial(31,15): Calculated in 0.000060081 seconds.
RECURSIVE binomial(32,16): Calculated in 107.939800024 seconds.
 DYNAMIC binomial(32,16): Calculated in 0.000065088 seconds.
RECURSIVE binomial(33,16): Calculated in 203.573032141 seconds.
 DYNAMIC binomial(33,16): Calculated in 0.000068903 seconds.
RECURSIVE binomial(34,17): Calculated in 409.646430969 seconds.
 DYNAMIC binomial(34,17): Calculated in 0.000072002 seconds.

PrEDA: cálculo empírico del coste temporal de Fibonacci

2018-02-04 - Categorías: PHP / PrEDA
Números

Continuando con post anteriores sobre esquemas algorítmicos. Dejo aquí unas pruebas empíricas sobre cómo calcular el Número de Fibonacci de números grandes. Si vemos la definición de Fibonacci es la siguiente:

Fibonacci(n) = 1, si n=0 o n=1
Fibonacci(n) = Fibonacci(n-1) + Fibonacci(n-2), si n> 1

El código en PHP para resolverlo sería el siguiente:

function fibonacciRecursive($n)
{
    if ($n <= 1) {
        return 1;
    } else {
        return fibonacciRecursive($n - 1) + fibonacciRecursive($n - 2);
    }
}

Observamos que se repiten cálculos, con lo que aplicando programación dinámica tenemos el siguiente código:

function fibonacciDynamic($n)
{
    $tabla = array();

    if ($n <= 1) {
        return 1;
    } else {
        $tabla[0] = $tabla[1] = 1;

        for ($i = 2; $i <= $n; ++$i) {
            $tabla[$i] = $tabla[$i - 1] + $tabla[$i - 2];
        }

        return $tabla[$n];
    }
}

Estas dos funciones, para compararlas empíricamente, las usamos en un bucle que vaya incrementando el número $n para obtener su coste temporal empírico. También en PHP con éste código:

define('MAX_N', 100);

for ($i = 0; $i <= MAX_N; ++$i) {
    $timeStart = microtime(true);
    $theNumber = fibonacciRecursive($i);
    //echo 'fibonacciRecursive('.$i.') = '.$theNumber.PHP_EOL;
    $timeEnd = microtime(true);
    echo 'RECURSIVE fibonacci('.$i.'): Calculated in '.number_format($timeEnd - $timeStart, 9).' seconds.'.PHP_EOL;

    $timeStart = microtime(true);
    $theNumber = fibonacciDynamic($i);
    //echo 'fibonacciDynamic('.$i.') = '.$theNumber.PHP_EOL;
    $timeEnd = microtime(true);
    echo '  DYNAMIC fibonacci('.$i.'): Calculated in '.number_format($timeEnd - $timeStart, 9).' seconds.'.PHP_EOL;
}

Ahora bien, ejecutamos todo junto. Así podemos ver cómo, para números grandes, se convierte en imposible resolver el Número de Fibonacci con programación recursiva:

RECURSIVE fibonacci(22): Calculated in 0.002473116 seconds.
  DYNAMIC fibonacci(22): Calculated in 0.000002861 seconds.
RECURSIVE fibonacci(23): Calculated in 0.004077911 seconds.
  DYNAMIC fibonacci(23): Calculated in 0.000005007 seconds.
RECURSIVE fibonacci(24): Calculated in 0.006484985 seconds.
  DYNAMIC fibonacci(24): Calculated in 0.000003815 seconds.
RECURSIVE fibonacci(25): Calculated in 0.010952950 seconds.
  DYNAMIC fibonacci(25): Calculated in 0.000016928 seconds.
RECURSIVE fibonacci(26): Calculated in 0.017073154 seconds.
  DYNAMIC fibonacci(26): Calculated in 0.000005960 seconds.
RECURSIVE fibonacci(27): Calculated in 0.027941942 seconds.
  DYNAMIC fibonacci(27): Calculated in 0.000008106 seconds.
RECURSIVE fibonacci(28): Calculated in 0.045236111 seconds.
  DYNAMIC fibonacci(28): Calculated in 0.000008106 seconds.
RECURSIVE fibonacci(29): Calculated in 0.073548079 seconds.
  DYNAMIC fibonacci(29): Calculated in 0.000009060 seconds.
RECURSIVE fibonacci(30): Calculated in 0.118798018 seconds.
  DYNAMIC fibonacci(30): Calculated in 0.000012159 seconds.
RECURSIVE fibonacci(31): Calculated in 0.202280998 seconds.
  DYNAMIC fibonacci(31): Calculated in 0.000010967 seconds.
RECURSIVE fibonacci(32): Calculated in 0.310992002 seconds.
  DYNAMIC fibonacci(32): Calculated in 0.000011921 seconds.
RECURSIVE fibonacci(33): Calculated in 0.506221056 seconds.
  DYNAMIC fibonacci(33): Calculated in 0.000011921 seconds.
RECURSIVE fibonacci(34): Calculated in 0.834569931 seconds.
  DYNAMIC fibonacci(34): Calculated in 0.000009060 seconds.
RECURSIVE fibonacci(35): Calculated in 1.305525780 seconds.
  DYNAMIC fibonacci(35): Calculated in 0.000010014 seconds.
RECURSIVE fibonacci(36): Calculated in 2.117379189 seconds.
  DYNAMIC fibonacci(36): Calculated in 0.000010014 seconds.
RECURSIVE fibonacci(37): Calculated in 3.495128870 seconds.
  DYNAMIC fibonacci(37): Calculated in 0.000009060 seconds.
RECURSIVE fibonacci(38): Calculated in 5.618491888 seconds.
  DYNAMIC fibonacci(38): Calculated in 0.000010014 seconds.
RECURSIVE fibonacci(39): Calculated in 9.390465975 seconds.
  DYNAMIC fibonacci(39): Calculated in 0.000010014 seconds.
RECURSIVE fibonacci(40): Calculated in 14.963202953 seconds.
  DYNAMIC fibonacci(40): Calculated in 0.000010014 seconds.

La función Fibonacci recursiva, crece exponencialmente, mientras que la dinámica es lineal. Es decir, que más nos vale usar la función de programación dinámica.


PrEDA: esquemas de programación genéricos avanzados

2018-02-03 - Categorías: PHP / PrEDA
PrEDA algoritmos genericos

Otro HOWTO, code-kata, a traer vengo hoy..

XDD vengo con este post cargado de esquemas algorítmicos para programar. Estos esquemas algorítmicos son los que nos ayudan a programar las mejores soluciones. Así conseguimos llegar a las soluciones que a veces tenemos que encontrar, pero quizá no vemos en un principio cómo. Quizá el problema lo tenemos en que tarda mucho, o necesitamos demasiado espacio de almacenamiento.

La mayoría de las veces, podemos recorrer todas las posibles soluciones a lo bruto hasta encontrar la solución. Es decir, muchos problemas se pueden resolver de muchas formas, pero no todas las formas de resolver el mismo problema son la mejor forma. Sólo hay una mejor forma para cada problema. Es decir, algunas soluciones son eficaces porque simplemente encuentran la solución, pero quizás no son la forma más eficiente. Incluso podemos tener problemas, que por no resolverlos eficientemente, se convierten en imposibles de resolver.

Se trata de estos esquemas:

  • Voraz.
  • Divide y vencerás.
  • Programación dinámica.
  • Vuelta atrás.
  • Ramificación y poda.

Vamos al grano hermano..

Esquema voraz

Este esquema o estrategia se utiliza para optimizar el funcionamiento cuando tenemos una única solución, y además, podemos establecer un algoritmo de selección de los elementos que forman la solución. Si estas selecciones no se vuelven a reconsiderar, entonces esta es la estrategia que debemos usar.

En estos problemas solemos tener un conjunto de elementos, que pueden formar parte de la solución o no. Por ejemplo, si estamos construyendo el camino más corto entre dos nodos de un grafo, tendremos que las aristas son las partes que podremos añadir o no a la solución. Si por ejemplo estamos eligiendo entre un conjunto de cosas, cada cosa podrá formar parte de la solución o no. Es crítico que debe de existir una función de selección y que no se vuelva a reconsiderar. Se puede usar para problemas de grafos, recorridos mínimos o máximos, el algoritmo de Dijkstra, Kruskal, Prim, mochila fraccionable..

Este esquema genérico en PHP queda así:

function Voraz($conjuntoCandidatos)
{
    $solucion = array();

    while (count($conjuntoCandidatos) != 0 and !esSolucion($solucion)) {
        $x = seleccionarSiguiente($conjuntoCandidatos);
        $conjuntoCandidatos = array_diff($conjuntoCandidatos, array($x));

        if (esFactible(array_merge($solucion, array($x)))) {
            $solucion = array_merge($solucion, array($x));
        }
    }

    if (esSolucion($solucion)) {
        return $solucion;
    } else {
        echo 'No hay solución.'.PHP_EOL;
    }
}

Fíjate que aquí no tenemos recursión, es una función iterativa. Se va estudiando el problema mientras que haya candidatos pendientes de estudio. De igual forma, se repite una y otra vez el bucle principal while mientras que no hemos encontrado la solución. Fíjate también en que si hubiéramos encontrado la solución se sale del bucle.

Esquema divide y vencerás

Ésta estrategia es la que se aplica cuando tenemos un problemón que podemos dividirlo en subproblemas más pequeños. Debemos de poder divirlo en problemas más pequeños, y estos a su vez, debemos poder subdividirlos una y otra vez, tantas veces como consideremos. Así sucesivamente hasta llegar a problemas muy pequeños de fácil solución. Es decir, tendremos una recursión hasta llegar a uno o varios casos base que pararán la recursión. Finalmente volveremos a construir la solución uniendo las soluciones parciales, hasta llegar a la solución del problema inicial.

En esta estrategia, suele ser más complicado combinar las soluciones una vez que hemos llegado a los casos base, y vamos subiendo combinando todas las soluciones. Tenemos aquí problemas como por ejemplo: la multiplicación de grandes números con el algoritmo de Karatsuba, Euclides para el máximo común divisor, Mergesort o Quicksort, Torres de Hanoi, exponenciales..

Nos queda así el esquema genérico en PHP:

function divideYVenceras($problema)
{
    if (esTrivial($problema)) {
        return solucionTrivial($problema);
    } else {
        $subproblemas = descomponer($problema);
        foreach ($subproblemas as $subproblema) {
            $subsoluciones[] = divideYVenceras($subproblema);
        }
    }

    return componer($subsoluciones);
}

Esquema de programación dinámica

Este esquema se debe usar cuando tenemos que hacer cálculos, que se repiten más de una vez. Es decir, si podemos almacenar los resultados parciales para luego reutilizarlos, así no tendremos que calcular varias veces lo mismo. Entonces, mejoraremos el coste de tiempo, aunque gastemos en espacio para almacenar estas soluciones.

Se suele utilizar una tabla en donde se van almacenando los valores calculados.

No existe un esquema genérico para esta estrategia, porque depende mucho de la forma en que necesitamos almacenar los datos y luego consultarlos. Es decir, el esquema de estas soluciones depende mucho del problema. Dichos problemas se suelen poder resolver mediante el esquema de divide y vencerás. Además, suelen ser recursivos, en donde hay más de una recursión por vez que se llama a la función principal. El caso más sencillo para estudiarlo es la función de Fibonacci, que en PHP queda así:

function fibonnaci($n)
{
    $tabla = array();

    if ($n <= 1) {
        return 1;
    } else {
        $tabla[0] = $tabla[1] = 1;

        for ($i = 2; $i <= $n; ++$i) {
            $tabla[$i] = $tabla[$i - 1] + $tabla[$i - 2];
        }

        return $tabla[n];
    }
}

Fíjate que si usáramos una función recursiva quedaría de la siguiente forma, que es menos eficiente. He marcado las recursiones abajo y los accesos a la tabla arriba para ver el paralelismo:

function fibonnaciRecursiva($n)
{
    if ($n <= 1) {
        return 1;
    } else {
        return fibonnaci($n - 1) + fibonnaci($n - 2);
    }
}

El punto que deja claro que fibonnaci mejor calcularlo con programación dinámica, es que cada valor se calcula con sus dos anteriores. Si lo pensamos un poco, queda claro que cada valor se va a recalcular 2 veces si lo hacemos recursivamente. Realmente, la función recursiva tiene un coste exponencial, mientras que la dinámica es lineal. Es decir, que la forma buena de resolverlo es mediante programación dinámica. Si no utilizamos programación dinámica llegaremos tarde o temprano a valores altos que son imposibles de calcular por su coste temporal.

Esquema de vuelta atrás

Esta estrategia se utiliza cuando tenemos que recorrer todas las posibles soluciones, o más bien cuando tenemos que hacer una búsqueda exhaustiva de todas las posibles soluciones. Normalmente la solución puede darse en forma de vector, árbol, matriz.. de forma que, en cada iteración, vamos estudiando cada ramificación. Comienza como si fueramos a resolver el problema por fuerza bruta. Pero se le añaden una serie de comprobaciones o lógica para disminuir el árbol de posibilidades que recorremos.

Se puede usar para recorrer grafos, moverte por escenarios planos como tableros de ajedrez, construir palabras o hacer operaciones sobre ellas, etc.. Casi cualquier problema que implique un estudio de todas las posibles soluciones.

Aquí es esquema general en PHP:

function vueltaAtras($secuencia, $k)
{
    iniciarExploracionNivel($secuencia, $k);

    while (opcionesPendientes($secuencia, $k)) {
        extenderConSiguienteOpcion($secuencia, $k);

        if (solucionCompleta($secuencia)) {
            procesarSolucion($secuencia);
        } else {
            if (completable($secuencia)) {
                vueltaAtras($secuencia, $k + 1);
            }
        }
    }
}

Un variante de este esquema, para que el algoritmo deje de buscar si le basta con sólo una solución:

function vueltaAtras1solucion($secuencia, $k)
{
    $encontrado = false;
    iniciarExploracionNivel($secuencia, $k);

    while (opcionesPendientes($secuencia, $k) and !$encontrado) {
        extenderConSiguienteOpcion($secuencia, $k);

        if (solucionCompleta($secuencia)) {
            procesarSolucion($secuencia);
            $encontrado = true;
        } else {
            if (completable($secuencia)) {
                vueltaAtras1solucion($secuencia, $k + 1);
            }
        }
    }
}

Lo más importante de ver en este algoritmo son dos cosas: una que se trata de un algoritmo recursivo, la otra que se lleva un contador de nivel $k para ir sabiendo el nivel en curso de la solución que estamos estudiando.

Esquema de ramificación y poda

Este último esquema es una evolución del anterior. Pero se trata de obtener una de las soluciones óptimas, no se trata de encontrar todas las posibles soluciones. La diferencia con respecto al anterior, es que vamos a ir calculando unas estimaciones de las ramificaciones que podemos tomar. Si estas ramificaciones no pueden mejorar la mejor solución encontrada, se poda dicha ramificación. Por otro lado llevamos un coste mejor encontrado que iremos actualizando para refinar las ramificaciones. Así con este coste mejor no ramificaremos si no es mejorable, y lo actualizaremos si el coste peor ya lo mejora.

Está el punto crítico de poder encontrar una fución de estimación peor y otra mejor. También es importante ver que es un algoritmo iterativo, para lo cual se vale de un montículo en donde almacena las posibles soluciones para luego estudiarlas. Aquí el esquema genérico:

function ramificacionYPoda($nodoRaiz, $mejorSolucion)
{
    $monticulo = new Monticulo();
    $cota = estimacionPesimista($nodoRaiz);
    $monticulo->insertar($nodoRaiz);

    while (!esVacio($monticulo) and estimacionOptimista($monticulo->getPrimero()) <= $cota) { 
        $nodo = $monticulo->getCima();

        foreach (hijosValidos($nodo) as $hijo) {
            if (esSolucion($hijo)) {
                if (coste($hijo) <= $cota) {
                    $cota = coste($hijo);
                    $mejorSolucion = $hijo;
                }
            } else {
                if (estimacionOptimista($hijo) <= $cota) { 
                    $monticulo->insertar($hijo);
                    if (estimacionPesimista($hijo) < $cota) {
                        $cota = estimacionPesimista($hijo);
                    }
                }
            }
        }
    }
}

En el montículo deberemos de tener arriba el nodo siguiente mejor. Es decir, tendremos un montículo de mínimos o máximos según lo que se estime que será mejor.


Magento 1: cómo sacar las ventas por método de pago

2018-01-26 - Categorías: Magento / PHP
Magento ventas por mes y método de pago

Hoy traigo otro code-kata, esta vez para Magento y hecho en PHP. Se trata de un simple script que se lanza en línea de comandos. Con este script obtendremos primero todos los métodos de pago que se han usado al hacer los pedidos. Después, haremos un listado, por método de pago, y entre ciertas fechas, del monto de pedidos completados por método de pago.

Es decir, supongamos que queremos saber cómo han ido las ventas con el método de pago por tarjeta de crédito. O quizá queremos saber si la financiación es un método de pago que funciona bien. O quizá simplemente queremos tener el total de ventas entre fechas.

Pues todo esto es bastante sencillo sabiendo donde está cada cosa.

Métodos de pago usados en pedidos

A saber, los pedidos en Magento 1 guardan parte de sus datos principales en la tabla sales_flat_order. La información del método de pago utilizado en cada pedido se guarda en la tabla sales_flat_order_payment.

Entonces, sacando la columna method de la tabla sales_flat_order_payment tendremos todos los métodos de pago utilizados en los pedidos. Esto en lenguaje SQL queda así:

SELECT distinct(sfop.method)
FROM sales_flat_order_payment sfop
ORDER BY sfop.method ASC;

Ventas de los pedidos enviados

El siguiente paso será saber cómo sacar las cantidades de los pedidos completados. A saber, los pedidos completados se quedan con un status=complete y state=complete. Sí, es raro, Magento tiene internamente dos estados en cada pedido. Uno llamado status y otro llamado state. Estos estados están respectivamente en sus columnas. Así la consulta a la base de datos en SQL para sacar las ventas de un método de pago queda así:

SELECT sum(sfo.grand_total) total_sold
FROM sales_flat_order sfo
JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'"

Fíjate que la tabla sales_flat_order_payment se relaciona con sales_flat_order de la forma que marco en negrita.

El código completo

Ahora bien, haciendo un poco de ejercicio con un par de bucles, programando el script completo, nos queda algo tal que así:

<php

require_once __DIR__.'/app/Mage.php';

Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

// Loading arguments.
if ($argc == 3) {
    $yearFrom = $argv[1];
    $yearTo = $argv[2];
} else {
    echo 'ERROR: Incorrect number of arguments.'.PHP_EOL;
    exit;
}

$sqlConnection = Mage::getSingleton('core/resource')->getConnection('core_read');

// Loading available method codes.
$query = 'SELECT distinct(sfop.method)
    FROM sales_flat_order_payment sfop
    ORDER BY sfop.method ASC;';
$methods = $sqlConnection->fetchAll($query);
$methodCodes = array();
foreach ($methods as $method) {
    $methodCodes[] = $method['method'];
}
//var_dump($methodCodes);

// Printing table in CSV format from January to December..
echo 'PAYMENT_METHOD';
for ($year = $yearFrom; $year <= $yearTo; ++$year) {
    for ($month = 1; $month <= 12; ++$month) {
        echo ','.$year.$month;
    }
}
echo PHP_EOL;
foreach ($methodCodes as $methodCode) {
    echo $methodCode;
    for ($year = $yearFrom; $year <= $yearTo; ++$year) {
        for ($month = 1; $month <= 11; ++$month) { 
            $startDate = new DateTime($year.'-'.$month.'-1 00:00:00'); 
            //echo 'Date from: '.$startDate->format('Y-m-d H:i:s').PHP_EOL;
            $endDate = new DateTime($year.'-'.($month + 1).'-1 00:00:00');
            //echo 'Date to: '.$endDate->format('Y-m-d H:i:s').PHP_EOL;

            $query = "SELECT sum(sfo.grand_total) total_sold
            FROM sales_flat_order sfo
            JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
            WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
            AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
            AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'";
            $amount = $sqlConnection->fetchOne($query);
            echo ','.$amount;
        }
        $startDate = new DateTime($year.'-12-1 00:00:00');
        //echo 'Date from: '.$startDate->format('Y-m-d H:i:s').PHP_EOL;
        $endDate = new DateTime($year.'-12-31 23:59:59');
        //echo 'Date to: '.$endDate->format('Y-m-d H:i:s').PHP_EOL;

        $query = "SELECT sum(sfo.grand_total) total_sold
        FROM sales_flat_order sfo
        JOIN sales_flat_order_payment sfop ON sfop.parent_id = sfo.entity_id
        WHERE sfo.state = 'complete' AND sfo.state = 'complete' AND sfop.method LIKE '".$methodCode."'
        AND sfo.created_at > '".$startDate->format('Y-m-d H:i:s')."'
        AND sfo.created_at < '".$endDate->format('Y-m-d H:i:s')."'";
        $amount = $sqlConnection->fetchOne($query);
        echo ','.$amount;
    }
    echo PHP_EOL;
}

Este script tendremos que ponerlo en el directorio raiz del proyecto Magento para que enganche al Magento. Fíjate que necesita de dos parámetros de entrada que son el año de inicio y el año de fin. Además he marcado un objeto que proporciona Magento para estas cosas, la conexión a la base de datos que se guarda en $sqlConnection.

Ejecutando

Guardamos el script anterior en un fichero, por ejemplo llamado script.php. Si queremos las ventas desde el año 2017 al 2017, lo ejecutamos así desde línea de comandos:

$ php script.php 2017 2017

Veremos la salida por pantalla. Ahora bien, si redireccionamos la salida del script a un fichero, luego podremos visualizarlo mejor. Para esto lo ejecutamos así:

$ php script.php 2017 2017 > ventas_por_metodo_de_pago.csv

Ahora abrimos el fichero CSV con nuestro programa de hojas de cálculo favorito para ver los datos. Si no tienes, es muy recomendable el LibreOffice que lo puedes descargar gratis haciendo click aquí.

Así jugando un poco con los datos resultantes, podemos tener una gráfica de evolución de los métodos de pago. Como la imagen de cabecera de arriba. Podemos ver que algo está pasando en abril y por Navidades del año 2017 con uno de los métodos de pago.

Espero que sirva.

Un saludo.


PHP: qué es un Singleton y para qué nos puede servir

2018-01-21 - Categorías: General / PHP
Coding

Ya vengo a la carga con nuevo material. Hoy con un patrón de diseño de software que puede que necesitemos. Se trata del Singleton, que simplemente es un tipo de objeto de programación. En Programación Orientada a Objetos (POO), tenemos este tipo de objetos que se usan para sólo instanciar uno y exclusivamente uno en todo el programa.

No sabemos cuántas veces ni en cuántos lugares se va a usar el objeto. Pero sí que sabemos que necesitamos que sólo exista uno como máximo. De aquí que viene su nombre de Singleton.

Porqué

La razón de ser de los Singletones es que sólo exista una instancia del objeto en todo el programa, y que sea accesible desde todo el programa. Se usa para guardar datos o funcionalidades globales, compartidas por todo el programa. Además, esta forma de carga única y acceso global mejora el rendimiento ya que la creación del objeto se hace una vez, guardándose en memoria todo el rato hasta que finaliza la ejecución. También mejora el rendimiento porque si no se instancia nunca el objeto, no se crea, con lo que no se gasta tiempo ni espacio innecesariamente.

La idea general es, limitar el uso del constructor por defecto de la clase, de forma que no se pueda llamar para construir instancias de este objeto. Y mediante otra función llamada getInstance() controlar que sólo se llame al contructor en la primera vez. En las siguientes llamadas a la función getInstance() simplemente se devuelve el objeto que ya lo tenemos creado.

El código

<?php

class MySingleton
{
    private static $instance;
    private $counter;

    private function __construct()
    {
        echo 'Contruyendo objeto..'.PHP_EOL;
    }

    public static function getInstance()
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public function hazAlgo()
    {
        echo 'Escribimos algo por pantalla.'.PHP_EOL;
        ++$this->counter;
    }

    public function getCounter()
    {
        return $this->counter;
    }
}

MySingleton::getInstance()->hazAlgo();
MySingleton::getInstance()->hazAlgo();
MySingleton::getInstance()->hazAlgo();

echo 'Contador = '.mySingleton::getInstance()->getCounter().PHP_EOL;

Cargamos este código en un fichero, por ejemplo en singleton.php y al ejecutarlo mediante ‘php singleton.php’ vemos algo como lo siguiente:

Contruyendo objeto..
Escribimos algo por pantalla.
Escribimos algo por pantalla.
Escribimos algo por pantalla.
Contador = 3

Mira que sólo en el primer getInstance se llama al contructor.


PHP: cómo geolocalizar visitantes, IPs o nombres de dominio

2017-12-14 - Categorías: General / GNU/Linux / PHP
La Tierra

No quiero perder las buenas costumbres, y esto de escribir en mi blog es una de ellas. Así que aquí estoy de nuevo con otro pequeño HOWTO para geolocalizar ordenadores por IP o servidores por nombre de dominio. Es realmente sencillo, un juego de niños, pero por si lo necesitas en algún proyecto aquí que lo dejo.

A continuación tienes cómo instalar en PHP5.6 las librerías en un sistema operativo Linux. Luego para PHP7 y enlaces a información sobre bases de datos relacionadas. Para geolocalizar dispositivos en Internet basta con utilizar uno de estos servicios. Quizá lo que necesitas es dar una traducción de tu sitio según la localización del visitante, mostrar mensajes por país, o desplegar países, regiones y localidades. También puedes usar las coordenadas de este servicio, pero no es muy fiable localizar con este sistema a tan bajo nivel pero por tener tendrás con esto unos datos orientativos para tu web.

Abajo del post tienes enlaces a Bases de Datos con países del mundo, regiones y localidades actualizadas. Lo mejor de todo, la versión simple es gratis para que puedas usarlo en tu proyecto. Sólo con nombrar en tu proyecto a la empresa autora.

También hay empresas que ofrecen bajo pago este servicio..

En PHP5.6

Aquí tenemos que instalar unas de librerías y la base de datos pública de GeoIP. En un servidor Linux con Ubuntu, Debian o compatible hacemos lo siguiente:

$ sudo apt-get install php-geoip
$ wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
$ gunzip GeoLiteCity.dat.gz
$ sudo mkdir /usr/share/GeoIP
$ sudo mv GeoLiteCity.dat /usr/share/GeoIP/GeoIPCity.dat

Con esto ya podemos usarlo en cualquier script o programa hecho en PHP así:

<?php
var_dump(geoip_record_by_name('jnjsite.com'));

Tenemos que ver por pantalla algo parecido a:

Array
 (
 [continent_code] => EU
 [country_code] => IE
 [country_code3] => IRL
 [country_name] => Ireland
 [region] => 07
 [city] => Dublin
 [postal_code] =>
 [latitude] => 53.333099365234
 [longitude] => -6.248899936676
 [dma_code] => 0
 [area_code] => 0
 )

Tengo que decir que la latitud y longitud son bastante aproximadas. Y dependerá bastante de tu proveedor de servicios de Internet si está bien localizado en las bases de datos de MaxMind. Pero lo que es el país, región y ciudad son bastante fiables, sobretodo el país.

En PHP7

Ahora es más fácil incluso con PHP7. Sólo tendremos que instalar la siguiente librería y se nos instalará todo de una vez:

$ sudo apt-get install php-geoip

Ya está, simplemente con esto ya podemos usar en el ordenador la geolocalización mediante PHP. Hay mucha funciones disponibles.

Terminando

Sólo me queda dejar algunos enlaces interesantes:

Esto creo que es muy interesante si estás montando una plataforma grande y quieres disponer de un listado completo mundial de países, regiones y localidades: https://www.maxmind.com/es/open-source-data-and-api-for-ip-geolocation

© 2021 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.