PHP

Cómo recuperar entrada al panel de control Joomla protegido con JSecure Lite

2015-08-27 - Categorías: General

Este es un pequeño HOWTO para estos casos. Si tenemos un Joomla que hace tiempo que no visitamos y tenemos instalado el componente que cito, JSecure Lite, que protege la entrada directa al directorio /administrator/ podemos entonces recuperarlo de la siguiente forma.

Esto funciona para la versión 2.5 por lo menos.

Teniendo acceso por FTP al servidor vamos al directorio:

../administrator/components/com_jsecurelite/

Y abrimos el archivo params.php
En este archivo tenemos que ver una linea como la siguiente:

public $publish = ‘1’;

.. que la sustituimos por:

public $publish = ‘0’;

Con esto lo que hemos hecho es desactivar el componente lo que hace que no haga falta poner la clave. Ahora ya podremos entrara al sitio con la URL:

http://nombredeldominio.com/administrator/

Cuando lo vuelvas a activar no olvides que la clave se pone de la forma siguiente:

http://nombredeldominio.com/administrator/?clave

Esto es todo, un saludo.


Atom Editor, ¡brutalmente sencillo y potente!

2015-08-26 - Categorías: General

Hola de nuevo, ya estoy trasteando con un nuevo editor de código fuente que hace poco ya alcanzó la versión de producción 1 y está en el momento en que escribo en la versión 1.0.7. Estoy hablando del Atom Editor, un editor de códigos fuentes ligero, rápido, con todo tipo de plugins.. como dicen en su web: un editor hackeable del siglo 21. Es sencillo a más no poder, pero a la vez se puede mejorar instalando paquetes y pieles, que lo convierten en una potente herramienta.

Como punto de entrada me sorprendió no recuerdo donde que leí que estaba hecho en HTML, CSS y Javascript. Ya sólo esto me sorprendió así que hace un par de meses decidí instalarlo y probarlo. No probé demasiado bien, pero ahora que ya ha alcanzado cierta madurez lo he probado bien durante una semana y estos son los resultados.

Características

Al estilo Sublime Text o tipo Eclipse con un estilo ‘dark’ resulta agradable ya la primera impresión. Tus ojos descansan con colores suaves mientras lees los códigos. Como es un proyectos de Github, trae integrada la detección de cambios Git del repositorio que estés viendo. Rápido como él sólo, arranca bien, abre las ventanas rápido con un ‘click’ cada archivo. Explorador de archivos integrado. Opción de abrir un directorio, un sólo archivo, varios proyectos en la misma ventana, o varias ventanas con distintos archivos o proyectos. Puedes abrir todo tipo de archivos de texto. También imágenes, que en mi caso el poder ver las imágenes en el propio editor se agradece. Se pueden dividir las ventanas en paneles para poner en cada uno un archivo según necesitemos.

Cuando empecemos a instalar paquetes para nuestras necesidades es cuando nos daremos cuenta de lo potente que es. La comunidad está desarrollando gran cantidad de ellos para todo o casi todo lo que he buscado. En mi caso, el hacer webs usando un editor de código que en sí mismo está hecho como una web me impresiona. Le he puesto el autocompletado para los lenguajes que uso, el resaltador de error de sintáxis, el famoso minimapa y otros plugins.

Lo tenemos disponible para Linux, Mac, FreeBSD y Windows.

Aquí la web: https://atom.io/

Los packages

Cuando digo packages, aquí es lo mismo que los plugins de WordPress, los complementos de Firefox, o las aplicaciones de un sistema operativo. Pues aquí se llaman packages, que son lo que completan con infinidad de funcionalidades este fenómeno editor. En mi caso, estoy probando y me quedo con los siguientes:

  • atom-autocomplete-php: para autocompletar rápidamente en proyectos basados en Composer.
  • atom-beautify: pone bonito el código identándolo, poniendo espacios o quitándolos donde debe, etc. Si tienes instalado php-cs-fixer te formatea el código según el estándar PSR.
  • autocomplete-plus: es básico para que otros paquetes de autocompletado funcionen.
  • php-twig: compatibilidad con Twig, muestra mensajes emergentes con sugerencias también.
  • php-getters-setters: como su nombre indica, genera los getters y setters de una clase PHP.
  • minimap: este me gusta mucho porque te visualiza una ventana lateral con todo el código fuente en miniatura con el que te puedes mover por un archivo.
  • linter y linter-php: analiza el código mostrando menús emergentes, errores sintácticos, etc. Muy bueno, imprescindible.
  • docblockr: genera comentarios automáticamente según escribes en las cabeceras de clases o funciones.
  • custom-title: con el que puedes configurar los títulos de la ventana mostrando, por ejemplo, el nombre del proyecto, la rama y la ruta completa al archivo. Muy configurable y útil para saber donde estás.
  • git-plus: para hacer tareas de Git dentro del editor. En mi caso, en Linux, pulsando Ctrl+Shift+H se me despliegan los comandos disponibles.

Veo brutalmente buenos estos packages, ya cada uno que busque los suyos. Tenemos disponibles 2602 en la fecha en que escribo esto según pone en:

https://atom.io/packages

¡Hay que probarlo que es una joya de la informática!
Un saludo.


Me preocupa el despliegue contínuo, fijando versiones en composer.json (editado)

2014-11-23 - Categorías: General

¿Qué pasa si tienes un proyecto con librerías gestionadas con #Composer y le has puesto muchas de ellas que se actualicen a la última versión disponible?
Para mantener el programa actualizado, beneficiándote de las últimas mejoras de cada librería, era una buena solución. Poner todo a la versión ‘dev-master’. Jeje, todo va bien mientras estás desarrollando..

De vez en cuando alguna función ‘deprecated’ deja de existir porque definitivamente la eliminaron. Nuevas funciones o propiedades que podemos usar. La aplicación funciona cada vez mejor pero sabes que no hay otra que fijar las versiones si no cuando pasemos a producción es un riesgo que se actualice automáticamente y zas! dejó de funcionar. Así que hay que buscar la versión que tenemos instalada de cada paquete y fijarlas.


Puede que tengamos un composer.json parecido a éste, con muchos ~ * @dev dev-master >= etcétera:

"requiere" : {
  "php" : ">=5.3.3",
  "symfony/symfony" : "2.5.*",
  "doctrine/orm" : "~2.2,>=2.2.3",
  "doctrine/doctrine-bundle" : "~1.2",
  "twig/extensions" : "~1.0",
  "symfony/assetic-bundle" : "~2.3",
  "symfony/swiftmailer-bundle" : "~2.3",
  "symfony/monolog-bundle" : "~2.4",
  "sensio/distribution-bundle" : "~3.0",
  "sensio/framework-extra-bundle" : "~3.0",
  "incenteev/composer-parameter-handler" : "~2.0",
  "sensio/generator-bundle" : "~2.3",
  "mopa/bootstrap-bundle" : "dev-master",
  "twbs/bootstrap" : "dev-master",
  "knplabs/knp-paginator-bundle" : "dev-master",
  "knplabs/knp-menu-bundle" : "dev-master",
  "knplabs/knp-menu" : "2.0.*@dev",
  "craue/formflow-bundle" : "dev-master",
  "friendsofsymfony/user-bundle" : "~2.0@dev",
  "doctrine/doctrine-fixtures-bundle" : "dev-master",
  "doctrine/data-fixtures" : "dev-master",
  "liip/functional-test-bundle" : "dev-master",
  "liuggio/excelbundle" : "dev-master",
  "phpunit/phpunit" : "4.2.*",
  "symfony/framework-bundle" : ">=2.0.0",
  "aws/aws-sdk-php" : ">=2.6.2"
 },

Por ejemplo, la primera línea sobre el requerimiento de php no depende de nosotros sino de la versión instalada en el servidor. Así que vamos a la segunda, la de symfony/symfony.

Ahora mismo el proyecto funciona, así que ejecutamos lo siguiente en un terminal o línea de comandos:

$ composer show -i

Si no lo tenemos instalado globalmente entonces:

$ php composer.phar show -i

En éste caso me puede haber salido algo como lo siguiente:

aws/aws-sdk-php                      2.7.2              AWS SDK for PHP - Use Amazon Web Services in your PHP project
craue/formflow-bundle                dev-master a88ecba Multi-step forms for your Symfony2 project.
doctrine/annotations                 v1.2.1             Docblock Annotations Parser
doctrine/cache                       v1.3.1             Caching library offering an object-oriented API for many cache backends
doctrine/collections                 v1.2               Collections Abstraction library
doctrine/common                      v2.4.2             Common Library for Doctrine projects
doctrine/data-fixtures               dev-master ac36ccc Data Fixtures for all Doctrine Object Managers
doctrine/dbal                        v2.4.3             Database Abstraction Layer
doctrine/doctrine-bundle             v1.2.0             Symfony DoctrineBundle
doctrine/doctrine-fixtures-bundle    dev-master 9a5b528 Symfony DoctrineFixturesBundle
doctrine/inflector                   v1.0               Common String Manipulations with regard to casing and singular/plural rules.
doctrine/instantiator                1.0.4              A small, lightweight utility to instantiate objects in PHP without invoking their constructors
doctrine/lexer                       v1.0               Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
doctrine/orm                         v2.4.6             Object-Relational-Mapper for PHP
friendsofsymfony/user-bundle         dev-master 9714a92 Symfony FOSUserBundle
guzzle/guzzle                        v3.9.2             Guzzle is a PHP HTTP client library and framework for building RESTful web service clients
incenteev/composer-parameter-handler v2.1.0             Composer script handling your ignored parameter file
jdorn/sql-formatter                  v1.2.17            a PHP SQL highlighting library
knplabs/knp-components               1.3.1              Knplabs component library
knplabs/knp-menu                     dev-master d6f3cc4 An object oriented menu library
knplabs/knp-menu-bundle              dev-master bdfc95d This bundle provides an integration of the KnpMenu library
knplabs/knp-paginator-bundle         dev-master 701dffe Paginator bundle for Symfony2 to automate pagination and simplify sorting and other features
kriswallsmith/assetic                v1.2.0             Asset Management for PHP
liip/functional-test-bundle          dev-master eb42b04 This bundles provides additional functional test-cases for Symfony2 applications
liuggio/ExcelBundle                  dev-master 22a2a74 This is a Symfony2 Bundle helps you to read and write Excel files (including pdf, xlsx, odt), thanks to the PHPExcel library
monolog/monolog                      1.11.0             Sends your logs to files, sockets, inboxes, databases and various web services
mopa/bootstrap-bundle                dev-master a0ee4e8 Easy integration of twitters bootstrap into symfony2
mopa/composer-bridge                 v1.3.0             Symfony2 Composer Bridge
phpoffice/phpexcel                   1.8.0              PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine
phpunit/php-code-coverage            2.0.11             Library that provides collection, processing, and rendering functionality for PHP code coverage information.
phpunit/php-file-iterator            1.3.4              FilterIterator implementation that filters files based on a list of suffixes.
phpunit/php-text-template            1.2.0              Simple template engine.
phpunit/php-timer                    1.0.5              Utility class for timing
phpunit/php-token-stream             1.3.0              Wrapper around PHP's tokenizer extension.
phpunit/phpunit                      4.2.6              The PHP Unit Testing framework.
phpunit/phpunit-mock-objects         2.3.0              Mock Object library for PHPUnit
psr/log                              1.0.0              Common interface for logging libraries
sebastian/comparator                 1.0.1              Provides the functionality to compare PHP values for equality
sebastian/diff                       1.2.0              Diff implementation
sebastian/environment                1.2.0              Provides functionality to handle HHVM/PHP environments
sebastian/exporter                   1.0.2              Provides the functionality to export PHP variables for visualization
sebastian/version                    1.0.3              Library that helps with managing the version number of Git-hosted PHP projects
sensio/distribution-bundle           v3.0.8             Base bundle for Symfony Distributions
sensio/framework-extra-bundle        v3.0.2             This bundle provides a way to configure your controllers with annotations
sensio/generator-bundle              v2.4.0             This bundle generates code for you
sensiolabs/security-checker          v2.0.0             A security checker for your composer.lock
swiftmailer/swiftmailer              v5.3.0             Swiftmailer, free feature-rich PHP mailer
symfony/assetic-bundle               v2.5.0             Integrates Assetic into Symfony2
symfony/monolog-bundle               v2.6.1             Symfony MonologBundle
symfony/swiftmailer-bundle           v2.3.7             Symfony SwiftmailerBundle
symfony/symfony                      v2.5.6             The Symfony PHP framework
twbs/bootstrap                       dev-master e27dc5d The most popular front-end framework for developing responsive, mobile first projects on the web.
twig/extensions                      v1.2.0             Common additional features for Twig that do not directly belong in core
twig/twig                            v1.16.2            Twig, the flexible, fast, and secure template language for PHP

Viendo la línea de symfony/symfony vemos que tenemos instalada la versión 2.5.6. Entonces podemos ir a nuestro fichero composer.json y cambiar el 2.5.* por 2.5.6 así siempre sabremos que no se actualizará a la versión 2.5.7 y sucesivas. Evitando así esos posibles fallos.

Así sucesivamente.

Hay versiones que siguen estando a dev-master al ejecutar ‘composer show -i’. No queda otra que ir viendo uno a uno en sus webs, a ver cuál es la última versión disponible e ir probando si al fijarla continúa funcionando nuestro proyecto.

Usando composer install en vez de update

Otra opción que tenemos es usar ‘composer install’. Lo que hacemos haciendo ‘install’ en vez de ‘update’ es usar la versión exacta que tengamos en el fichero composer.lock es en vez de comprobar las versiones disponibles, descargar las últimas permitidas y así arriesgarnos a que luego deje de funcionar.

Hay que ir viendo un término intermedio entre fijar las librerías a una cierta versión con lo que te quedas sin las últimas mejoras pero ganas estabilidad, o pones las librerías a dev-master con lo que te puedes pasar mucho tiempo «acomodando» las nuevas funcionalidades de cada librería a la aplicación pero ganarás que tu aplicación tendrá todo lo nuevo.

Resumiendo, en local deberíamos siempre de ir actualizando las librerías. Usando ‘composer update’ de vez en cuando para mantener nuestro programa actualizado. Comprobamos que todo funcione y cuando estamos seguros subimos el fichero composer.lock al repositorio para hacer ‘composer install’ en el despliegue.

Además, si ejecutamos en el despliegue:

$ composer install –prefer-dist

sólo te descargará lo necesario para que tu aplicación funcione con lo que descargará bastante menos y la aplicación será más ligera.

Espero que sirva.

Un saludo 🙂


Hola mundo Silex usando Composer

2014-09-28 - Categorías: General


Estoy ahora curioseando los entresijos de PHP. Viendo hacia dónde tiende la programación en la web. Añadiendo herramientas, librerías de programación para mis proyectos.

Dentro del mundo de la programación, elegir librerías, actualizarlas, combinarlas.. todo esto lleva trabajo y en los últimos años están apareciendo herramientas que nos facilitan la vida.

Pongámonos en situación

Cuando empezamos a programar con PHP. Desde el inicio hasta hace pocos años, que casi todo se hacía a pelo. PHP puro y duro, añadiendo código y más código. Los programas iban creciendo y creciendo. Se iban añadiendo manualmente librerías para todo tipo de tareas. Ahora ya no tenemos que reinventar la rueda. Estan surgiendo librerías públicas como por ejemplo  Packagist, donde se recopilan códigos para hacer todo tipo de tareas con PHP.

Para manejo de Packagist tenemos una herramienta llamada Composer, con la que una vez configurado lo que vamos a necesitar Composer se encarga de conectar a Packagist y descargar los ficheros que haga falta. Para la programación front-end tenemos herramientas como Bower, similar a Composer, pero pensado para gestionar librerías Javascript/CSS. Estas herramientas, una vez configuradas, nos descargarán los ficheros a nuestro directorio de trabajo y listo. Ya tendremos nuestro proyecto para programar, podremos añadir o borrar librerías fácilmente, elegir versiones, actualizar todo..

Web de Composer: https://getcomposer.org/
Web de Packagist: https://packagist.org/
Web de Bower: http://bower.io/

Herramientas

Para éste post he elegido probar Silex, es un framework bien sencillo. Más bien un micro-framework como lo nombran oficialmente. Está construido a base de componentes del gran framework Symfony. Con él tendremos una serie de herramientas para usar como plantillas Twig, podremos enviar emails con Swiftmailer, si no eres un experto en seguridad (como es mi caso) no pasa nada porque tenemos el componente de seguridad Security, el componente para generar formularios Form, el generador de URLs, etcétera.

Todos los componentes

Como iba diciendo, Composer se va a encargar de gestionar por nosotros las librerías PHP de nuestro proyecto. Con el ejemplo veremos mejor cómo funciona. Prepararemos nuestro proyecto hola mundo con Silex con Composer y veremos qué sencillo que lo tenemos para aumentar luego librerías. Pasos:

  1. Descargamos el fichero composer.phar
  2. Preparamos nuestro fichero composer.json
  3. Ejecutamos Composer.
  4. Empezamos a programar.

Descargamos el fichero composer.phar

En el directorio donde vayamos a desarrollar nuestro proyecto o queramos hacer éstas pruebas debemos de poder usar Composer. Se puede instalar en el sistema, en el PATH del sistema, pero eso ya se sale del post. La forma sencilla que viene en su web oficial es poniendo el fichero composer.phar en el directorio. Si tienes curl ejecutas lo siguiente:

curl -sS https://getcomposer.org/installer | php

Si sólo tienes PHP, puedes ejecutar lo siguiente en línea de comandos y se te descargará el fichero:

php -r «readfile(‘https://getcomposer.org/installer’);» | php

O manualmente como se indica en su web: https://getcomposer.org/download/

Si quieres instalar globalmente Composer tengo un post anterior donde lo explico: https://jnjsite.com/programacion/instalar-globalmente-composer-en-ubuntu/

No lo he dicho pero supongo que ya tienes instalado PHP. Sino desde su web puedes instalarlo: http://es1.php.net/downloads.php. Si tienes Linux, en mi caso Ubuntu, basta con poner en línea de comandos ‘sudo apt-get install php5’.

Preparamos nuestro fichero composer.json

Para Silex va a ser muy sencillo. Si queremos elegir librerías no tenemos más que ir a Packagist y añadir las que necesitemos. El fichero:

{
«require» : {
«silex/silex» : «dev-master»,
«phpoffice/phpword»: «dev-master»
}
}

En el mismo directorio raiz donde tenemos el composer.phar creamos éste fichero composer.json con el contenido de aquí encima. Si nos fijamos he puesto dev-master aquí arriba, esto nos descargará siempre la última versión que haya disponible. He añadido phpoffice/phpexcel, para hacer una prueba a ver qué tal.

Ejecutamos Composer

Entonces la primera vez tenemos que ejecutar una instalación de las librerías en linea de comandos:

php composer.phar install

Veremos entonces algo parecido a lo que hay en la imagen inicial. Si estamos modificando el fichero composer.json debemos entonces ejecutar lo siguiente:

php composer.phar update

Si todo ha ido bien entonces tendremos en nuestro directorio los siguientes ficheros: composer.phar, composer.json, composer.lock y el directorio vendor. El fichero composer.lock es un fichero que se actualiza para llevar sus tareas internas cada vez que ejecutamos Composer. Luego en el directorio vendor/ tendremos todas las librerías actualizadas a la versión.

Empezamos a programar

No lo he dicho antes pero dentro del directorio de las librerías (vendor), tendremos ahora un fichero autoload.php que simplemente lo incluimos o requerimos y a programar. Tendremos acceso a todas las librerías de Composer que hayamos incluido. Con un ejemplo se verá más claro:

<?php
// index.php
ini_set('display_errors', 'On');
error_reporting(E_ALL);

require_once __DIR__.'/vendor/autoload.php';

$app = new SilexApplication();

$app->get('/', function () {
    return 'Página inicial.';
});

$app->get('/crearTexto', function () {
    $nombreArchivo = 'fichero.odt';
    
    $phpWord = new PhpOfficePhpWordPhpWord();
    
    $section = $phpWord->addSection();
    $section->addText('Hello world!');
    $section->addText('Hello world! I am formatted.',
        array('name'=>'Tahoma', 'size'=>16, 'bold'=>true));
    $phpWord->addFontStyle('myOwnStyle',
        array('name'=>'Verdana', 'size'=>14, 'color'=>'1B2232'));
    $section->addText('Hello world! I am formatted by a user defined style',
        'myOwnStyle');
    $objWriter = PhpOfficePhpWordIOFactory::createWriter($phpWord, 'ODText');
    $objWriter->save($nombreArchivo);
    
    $str = 'Fichero de Word en formato Open Document creado.';
    $str .= '<a href="' . $nombreArchivo . '">Haz click aquí para descargar</a>';
    
    return $str;
});

$app->run();

Ahora vamos a arrancar el servidor web interno de PHP para probar la pequeña aplicación. Ejecutamos en línea de comandos estando en el directorio del proyecto:

php -S localhost:8000

He usado el ejemplo del libro oficial en castellano sobre Silex, modificándolo para ésta prueba. Si quieres puedes ver toda la documentación de Silex en http://librosweb.es/silex/

Web de PHPWord: https://github.com/PHPOffice/PHPWord

Si todo ha ido bien, ejecutando http://localhost:8000/ veremos el mensaje ‘Página inicial.’. Si ejecutamos http://localhost:8000/crearTexto veremos el segundo mensaje, en donde se habrá creado el fichero de texto fichero.odt y podremos descargarlo. Si vemos nuestro directorio del proyecto ahí lo tendremos. En el ejemplo he seguido la documentación de su página oficial para crear dicho fichero.

Resumiendo

¿Qué sencillo usar las librerías para hacer algo tan complejo como un fichero compatible con Word? En casi 40 líneas de código tenemos una aplicación web que crea dicho fichero. Todo gracias a usar dos librerías de Composer y en seguida ya lo tenemos.

Recapitulando, ponemos en el fichero composer.json las librerías que vayamos a usar. Luego incluimos el fichero /vendor/autoload.php y ya tenemos todas las clases de objetos listos para usar. Te ahorra tiempo, es práctico, organizado. Si no estás ya usando los vendors deberías de hacerlo.

¡Un saludo!


Instalar globalmente Composer en Ubuntu

2014-09-08 - Categorías: General

Este post va a ser bien corto. Se trata de cómo instalar el ejecutable de Composer en un directorio de Linux que está en la variable PATH del sistema.

Los directorios que están en PATH son los directorios en donde se busca el programa cuando queremos ejecutar algo en Linux. Podemos escribir lo siguiente en línea de comandos y veremos cuáles tenemos ahora mismo:

echo $PATH

Tendremos separados por : todos los directorios actuales. Vamos entonces a añadir en uno nuestro ejecutable de Composer, que se trata de un phar. Un fichero .phar es un programa PHP empaquetado dentro de un sólo archivo.

Vamos entonces..

Descargar la última versión de composer

Si tenemos instalado curl podemos ejecutar lo siguiente en línea de comandos:

curl -sS https://getcomposer.org/installer | php

Si sólo tenemos instalado PHP ejecutamos lo siguiente:

php -r «readfile(‘https://getcomposer.org/installer’);» | php

Entonces debemos tener en el directorio en que estamos un fichero llamado composer.phar que podríamos usar con ‘php composer.phar …’. Pero queremos ir un paso más allá. Pudiendo ejecutarlo desde cualquier directorio, ¿recuerdas?

Instalamos en el sistema en PATH

Ejecutamos los siguientes comandos y ya lo tendremos instalado globalmente.

sudo mv composer.phar /usr/local/bin/composer
sudo chown root:root /usr/local/bin/composer
sudo chmod 755 /usr/local/bin/composer

Ahora podremos usarlo simplemente poniendo $ composer ..

Por ejemplo, si queremos actualizar composer debemos usar:

composer selfupdate

Siendo usuario root, si eres usuario normal debes poner:

sudo composer selfupdate

¿Sencillo verdad? Espero haberme explicado bien, si no, mensaje aquí abajo 😉


PHP 8: Creando objetos a partir de otros, herencia simple

2014-03-30 - Categorías: PHP
Objetos relacionados

Estoy con algo más de tiempo, así que jugando un poco más con la programación orientada a objetos, les escribo ahora sobre cómo crear objetos a partir de otros de la forma más simple.

Hay tres formas de crear objetos a partir de otros objetos: mediante extends, con clases abstract, y con interface.

Todo éste proceso es lo que se llama herencia, donde los objetos hijos no heredan casas ni coches ni grandes fortunas xD, si no que en éste caso heredan funciones y variables.

Empezando con la herencia simple

Vamos a suponer lo siguiente. Queremos usar en nuestro programa la información de ciertos vehículos, y dichos vehículos pueden ser motos, coches y camiones. Usando la palabra reservada extends vamos a ver con un ejemplo lo sencillo que es el siguiente:

<?php

// clase base
class vehiculo {

    public $matricula;

}

// clases que extienden la clase base
class moto extends vehiculo {

    public $velocidadMaxima;

}

class coche extends vehiculo {

    public $tipo;

}

class camion extends vehiculo {

    public $numeroRuedas;

}

// creamos 3 vehiculos
$micoche = new coche();
$mimoto = new moto();
$micamion = new camion();

// tanto la moto como el coche y el camión tienen matrícula
// porque heredan la variable matrícula
$micoche->matricula = "ABC123";
$micoche->tipo = "utilitario";
$mimoto->matricula = "DEF123";
$mimoto->velocidadMaxima = 300;
$micamion->matricula = "GHI123";
$micamion->numeroRuedas = 12;

echo "El vehículo con matrícula " . $micamion->matricula . " tiene " . $micamion->numeroRuedas . " ruedas.";

Si ejecutamos el código fuente nos saldrá: «El vehículo con matrícula GHI123 tiene 12 ruedas.»

Lo importante o nuevo en éste capítulo de la serie es la palabra reservada extends con la que se crean las nuevas clases a partir de otras. Aquí arriba tenemos cuatro clases, la clase base que tiene sólo una variable para guardar la matrícula.

Por otro lado tenemos las clases coche, moto y camión que se crean a partir de la clase base vehículo. Y por lo tanto heredan la variable para guardar la matrícula. Podemos ver que en las últimas líneas del ejemplo, tanto $micoche, como $mimoto y $micamion tienen la variable $matricula aunque no se ha declarado en dichas clases pero sí en la clase base.

Imaginemos entonces si quisiéramos podríamos crear a su vez subtipos de camiones que extendieran de la clase camión, añadiendo así más variables y funciones. Así sucesivamente.

Terminando

Para terminar, podemos de paso ver qué pasa cuando modificamos la clase base y cambiamos la variable de la matrícula a protected:

// clase base
class vehiculo {
    protected $matricula;
}

Ahora poniendo matricula protegido nos saldrá un error, porque protegiendo dicha variable lo que hacemos es que no se herede en las clases hijas. Es decir, en el caso del camión, protegiendo la matrícula, dicho camión ahora sólo tendrá una variable llamada $numeroRuedas porque no ha heredado la variable $matricula.

Hay otros tipos de como el que acabamos de ver protected, también tenemos abstract y static, pero eso es para otro post.

De nuevo me remito a la documentación oficial: http://www.php.net/manual/es/

Si te ha gustado, te ha parecido interesante, te ha aclarado alguna cosa, quieres recomendarlo, decirme que siga escribiendo, darme ánimos, dale un click a +1, comparte, facebootealo, tuitea, tuentiea o deja un mensaje aquí abajo que es gratis 😉

Un saludo!


PHP 7: Empezando con clases y objetos

2014-03-29 - Categorías: PHP
Objetos relacionados

A partir de ahora vamos a entrar en la programación orientada a objetos, POO para los amigos. En las técnicas de programación utilizar objetos es básico para poder hacer programas bien estructurados que se puedan reutilizar o  modificar.

En programación, una clase es un objeto, y un objeto es una clase. Éstas clases de PHP tienen unas variables y unos métodos o funciones.

Dicho ésto vamos al grano.

Una primera clase básica

A continuación tenemos un objeto, que es una clase de PHP. Éste objeto representa un avión que está en unas coordenadas, tiene un tipo de motor, cierta matrícula, y está en un estado determinado. Por ejemplo:

<?php

// se declara el objeto
class Avion {

    // variables
    public $x, $y, $z;
    private $tipoMotor;
    public $matricula;
    private $estado;

    // a partir de aquí estan las funciones
    // constructor de la clase
    function __construct() {
        $this->x = $this->y = $this->z = 0;
        $this->tipoMotor = 1;
        $this->matricula = "No definida";
        $this->estado = "No definido";
    }

    public function getTipo() {
        switch ($tipoMotor) {
            case 1: $descripcion = "Hélices";
                break;
            case 2: $descripcion = "Propulsión a chorro";
                break;
            default:
                $descripcion = "Tipo no encontrado";
        }
        return $descripcion;
    }

    public function getEstado() {
        return "El avión con matrícula >" . $this->matricula . "< está: " . $this->estado;
    }

    public function despegar() {
        $this->estado = "Volando";
    }

    public function aterrizar() {
        $this->estado = "Esperando";
    }

    public function setMatricula($m) {
        $this->matricula = $m;
    }

    // destructor de la clase
    function __destruct() {
        echo "El avión con matrícula >" . $this->matricula . "< se ha destruido.";
    }

}

// fin del objeto
// a partir de aquí éste código usa el objeto avión
$miavion = new Avion();
$miavion->setMatricula("ABC123");
$miavion->despegar();
dispose($miavion);
echo "Mi avión está en (" . $miavion->x . ", " . $miavion->y . ", " . $miavion->z . ")<br>";
echo $miavion->getEstado() . "<br>";

Para empezar los objetos se crean usando la palabra reservada class. Tenemos dos partes principales dentro de la clase. La primera parte donde se declaran las variables, y después las funciones para ejecutar los algoritmos de programación.

Tenemos $this, que es especial. ¡Ojo! ésta variable es muy importante. Representa la clase, se usa dentro de dicha clase, y con ella accederemos a las variables y funciones cuando desarrollemos dicha clase. Con el símbolo -> después del $this accederemos así: $this->variable

De igual manera con el objeto $miavion->nombreDeVariable o $miavion->nombreDeFuncion() fuera de la clase.

Los tipos básicos de variables y funciones son public y private. Los private, como su nombre indica, son privados y sólo se pueden usar dentro de la clase. Los públicos se pueden usar desde fuera. Si vemos abajo del todo en la línea 62 de código se usa $miavion->x porque x es una variable pública. No podremos por ejemplo acceder a la variable estado de la forma $miavion->estado porque es privada. Lo mismo pasa con la variable $tipoMotor, no podemos acceder directamente, por ello se declara la función getTipo() que lo hace por nosotros y nos dice el tipo de motor que tiene el avión.

Constructor y destructor de una clase

El resto de funciones no tiene misterio, salvo dos especiales, el constructor y destructor de la clase. Si ejecutamos el ejemplo nos mostrará lo siguiente:

Mi avión está en (0, 0, 0)
El avión con matrícula >ABC123< está: Volando
El avión con matrícula >ABC123< se ha destruido.

El constructor empieza en la línea 14 y el destructor en la 50. Éstas dos funciones se ejecutan automáticamente cuando se crea un objeto avión y cuando se destruye respectivamente. Se suelen usar para hacer tareas de inicialización en el caso del constructor o para terminar procesos con el destructor. En el ejemplo el constructor simplemente le da unos valores a las variables de la clase y el destructor simplemente muestra un mensaje. Al ejecutarlo el ejemplo podemos ver que cuando termina el código PHP se llama automáticamente al destructor.

Terminando

Hay otros tipos de variables y funciones. También se pueden crear varios constructores.  Y muchas otras cosas más. Para terminar con éste minitutorial para empezar con los objetos de PHP me remito una vez más a la documentación oficial: http://www.php.net/manual/es/language.oop5.php

Si te ha gustado, te ha parecido útil o quieres que siga escribiendo dale aquí abajo al +1, al me gusta, a compartir, etcétera.. también puede dejar un comentario 😉

Un saludo!


PHP 3: Condicionales

2014-03-14 - Categorías: PHP

Siguiendo con la serie de minitutoriales, hoy les voy a dejar uno sobre los condicionales en PHP. Estamos viendo lo que se llaman estructuras de control, y dentro de ellas pretendo dividirlas y ver aquí sólo los condicionales if y switch.

Si buscas un tutorial breve y concreto, sin rodeos y sin entrar en detalles, aquí lo tienes.

A saber..

If

Los condicionales comprueban si se cumple una condición, como su nombre indica, y en ése caso ejecutan el código programado en él. Por ejemplo:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <p>
            <?php
   $variable = 2;
   if ($variable == 2) {
    echo "La variable vale dos.";
   } else {
    echo "La variable no vale dos.";
   }
            ?>
        </p>
    </body>
</html>

El código habla por sí sólo. Tenemos una web que utiliza el estándar HTML5 como indica su DOCTYPE, y en el cuerpo de la página tenemos incrustado código PHP. Simplemente mira si la variable vale 2 y como así es imprimirá ‘La variable vale dos.» en la web. Si no valiera dos entonces ejecutaría lo que hayas puesto en el else.

El meollo de los condiciones está en la expresión de comparación ($variable == 2). Tenemos disponibles varios operadores de comparadores, por ejemplo: ==, <=, >=, !=
Están todos muy bien explicados aquí http://www.php.net/manual/es/language.operators.comparison.php

Switch

Por otro lado tenemos el switch. Se usa para encontrar valores concretos. Un ejemplo para vero claro podría ser:

$otravariable = "patata";
switch ($otravariable) {
  case "patata" :
    echo "Es una patata.";
    break;
  case "boniato" :
    echo "Es un boniato.";
    break;
  case "cebolla" :
    echo "Es una cebolla.";
    break;
  default :
    echo "¡Alguien me ha cambiado el valor de la variable!";
    break;
}

Podriamos haber usado varios condicionales de tipo if para ir comprobando $otravariable. Pero cuando tenemos más de dos posibles valores es mejor usar la estructura del switch. Fíjate que dentro de cada caso hay un break para indicar que ahí termina el caso. Si no los ponemos estaremos uniendo varios casos.

Es importante notar también el caso especial default que se ejecutará sólo cuando no encuentre el valor.

De nuevo para terminar me remito a la documentación oficial http://www.php.net/manual/es/index.php, aquí tiene en la sección de Estructuras de control los condicionales y muchísima más información.

Si te ha sido útil o te ha gustado dale click al +1, compartelo.. también puede dejar un comentario.

¡Un saludo!


PHP siguiente nivel (editado)

2013-12-30 - Categorías: General
Pensaba que ya tenia un buen nivel de conocimiento en la programación PHP. Estaba a mitad de camino entre programar webs desde cero, o usar CMSs y adaptarlos. Indagando, indagando, y bien aconsejado me decidí por aprender a fondo sobre el tema, cómo estan hechos los CMSs, y sobre los frameworks de desarrollo. La idea que tenia en mente era no reinventar la rueda una y otra vez con cada proyecto. Ahora resulta que como dice el dicho, cuanto más aprendo, más veo que me falta por aprender. Es chistoso pero así es, a veces me siento contento de haber aprendido, pero miro hacia adelante, no quiero quedarme atrás, y pienso sobre todo lo que quisiera saber.
Por todo ésto les quiero compartir una serie de enlaces a alimento académico y herramientas. Para todos aquellos que estamos en constante reciclaje, que creo la mayoría de los programadores..

Al grano, los recursos:
  • PHP -> Imprescindible, manuales oficiales sobre el lenguaje de programación PHP.
  • LIBROSWEB -> Muchos libros gratis para la comunidad de programadores. Son libros avanzados sobre XHTML, Symfony, Silex, Composer, Git, CSS y más.
  • Composer -> Es una herramienta con la que podemos instalar de forma automática todo tipo de librerías para aplicaciones PHP. Es un manejador de dependencias. Resumiendo, eliges las herramientas, ejecutas el comando de composer y te instala todas las librerías. Por ejemplo, Symfony y Silex se pueden instalar o actualizar usando Composer.
  • Packagist -> Es el repositorio principal de paquetes para Composer. Podemos consultarlo que probablemente encontremos herramientas que nos agilicen los desarrollos.
  • Symfony -> Framework de desarrollo con una comunidad de habla hispana muy grande, podemos encontrar mucha documentación en Internet.
  • Silex -> Microframework basado en algunos componentes que usa Symfony. Muy sencillo para empezar y potente cada vez más cuando vamos aprendiendo a usar sus componentes.
  • Aptana Studio -> Un entorno de desarrollo basado en Eclipse que seguro que a mas de uno le gustará si lo prueba. Tiene de todo para el desarrollo web desde el punto de vista de un programador. Editor de código bueno, autoformateo de código, autocompletado, resaltado de sintáxis. Y es gratis.
  • PHP, la manera correcta -> Proyecto de documentación sobre buenas prácticas a la hora de programar.
Gracias a Albert por gran parte de la información.
Un saludos y ¡que aproveche!

Joomla carga muy lento con Windows 7

2013-08-27 - Categorías: General
Desarrollando y construyendo una web en Joomla, me encuentro con el problema de que mi instalación en una máquina de trabajo con Windows 7 y Uniform Server tarda mucho en servir las páginas. Llega a tardar del orden de 2 a 10 segundos para cada página.
Después de haber encontrado mi solución y la de muchos otros en situaciones parecidas les expongo aquí las soluciones que encontré y fui probando.
Ésto lo he aplicado a una instalación de un servidor Apache con PHP y MySQL corriendo un Joomla 2.5. Debería de funcionar también si tienes el problema con otros CMS como Drupal o WordPress.

1. Desactivar antivirus o excluir el directorio de instalación del servidor.
2. Desactivar el firewall o permitir el acceso directo de los programas del servidor Apache, PHP y MySQL.
3. Desactivar IPv6 de la máquina.
4. Acceder a la máquina local mediante 127.0.0.1 en vez de poniendo localhost. También se puede modificar el archivo de hosts para ésto. 
5. Editar el fichero php.ini
6. Editar http.conf de Apache para escuchar con IPv4.
7. Si tienes XAMPP instalar Uniform Server, o viceversa. O actualizar a una nueva versión.
8. Poner PHP como módulo de Apache, en vez de como CGI.
9. Desactivar Xdebug.
La solución que me funcionó fué la última, tal vez las otras ayudaron pero la que se notó fué al desactivar el Xdebug de PHP.
Lo dejo aquí por si alguien le sirve.
Saludos.

Joomla! Platform 4: Autocarga de clases con JLoader

2013-07-07 - Categorías: PHP
Empieza el domingo interesante. Un poco desesperado porque el framework de Joomla no tiene mucha documentación para usarlo independientemente del CMS pero ahí seguiré atento a los cambios, que seguro que algún día se convertirá en un framework de la categoría de Symfony o Codelgniter, por ejemplo. Con 2700 desarrolladores en todo el mundo, más de 6000 extensiones, con un 2,7% de portales en Internet que usan Joomla! ésto promete.
A fecha de hoy leo en los foros de grupos de desarrollo que con la separación de Joomla Platform del desarrollo del CMS se están cambiando los espacios de nombres y añadiendo más y más funcionalidades. Una de éstas últimas es el nuevo método para autocargar clases.

Con un ejemplo se ve bien sencillo:

<?php

// se importa la Plataforma Joomla.
require_once 'libraries/import.php';

// directorio base que se usa para el autocargador de clases
const JPATH_BASE = __DIR__;

// registra un prefijo cualquiera
JLoader::registerPrefix('Jnj', JPATH_BASE);

// crea una instancia de la clase nueva
$app = JApplicationWeb::getInstance("JnjUnDirectorioEjemploWeb");

// se supone que debemos guardar la applicación en el core
JFactory::$application = $app;

// y entonces podemos ejecutarla desde el core
JFactory::getApplication()->execute();

// así queda todo ordenado en el núcleo del framework

Éste podría ser un index.php del directorio principal. Con JLoader se establece un prefijo para las clases, en éste caso, ‘Jnj’. Ahora al cargar la instancia de la clase ‘JnjUnDirectorioEjemploWeb’, la clase se buscará en el directorio ‘/Un/Directorio/Ejemplo/web.php’ y la clase se deberá llamar ‘JnjUnDirectorioEjemploWeb’. Aquí abajo tienen la clase web para ejecutarlo:
<?php
class JnjUnDirectorioEjemploWeb extends JApplicationWeb {
    protected function doExecute()
    {
        $this->setBody("");
        $this->appendBody("<h1>Plataforma Joomla! Autocarga de clases con JLoader</h1>");
        $this->appendBody("Si estas viendo ésto en el navegador es que está bien la autocarga...");
    }
}
Los dos ejemplos y la estructura de directorios se pueden descargar aquí.
Para que funcione debemos tener el framework descomprimido en el mismo directorio raiz de la web, y ejecutarlo desde un navegador, como viene siendo costumbre en éstos minitutoriales.
Ahora eligiendo un prefijo, y organizando las clases del proyecto en directorios podemos ‘autocargar’ todo fácilmente sin tanto import o require. De manera dinámica el framework se encargará de todo.
Espero que sirva. Saludos.

Joomla! Platform 3: Bases de datos

2013-06-25 - Categorías: PHP
Ya llegó el calor, terminaron los exámenes de junio y parece que ya llega el verano, aunque hace poco aún estaba lloviendo en la costa mediterránea de España. Yo sigo con lo mío y hoy les traigo un acceso a bases de datos usando SQLite, una base de datos de dominio público, embebida, no necesita instalación y simplemente con que PHP tenga activada la extensión para ello ya podemos usarla.
Como viene siendo costumbre de la serie que escribo, me voy a centrar en el uso en un servidor web, con lo que el ejemplo es una página web. Se puede también ejecutar en línea de comando pero yo no uso PHP de esa forma así que… vamos al grano.


Materiales

  1. El framework Joomla Platform. Yo he usado la versión 12.3, la oficial veo que es la 12.1 y está en desarrollo la 13.1. Aquí hay más información para los curiosos como yo.
  2. Un entorno de desarrollo. Aquí ya para gustos estan los colores, Eclipse va muy bien y es multiplataforma.
  3. Un servidor para ejecutarlo. XAMPP funciona muy bien y también es multiplataforma.

Empezando

Descomprimo el framework en el directorio si es que no lo tengo ya, entonces hay que crear un fichero .php como en el ejemplo siguiente y veremos rápidamente como funciona:
...
<?php

// se importa la Plataforma Joomla.
require_once 'libraries/import.php';

class laWeb extends JApplicationWeb {
protected function doExecute()
{
$this->setBody("<html><body>");
$this->appendBody("<h1>Plataforma Joomla! SQLite</h1>");

// vector de configuración de la base de datos
// para sqlite sólo hace falta ésto
$option = array();
$option['driver'] = 'sqlite'; // tipo de bd
//$option['host'] = 'direccionhost.com'; // host
//$option['user'] = 'usuario'; //
//$option['password'] = 'contraseña'; //
$option['database'] = 'bdpruebas.sqlite'; // nombre de la bd
//$option['prefix'] = 'pref_'; // prefijo
$dbo = JDatabaseDriver::getInstance($option);

// se puede abreviar usando:
//$dbo = JDatabaseDriver::getInstance(array('driver' => 'sqlite', 'database' => 'bdpruebas.sqlite'));


// crea una tabla para las pruebas, la borra si existe
$dbo->setQuery("DROP TABLE IF EXISTS aleatorios");
$dbo->execute();
$dbo->setQuery('CREATE TABLE IF NOT EXISTS aleatorios (id INTEGER PRIMARY KEY, numero INTEGER)');
$dbo->execute();
$this->appendBody("<p>Creada la base de datos...</p>");

// inserta unos datos en la BD
for ($i = 0; $i < 10; $i++) {
$dbo->setQuery('INSERT INTO aleatorios (id, numero) VALUES (' . $i . ', ' . rand(0,99) . ');');
$dbo->execute();
}
$this->appendBody("<p>Insertados datos...</p>");

// lee la tabla
$this->appendBody("Listando con loadRowList ");
$dbo->setQuery("SELECT * FROM aleatorios;");
$resultados = $dbo->loadRowList();
foreach ($resultados as $res){
$this->appendBody( "<p>" . $res[0] . ", " . $res[1] . "</p>");
}

// lee la tabla de otra forma
$this->appendBody("Listando con loadAssocList ");
$dbo->setQuery("SELECT * FROM aleatorios;");
$resultados = $dbo->loadAssocList();
foreach ($resultados as $res){
$this->appendBody( "<p>" . $res['id'] . ", " . $res['numero'] . "</p>");
}

// lee de nuevo la tabla de otra forma
$this->appendBody("Listando con loadObjectList ");
$dbo->setQuery("SELECT * FROM aleatorios;");
$resultados = $dbo->loadObjectList();
foreach ($resultados as $res){
$this->appendBody( "<p>" . $res->id . ", " . $res->numero . "</p>");
}

$this->appendBody("</body></html>");

}
}

JApplicationWeb::getInstance("laWeb")->execute();
...

El ejemplo en descarga está aquí.

El resultado de ejecutarlo desde el navegador debe ser algo tal que así:

Plataforma Joomla! SQLite

Creada la base de datos…
Insertados datos…
Listando con loadRowList
0, 55
1, 4
2, 1
3, 76
4, 95
5, 62
6, 63
7, 16
8, 52
9, 73
Listando con loadAssocList
0, 55
1, 4
2, 1
3, 76
4, 95
5, 62
6, 63
7, 16
8, 52
9, 73
Listando con loadObjectList
0, 55
1, 4
2, 1
3, 76
4, 95
5, 62
6, 63
7, 16
8, 52
9, 73

Explicaciones

Lo que hace el programa es crear el archivo bdpruebas.sqlite si no existe, es un comportamiento peculiar de SQLite. Si usásemos otra base de datos como PostgreSQL o MySQL podríamos usar las sentencias SQL propias de cada base de datos. El código usa las funciones para cargar tablas de datos loadRowList, loadAssocList y loadObjectList. Ésto nos devuelve un objeto que almacenará los datos de la base de datos en nuestro programa. Hay otras funciones para cuando la consulta devuelve sólo una fila o sólo un dato por ejemplo.
Hasta aquí todo bien, pero entonces ¿porqué usar el framework y no usar directamente PHP como siempre sin framework?
Ahora a partir de las últimas versiones se han añadido más bases de datos aparte de la original MySQL, tenemos PostgreSQL, SQLServer, y se preveen el acceso a muchas otras. Ahora bien, tenemos una capa extra que nos va a independizar totalmente de la base de datos, siempre que no usemos las tradicionales sentencias SQL, como en el ejemplo anterior, y construyamos nuestra aplicación usando ésta herramienta. Puede que en un futuro querramos cambiar de base de datos o hacer posible que dicho cambio sea dinámico según configuración del usuario.

Independizando de la base de datos

La historia está en crear la query usando el framework. Tenemos la clase JDatabaseDriver que es la que nos provee de la conexión a la base de datos. Ahora sí, contruimos la $query usando la clase JDatabaseQuery y entonces obtendremos esa compatibilidad de las consultas, inserciones, actualizaciones y borrados de nuestro programa con varios tipos de bases de datos. He simplificado el ejemplo anterior:
...
<?php

// se importa la Plataforma Joomla.
require_once 'libraries/import.php';

class laWeb extends JApplicationWeb {
protected function doExecute()
{
$this->setBody("<html><body>");
$this->appendBody("<h1>Plataforma Joomla! Independizando de la base de datos</h1>");

//
$dbo = JDatabaseDriver::getInstance(array('driver' => 'sqlite', 'database' => 'bdpruebas.sqlite'));

// borrando los datos de la tabla aleatorios
// para tenerla limpia
$query = $dbo->getQuery(true);
$query->delete('aleatorios');
$dbo->setQuery($query);
$dbo->execute();

$this->appendBody("<p>Borrados los datos...</p>");

// inserta unos datos en la BD
for ($i = 0; $i < 10; $i++) {
// crear una nueva consulta
$query = $dbo->getQuery(true);

// se construye
$query->insert('aleatorios')
->columns('id, numero')
->values($i . ', ' . rand(0,99));

// se establece
$dbo->setQuery($query);

// se ejecuta
$dbo->execute();
}
$this->appendBody("<p>Insertados datos...</p>");

// consultado datos
$query->select('*')->from('aleatorios');
$dbo->setQuery($query);
$resultados = $dbo->loadObjectList();
foreach ($resultados as $res){
$this->appendBody( '<p>' . $res->id . ', ' . $res->numero . '</p>');
}

$this->appendBody("</body></html>");

}
}

JApplicationWeb::getInstance("laWeb")->execute();
...

Éste segundo ejemplo está en descarga aquí.

Utilizando la base de datos creada con el primer ejemplo, si guardamos éste segundo en otro fichero .php y lo ejecutamos en el navegador funcionará de forma similar. Ahora, si tenemos la base de datos distinta de SQLite, simplemente con cambiar la línea:
$dbo = JDatabaseDriver::getInstance(array('driver' => 'sqlite', 'database' => 'bdpruebas.sqlite'));
Haciendo la conexión con la base de datos que tengamos, nos aseguramos que el resto de consultas, inserciones, actualizaciones o borrados van a funcionar.

Terminando

Otra vez más, me remito para más información a la documentación oficial:

http://docs.joomla.org/J3.1:Accessing_the_database_using_JDatabasehttp://api.joomla.org/Joomla-Platform/Database/JDatabaseQuery.html#insert

Éste post al final se ha hecho bien largo, ¡otro testamento! xDD Es que las bases de datos lo merecen ¿no cree?

Espero que sirva.
Saludos.

© 2023 JnjSite.com - MIT license

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