Categoría: Herramientas

En estos artículos aprenderás sobre herramientas que harán más eficiente tu trabajo

  • Por qué PHP 8 no satisface el requisito ^7.3 de composer

    Por qué PHP 8 no satisface el requisito ^7.3 de composer

    He ejecutado el comando:

    composer install

    En un ordenador de desarrollo recién instalado. Me ha arrojado un error:

    Root composer.json requires php ^7.3 but your php version (8.1.0) does not satisfy that requirement

    ¿No se supone que si tengo la versión 8 de PHP ese requisito debería no dar un error?

    Me crucé con esta consulta recientemente y me pareció interesante responderla ya que, probablemente, quien la realizó no sea el único con este problema u otro similar.

    Claramente la respuesta a la pregunta ¿No se supone que si tengo la versión 8 de PHP ese requisito debería no dar un error? es un No rotundo. Vamos que si no fuese así la pregunta ni existiría pero bueno… tomémosla como una pregunta retórica y un buen disparador para este post.

    Es más, cambiaré la pregunta por la que puse como título: ¿Por qué PHP 8 no satisface el requisito ^7.3 de composer?

    Para responder a esta pregunta es necesario comprender qué significa el ^7.3 que está en el archivo composer.jon.

    Antes de meterme en los detalles es importante saber que, en este contexto, esa expresión corresponde a un requisito de versión (Version constraint).

    Cómo funcionan los version constraints de composer

    Una de las características más potentes (¡y más confusas!) de composer es esta: la especificación de las versiones aceptables de cada dependencia.

    Precisamente, el motivo de la creación de composer fue el poder evitar tener la versión X.Y.Z de una librería en desarrollo y luego, en producción encontrarte con la versión X+1.Y.Z y que todo explote misteriosamente.

    La premisa en la que se basa composer es que las dependencias definen sus versiones utilizando versionado semántico. Si este supuesto no se cumple… difícilmente composer pueda hacer bien su trabajo.

    Ahora, el modo de expresar estos requerimientos requiere prestarle bastante atención a los detalles.

    Para comenzar, existen diferentes tipos de restricción.

    La más simple de ellas es la exacta. Este tipo de restricción debe usarse cuando sabés exactamente qué versión de una dependencia querés usar. En este ejemplo sería algo como 7.3.33.

    En un caso como este no hay dudas, el trabajo de composer es bien sencillo: descargar los archivos correspondientes al tag 7.3.33 del repo de php.

    Un caso de uso más realista es aquel en que no sabés el número exacto de la versión si no un aproximado.

    Sería algo como decir quiero usar una versión de php 7.3, el patch no me interesa demasiado.

    Esto sería similar a decir quiero una versión que se encuentre entre la 7.3.0 y la última antes de la 7.4.0.

    Para expresar dicha restricción podés usar la combinación de dos restricciones:

    • >=7.3
    • <7.4

    En el caso de composer, estas se combinan usando un simple espacio en blanco, que se interpreta como un &:

    >=7.3 <7.4

    Un detalle importante: composer siempre responderá con la versión más reciente que pueda, es decir, aquella que satisfaga todas las restricciones solicitadas.

    Qué significan los símbolos de las versiones en composer

    Ahora sí, vamos a ir un poco más abajo y veamos cada uno de los símbolos que se pueden usar para especificar versiones.

    >= y < se explican por si mismos creo.

    Pero también existen ~, ^ y *… la cosa se complica, ¿no?

    Intentaré ir del más simple al más complejo.

    El * es un viejo conocido de quienes estamos en software. ¿Tal vez te suene de las experesiones regulares? Pues sí, es el comodín.

    Esto significa que una restricción de tipo 7.3.* se interpreta como cualquier patch de la rama 7.3. Es decir, composer mapeará este valor al tag más reciente que encuentre en dicha rama.

    El ~ es una forma abreviada de escribir una restricción tipo >= <.

    El ^ se comporta casi como ~ pero es algo más estricto.

    La idea tanto de ~ como de ^ es evitar problemas de incompatibilidad hacia atrás.

    Esta es la clave para comprender el problema que está experimentando el protagonista de esta historia:

    La restricción ^7.3 marca, ante todo, que el major de la versión de php a utilizar debe ser 7, el minor podría cambiar (y, por supuesto, también el patch).

    Esto se debe a que, en cualquier salto de versión major es posible encontrar cambios que rompan compatibilidad hacia atrás.

    Es por eso que composer no permitirá que inadevertidamente introduzcamos un cambio de infraestructura que ponga nuestro sistema en riesgo.

    El sistema de restricciones de versiones es bastante complejo, si quieres conocer más sobre él te recomiendo ir directo a la fuente.

    De hecho, si te encuentras en una situación como la que dio origen a este post, te recomiendo abrir ese documento en una ventana y tu composer.json en otra.

  • Cómo usar PHPUnit

    Cómo usar PHPUnit

    Te decidiste. Llegó la hora de incorporar el testing automatizado a tus proyectos.

    Permitime que te felicite, es un gran paso hacia la generación de software de calidad superior.

    Después de hacer la debida investigación hay pocas dudas: PHPUnit es la herramienta que debes conocer.

    Para hacerte un poco más sencillo el camino, te dejo los lineamientos para dar tus primeros pasos.

    Cómo instalar PHPUnit

    Lo primero, como de costumbre, será instalar la herramienta.

    Si vas al sitio de phpunit.de encontrarás algo como:

    Nada mal para tener a mano pero, para empezar de cero… puede ser algo intimidante.

    Antes de continuar, una advertencia: las versiones de phpUnit están bastante ligadas a las de php. Esto significa que, para asegurarte de elegir una versión que puedas utilizar, debes saber qué versión de php tienes instalada (O piensas instalar, por ejemplo, si utilizarás docker).

    Seleccionar la versión de phpunit

    Asumamos que, para arrancar, usarás el php instalado en tu propio host.

    Un simple php -v alcanzará:

    Pues bien, en este caso, la versión que más conviene utilizar es la 11, como puede verse aquí:

    El siguiente paso: instalar la librería.

    Instalar phpunit mediante el phar

    La primera opción disponible es descargar el paquete cerrado (el archivo .phar) de aquí y hacerlo ejecutable.

    Esta opción puede resultar útil si quieres dejar una única versión de phpunit disponible para todos tus proyectos.

    Instalar phpunit usando composer

    Por lejos, la forma que recomiendo (y utilizo) es hacerlo a través de composer.

    De esta forma, la dependencia quedará circunscripta al proyecto en el que estás trabajando en este momento, a la vez que puedes compartir esta configuración con el resto de tu equipo.

    Usa composer require --dev phpunit/phpunit ^11 y composer se encargará de todo.

    Para verificar tu instalación usa el comando ./vendor/bin/phpunit --version

    Si ves algo como

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.

    Estás listo para avanzar.

    Cómo escribir un test con PHPUnit

    Escribir un test de PHPUnit supone crear una nueva clase que extienda de TestCase:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
    }

    Para ejecutar tus tests puedes utilizar el comando ./vendor/bin/phpunit MyTest.php y obtendrás una salida como:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    There was 1 PHPUnit test runner warning:
    
    1) No tests found in class "MyTest".
    
    No tests executed!

    Bastante razonable ¿no? Al fin y al cabo, se ha definido un TestCase pero ningún test dentro. Corrijamos eso.

    Por convención, PHPUnit entenderá que cualquier método de una clase TestCase cuyo nombre comience por test debe ser ejecutado por él, es decir, lo próximo que deberías hacer es agregar un método como este:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
       public function testSomething(): void
       {
       }
    }

    Al ejecutar este test verás algo como:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    R                                                                   1 / 1 (100%)
    
    Time: 00:00.003, Memory: 8.00 MB
    
    There was 1 risky test:
    
    1) MyTest::testSomething
    This test did not perform any assertions
    
    /home/mauro/phpunit-poc/MyTest.php:6
    
    OK, but there were issues!
    Tests: 1, Assertions: 0, Risky: 1.

    Es decir, se ha ejecutado un test, sólo que este test no ha verificado nada, es decir, no hay realizado ningún assertion.

    Para que efectivamente aporte algo de información, dentro del cuerpo del test debe utilizarse alguno de los métodos assert* que provee phpUnit, por ejemplo:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
       public function testSomething(): void
       {
          $this->assertTrue(true);
       }
    }

    Y ahora sí, llegarás a una salida algo más parecida a lo deseable:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    .                                                                   1 / 1 (100%)
    
    Time: 00:00.003, Memory: 8.00 MB
    
    OK (1 test, 1 assertion)

    Por supuesto que este test en la realidad no aporta mucho. Depende de vos hacer tests que sean relevantes para tu aplicación.

    Ejemplo de un test con PHPUnit

    Veamos un ejemplo algo más realista de lo que se puede hacer con phpUnit.

    Imaginemos que tienes una función que, recibe una lista de números de teléfono y una lista de prefijos y retorna aquellos que comienzan por alguno de los prefijos indicados por el segundo argumento.

    Un test para dicha función podría ser este:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
            public function testPhoneFilter(): void
            {
                    $filteredPhoneNumbers = filter_phone_numbers(
                            [ '(34) 665-55-22-112', '34 992 11 22 33', '+54 9 11 5494 2211', '054 121 123123' ],
                            [ '34', '054' ],
                    );
    
                    $this->assertEquals( [ '34 992 11 22 33', '054 121 123123'] , $filteredPhoneNumbers );
            }
    }

    Por dónde continuar aprendiendo

    PHPUnit ofrece una cantidad de posibilidades realmente interesante pero, para no marearte con todo al comienzo te recomiendo continuar aprendiendo sobre dobles de test y sobre la configuración de phpUnit.

    Ya habrá tiempo para lo demás.

  • Cuál es el mejor framework PHP para hacer APIs REST

    Te encomendaron hacer una nueva API REST para tu aplicación PHP.

    Podrías hacer en PHP puro, claro que sí pero tendrías que ponerte a tratar con una cantidad de cosas bastante molestas, entre ellas:

    • El ruteo
    • Los diferentes métodos HTTP
    • La autenticación/autorización
    • El caché
    • La documentación

    Qué pereza, ¿no? Al fin de cuentas… una API es una API… tiene que haber alguna herramienta que simplifique esto.

    Claro que la hay.

    Hace unos años te habría recomendado sin pensarlo dos veces usar Tonic (De hecho, yo mismo lo usé para armar un proyecto bastante lindo de procesamiento de imágenes).

    Pero… como todo en esta vida, la tecnología evoluciona y, para qué usar una herramienta menos potente cuando podríamos usar una más moderna, ¿no?.

    Salvo que tengas muchas ganas de aprender algunas cosas y prefieras hacerlo todo a pulmón como hice yo en este repositorio (Bueno, en rigor de verdad no empecé de 0, usé unas cuantas librerías de Symfony y alguna otra cosita como Swagger-PHP para generar la documentación de OpenAPI pero aún así… le puse algunas horas de coding).

    Ahora bien, si lo tuyo es ir directo a lo rápido y seguro no hay muchas dudas: API-Platform es lo que buscás.

    Se trata de un framework basado en lo mejorcito que hay ahí afuera, se trata de definir tus modelos y… no mucho más.

    Una API crocante en 10′.

    Vamos por pasos:

    Instalar el framework

    Bueno… un pequeño pasito antes es instalar Docker y docker-compose, pero seguramente ya lo hiciste.

    Vamos al framework

    gh repo create --clone --template api-platform/api-platform my-api --public
    cd my-api
    
    docker compose up -d --wait
    open https://localhost

    No te preocupes si ve un aviso algo tenebroso como este:

    Nadie te está por robar todo el dinero de tu cuenta, simplemente se trata de que estás usando https para entrar a tu propio localhost y el browser no acepta el certificado autofirmado.

    Si querés seguir podés hacerlo con tranquilidad. Si no, podés buscar ayuda para configurar certificados auto-firmados.

    En cualquier caso, deberías terminar viendo algo como:

    No está tan mal para haber requerido 4 comandos, ¿no?

    Definir tus recursos

    Para empezar con algo simple, definí tus entidades dentro del directorio api/src/Entity. Por ejemplo, esta es una de las que yo usé en mi proyecto:

    <?php
    
    namespace App\Entity;
    
    use App\Repository\FactionRepository;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;
    use Doctrine\DBAL\Types\Types;
    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity(repositoryClass: FactionRepository::class)]
    #[ORM\Table(name: '`factions`')]
    class Faction
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(length: 128)]
        private ?string $faction_name = null;
    
        #[ORM\Column(type: Types::TEXT)]
        private ?string $description = null;
    
        #[ORM\OneToMany(targetEntity: Character::class, mappedBy: 'faction')]
        private Collection $characters;
    
        public function __construct(string $faction_name, string $description, ?int $id = null)
        {
            $this->id = $id;
            $this->faction_name = $faction_name;
            $this->description = $description;
            $this->characters = new ArrayCollection();
        }
    
        public function getId(): ?int
        {
            return $this->id;
        }
    
        public function getFactionName(): ?string
        {
            return $this->faction_name;
        }
    
        public function setFactionName(string $faction_name): static
        {
            $this->faction_name = $faction_name;
    
            return $this;
        }
    
        public function getDescription(): ?string
        {
            return $this->description;
        }
    
        public function setDescription(string $description): static
        {
            $this->description = $description;
    
            return $this;
        }
    
        /**
         * @return Collection<int, Character>
         */
        public function getCharacters(): Collection
        {
            return $this->characters;
        }
    
        public function addCharacter(Character $character): static
        {
            if (!$this->characters->contains($character)) {
                $this->characters->add($character);
                $character->setFaction($this);
            }
    
            return $this;
        }
    
        public function removeCharacter(Character $character): static
        {
            if ($this->characters->removeElement($character)) {
                // set the owning side to null (unless already changed)
                if ($character->getFaction() === $this) {
                    $character->setFaction(null);
                }
            }
    
            return $this;
        }
    
        public function __toString(): string
        {
            return $this->faction_name;
        }
    }

    En este caso estoy usando Doctrine pero existen muchas otras opciones.

    Hasta acá el modelo.

    Si vas a https://localhost/docs vas a ver algo como:

    Que tampoco está tan mal, ¿no? Una interface Swagger interactiva que describe todas las operaciones disponibles en tu API.

    ¿Que por qué dice «greetings»? Ah, sí… detallín. Greetings es el modelo dummy que viene con el ejemplo.

    Probablemente lo que esperabas era algo más del estilo de:

    Lograrlo es bastante fácil, se trata de agregar esta etiqueta al comienzo de la definición de la clase Faction:

    #[ApiResource]

    Luego re-cargar y… voilà. La API está lista… o casi.

    Actualizar la base de datos

    Para poder ingresar/leer datos no sólo se necesita el código PHP, la base de datos también debe estar actualizada.

    Usando estos comandos:

    docker compose exec php php bin/console make:migration

    Y

    docker compose exec php php bin/console doctrine:migrations:migrate -q

    Estará todo listo para probar la API.

    Con el comando:

    curl -X 'POST' \
      'https://localhost/factions' \
      -H 'accept: application/ld+json' \
      -H 'Content-Type: application/ld+json' \
      -d '{  
      "faction_name": "A Faction",
      "description": "A description"
    }'

    Obtendrás esto como respuesta:

    {
      "@context": "/contexts/Faction",
      "@id": "/factions/1",
      "@type": "Faction",
      "id": 1,
      "faction_name": "A Faction",
      "description": "A description",
      "characters": [],
      "factionName": "A Faction"
    }

    Y también unos cuantos encabezados interesantes:

     accept-patch: application/merge-patch+json 
     alt-svc: h3=":443"; ma=2592000 
     cache-control: no-cache,private 
     content-location: /factions/1 
     content-type: application/ld+json; charset=utf-8 
     date: Thu,17 Oct 2024 18:01:30 GMT 
     link: <https://localhost/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation" 
     location: /factions/1 
     permissions-policy: browsing-topics=() 
     server: Caddy 
     vary: Accept 
     x-content-type-options: nosniff 
     x-debug-token: 0d76d9 
     x-debug-token-link: https://localhost/_profiler/0d76d9 
     x-frame-options: deny 
     x-robots-tag: noindex 

    Todo esto también lo podés ver más cómodamente desde acá:

    Y por supuesto, podés probar los otros métodos HTTP.

    Agregar lógica de negocio

    ¿Validaciones dijiste? Ningún problema, hagamos algunas.

    ¿Qué tal una que no permita dejar vacío el nombre ni la descripción de la Faction?

    Symfony al rescate: usá el atributo Symfony\Component\Validator\Constraints\NotBlank y a otra cosa:

    #[ORM\Entity(repositoryClass: FactionRepository::class)]
    #[ORM\Table(name: '`factions`')]
    #[ApiResource]
    class Faction
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column]
        private ?int $id = null;
    
        #[ORM\Column(length: 128)]
        #[NotBlank]
        private ?string $faction_name = null;
    
        #[ORM\Column(type: Types::TEXT)]
        #[NotBlank]
        private ?string $description = null;

    Si probás ejecutar:

    curl -X 'POST' \ 'https://localhost/factions' \ -H 'accept: application/ld+json' \ -H 'Content-Type: application/ld+json' \ -d '{ "faction_name": "", "description": "Not empty" }'

    Vas a obtener:

    {
      "@id": "/validation_errors/c1051bb4-d103-4f74-8988-acbcafc7fdc3",
      "@type": "ConstraintViolationList",
      "status": 422,
      "violations": [
        {
          "propertyPath": "faction_name",
          "message": "This value should not be blank.",
          "code": "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
        }
      ],
      "detail": "faction_name: This value should not be blank.",
      "hydra:title": "An error occurred",
      "hydra:description": "faction_name: This value should not be blank.",
      "type": "/validation_errors/c1051bb4-d103-4f74-8988-acbcafc7fdc3",
      "title": "An error occurred"
    }

    Control de acceso

    El control de acceso se puede implementar de forma muy sencilla también: a través de la propiedad security del atributo #[ApiResource].

    Basta con cambiar el encabezado por:

    #[ApiResource(security: "is_granted('ROLE_USER')")]
    class Faction

    Para que, al hacer esta petición:

    curl -X 'GET' \
      'https://localhost/factions?page=1' \
      -H 'accept: application/ld+json'

    La respuesta sea:

    {
      "@id": "/errors/401",
      "@type": "hydra:Error",
      "title": "An error occurred",
      "detail": "Full authentication is required to access this resource.",
      "status": 401,
      "type": "/errors/401",
      "trace": [
        {
          "file": "/app/vendor/symfony/security-http/Firewall/ExceptionListener.php",
          "line": 189,
          "function": "throwUnauthorizedException",
          "class": "Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/security-http/Firewall/ExceptionListener.php",
          "line": 148,
          "function": "startAuthentication",
          "class": "Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/security-http/Firewall/ExceptionListener.php",
          "line": 103,
          "function": "handleAccessDeniedException",
          "class": "Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/event-dispatcher/Debug/WrappedListener.php",
          "line": 116,
          "function": "onKernelException",
          "class": "Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/event-dispatcher/EventDispatcher.php",
          "line": 220,
          "function": "__invoke",
          "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/event-dispatcher/EventDispatcher.php",
          "line": 56,
          "function": "callListeners",
          "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php",
          "line": 139,
          "function": "dispatch",
          "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
          "line": 239,
          "function": "dispatch",
          "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
          "line": 91,
          "function": "handleThrowable",
          "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/http-kernel/Kernel.php",
          "line": 197,
          "function": "handle",
          "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
          "type": "->"
        },
        {
          "file": "/app/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php",
          "line": 35,
          "function": "handle",
          "class": "Symfony\\Component\\HttpKernel\\Kernel",
          "type": "->"
        },
        {
          "file": "/app/vendor/autoload_runtime.php",
          "line": 29,
          "function": "run",
          "class": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner",
          "type": "->"
        },
        {
          "file": "/app/public/index.php",
          "line": 5,
          "function": "require_once"
        }
      ],
      "hydra:title": "An error occurred",
      "hydra:description": "Full authentication is required to access this resource."
    }

    Desplegar en Producción

    A la hora de desplegar la recomendación es usar el mismo docker compose que trae el framework. ¿Tenés dudas sobre este punto? Acá tenés un recurso que puede ser interesante.

    Pues aquí bastará con tener algún servidor donde desplegar (Mi recomendación aquí es usar un sencillo Droplet de DigitalOcean), instalarle docker y crear un contexto especial, de modo de terminar lanzando un comando tipo:

    docker compose -c prod docker-compose.yml up -d --wait

    Ah! Casi olvido un detalle importante: modificar el archivo .env para que diga APP_ENV=prod en lugar de APP_ENV=dev.

    Y listo. Bueno, luego vendrá, si corresponde, mover el DNS y demás pero eso será igual se trate o no de una API.

    Qué más se puede hacer con API-Platform

    La verdad es que mucho, pero no puedo meter todo en un único post.

    Dejo algunas cosas que me parecen sumamente interesantes para que las investigues:

    • Scaffolding de clientes
    • Comunicación en tiempo real a través de Mercure
    • Generación de los modelos a partir de especificaciones OpenApi

    ¿Te convencí? Dejame tus preguntas en los comentarios.

  • ¿Puede Docker sustituir a XAMPP?

    ¿Puede Docker sustituir a XAMPP?

    Hace unos años, montar un entorno de desarrollo para una aplicación web PHP era una verdadera molestia:

    Primero era necesario instalar Apache, luego MySQL, después configurar todo para que el Apache pudiera procesar correctamente los archivos PHP y generar la salida esperada, pasando en el medio por la configuración de MySQL y rogar que todo funcionara bien.

    Y así pasábamos los días hasta que llegó XAMPP.

    Qué es XAMPP

    XAMPP fue, en su día, una verdadera revolución: un paquete único que contenía todo lo que podíamos necesitar para desarrollar cómodamente: click aquí, click allí y listo, a programar se ha dicho!

    Si bien XAMPP simplicaba, y mucho, el montaje de un entorno local, en el fondo no se trataba de algo esencialmente diferente de lo que veníamos haciendo hasta el momento… sólo que mucho más rápido y cómodo.

    El problema empezó cuando muchos programadores comenzaron a usarlo como si se tratara de una solución mágica y dejaron de lado comprender qué es lo que estaba ocurriendo tras bambalinas.

    Craso error.

    Error que se hace patente a la hora de subir el sitio al hosting. Es entonces cuando empiezan los problemas como:

    Tengo este código que en localhost funciona perfectamente, que recibe los datos enviados mediante un formulario y saca los resultados mediante una tabla que se rellena automáticamente según los datos enviados. Resulta que cuando lo subo al servidor (hosting) no funciona y no da ningún tipo de error, simplemente no dibuja la tabla.

    O:

    tengo una pagina en php, en el servidor local funciona bien, es un login que al ingresar los datos dirige a una parte especifica de la web dependiendo el perfil, ahora si subo esto a mi hosting funciona perfectamente, pero si subo la web a otro dominio al ingresar los datos del login se queda la página en blanco. Ya intente que mostrara errores pero no me sale nada.

    Hace algunos años habría recomendado usar máquinas virtuales para desarrollar. Hoy por hoy, existiendo Docker hay un nuevo ganador.

    Qué es Docker

    Docker es, ante todo, una plataforma de ejecución de aplicaciones basada en el concepto de contenedor.

    La idea, muy resumida, es tener una única unidad (un paquete) que contenga en sí mismo todo lo necesario para ejecutar una aplicación.

    De este modo, nos olvidamos de las pequeñas sorpresas que pueden encontrarse al llevar a un hosting un proyecto desarrollado en forma local.

    ¿Puede usarse Docker para desarrollos PHP?

    ¡Claro que sí!

    Existen varias formas diferentes de usar Docker en proyectos PHP. En general, lo que se necesitará será una imagen que incluya el intérprete de PHP y, si se trata de una aplicación web, también un servidor.

    XAMPP vs. Docker

    Recapitulando un poco, como para que quede claro: XAMPP es un paquete de software específicamente diseñado para PHP, mientras que Docker es un sistema de manejo de contenedores genérico, es decir, no sólo puede usarse para PHP si no para cualquier lenguaje.

    Por otro lado, con Docker es sumamente sencillo tener, en una misma máquina física, muchos procesos PHP corriendo diferentes versiones. Te reto a que intentes eso con XAMPP.

    La desventaja, por llamarle de algún modo, de usar Docker en lugar de XAMPP es su dificultad para montarlo. Claro que, una vez que lo domines ésta desaparecerá.

    ¿Cómo reemplazar XAMPP por Docker?

    Para reemplazar XAMPP por Docker se requiere contar con una configuración de Docker que incluya los servicios que XAMPP contiene (Además del propio Docker instalado localmente, claro).

    En principio se trata de:

    Esto supone crear 4 contenedores diferentes, los cuales tendrán que estar en sincronía.

    Suena complicado, ¿no? Bueno… no es para tanto.

    Usando docker-compose es posible lograrlo con poco esfuerzo.

    Por ejemplo, si tenemos un proyecto donde tenemos un archivo llamado index.php que contiene lo siguiente:

    <?php
    
    echo "Viva PHP!";

    Se puede:

    1. Crear un archivo docker-compose.yml en el mismo directorio con este contenido:
    version: '3.8'
    services:
        mariadb:
            image: 'mariadb:11.0'        
            volumes:
                - 'database-data:/var/lib/mysql'
            environment:
                - MYSQL_ROOT_PASSWORD=root
                - MYSQL_DATABASE=my_app
                - MYSQL_USER=my_user
                - MYSQL_PASSWORD=my_pwd
            ports:
                - '29003:3306'
            restart: always
        webserver:
            image: 'php:7.4-apache'
            working_dir: '/var/www/html'
            volumes:
                - '.:/var/www/html'
            ports:
                - '29000:80'
            restart: always   
        pma:
            image: 'phpmyadmin:latest'
            ports:
                - '29005:80'
            links:
                - 'mariadb:db'
            environment:
                - PMA_ARBITRARY=1
    volumes:
        database-data: {}
    1. Darle al docker-compose up -d
    1. Abrir el navegador en http://localhost:29000 y voilá:

    Luego, si se quiere entrar al PhpMyAdmin basta con abrir una nueva ventana en http://localhost:29005, ingresar las credenciales y se verá esto:

    Y poco más…

    A partir de aquí es posible hacer todo lo que se hacía con XAMPP, con la diferencia de que luego subir todo al hosting será mucho más sencillo.

  • XDebug con VSCode y Docker en Ubuntu

    Usar Docker en proyectos PHP es un viaje de ida.

    Olvidarse del «te juro que en mi casa andaba!» es una bendición.

    Claro que, para poder desplegar, primero hay que desarrollar. Y desarrollar implica, claro está, debuggear.

    En PHP no contamos con un debugger incorporado a nuestros IDEs… afortunadamente existe XDebug.

    El problema, sin embargo, suele ser la configuración que depende de dos factores que deben combinarse:

    1. La instalación y configuración del lado del servidor.
    2. La configuración del IDE para poder utilizarlo.

    Existen muchas combinaciones posibles para realizar esta tarea, cada una con sus pequeñas particularidades. En este artículo me enfocaré en una de las combinaciones más populares por estos días:

    VSCode sobre Ubuntu con un WebServer montado en Docker.

    La aplicación que usaré como ejemplo es el CRM de código abierto rukovoditel.

    El Dockerfile

    Lo primero será definir el archivo Dockerfile que permita contar con un servidor web que pueda ejecutar php y, a la vez, tenga instalado XDebug:

    FROM php:7.4-apache
    LABEL authors="mauro.chojrin@leewayweb.com"
    
    RUN apt-get update && \
        apt-get install -y \
            libpng-dev \
            libzip-dev && \
            pecl install xdebug-3.1.6
    
    RUN docker-php-ext-install gd && \
        docker-php-ext-install zip && \
        docker-php-ext-install mysqli && \
        docker-php-ext-enable xdebug
    
    COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
    
    ADD app /var/www/html
    
    RUN chown -R www-data.www-data /var/www/html

    La configuración de XDebug

    A continuación ha de crearse el archivo de configuración de xdebug (xdebug.ini) en la raíz del proyecto:

    zend_extension=xdebug
    
    [xdebug]
    xdebug.mode=develop,debug
    xdebug.client_host=host.docker.internal
    xdebug.start_with_request=yes

    El archivo docker-compose.yml

    Posteriormente será el turno del archivo docker-compose.yml que contendrá los servicios necesarios para dar vida a la aplicación:

    version: '3.1'
    services:
      mysql:
        image: 'mysql:5.7.42-debian'
        environment:
          - MYSQL_ROOT_PASSWORD=root
          - MYSQL_DATABASE=ruko
          - MYSQL_USER=ruko
          - MYSQL_PASSWORD=ruko
        restart: always
      webserver:
        image: 'ruko_ws'
        build:
          context: '.'
        ports:
          - '8888:80'
        volumes:
          - './app:/var/www/html:rw'
          - './xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini'
        extra_hosts:
          - host.docker.internal:host-gateway

    Con esto está listo todo lo necesario del lado de la infraestructura.

    Los siguientes pasos se darán del lado del host.

    Configuración de VS Code

    La extensión de debugging de PHP

    Una vez abierto el proyecto, hay que asegurarse de tener instalada la extensión requerida para realizar debugging:

    La configuración de lanzamiento

    A continuación, debe crearse una configuración de lanzamiento para realizar el debugging:

    De las múltiples entradas que encuentro la que interesa es la llamada Listen for Xdebug.

    Por defecto, lo que se ve es:

    {
          "name": "Listen for Xdebug",
          "type": "php",
          "request": "launch",
          "port": 9003
    },

    Lo que se necesita es agregar la definición del mapeo de directorios, desde el local al del servidor.

    En este caso, el directorio en el servidor (en el contenedor Docker) es /var/www/html/, el cual debe ser conectado con el directorio app dentro de la raíz del proyecto.

    Para usar el directorio del host, sin hardcodearlo, es posible usar la variable de VisualStudio workspaceFolder la cual contiene el directorio raíz del proyecto.

    De modo que la configuración quedará de esta forma:

    {
          "name": "Listen for Xdebug",
          "type": "php",
          "request": "launch",
          "port": 9003,
          "pathMappings": {
                 "/var/www/html/": "${workspaceFolder}/app"
          }
    },

    Debuggeando

    Con esto listo es posible arrancar el debugger:

    Para ver su efecto es conveniente introducir un punto de interrupción (breakpoint) en algún lugar del código.

    Tomemos como ejemplo el archivo app/install/index.php ya que es el primero que se ejecuta al ingresar a la aplicación.

    Lo próximo será ir al navegador web, donde se dará comienzo a la sesión de debugging usando el asistente de XDebug.

    Luego se debe ingresar la dirección de la página (http://localhost:8888 en este caso) y, al dar enter se abrirá automáticamente la ventana de VS Code:

    Donde podrá ejecutarse el código paso a paso:

    Inspeccionar variables en tiempo real

    Y, en general, utilizar todos los servicios provistos por XDebug.

    Puntos clave

    En resúmen, las claves para hacer funcionar XDebug con VSCode en Docker sobre Ubuntu son:

    1. Contar con una imagen basada en un Dockerfile que incluya la instalación de XDebug
    2. Tener instalada la extensión de debugging con PHP en VS Code
    3. Configurar correctamente el mapeo de directorios en la configuración de lanzamiento (launch.json)
    4. Tener la definición de extra_hosts del contenedor que tiene php apuntando a host-gateway
    5. Tener el xdebug correctamente configurado dentro del contenedor de php
  • ¿Cualquier aplicación PHP se puede dockerizar?

    ¿Cualquier aplicación PHP se puede dockerizar?

    ¿Vale la pena Dockerizar tu php?

    Permitime contestarte con un ejercicio.

    Imaginate esta situación:

    Venís trabajando sin descanso en una aplicación muy importante para entregar a un cliente.

    Vas a buen ritmo, vas a llegar a la entrega sin mucho problema.

    De pronto, te aparece la notificación de actualizar el sistema operativo y pensás: «Y… la verdad que no estaría mal… no puedo seguir toda la vida con la 1.0…».

    Le das aceptar y, luego de unos minutos, cuando todo terminó te das cuenta de que la aplicación que andaba perfecta dejó de funcionar.

    ¿Eh?

    ¿Cómo puede ser?

    Así como instintivamente tirás un php -v y comprobás que de 7.4 saltaste sin escalas a 8.1… y las cosas ya no son como antes.

    ¡Qué problema! Menos mal que es una situación hipotética ¿no?

    Pues… no tanto. Esta historia es una adaptación libre de esta conversación que leí recientemente:

    «se me actualizo php a la version 8 en Manjaro, y por ende se me descuadró todo, qué tengo que cambiar en el httpd.conf para que me funcione php8«

    «Soy usuario de manjaro y me pasó lo mismo. Pero por suerte tenia dockerizado mis proyectos y no sufrí demasiado, deberías considerarlo«

    Yo diría que suerte habría sido no tener que enfrentarse al problema.

    Tener tus proyectos php dockerizados no es suerte, es una decisión.

    Una decisión que en algún momento este desarrollador tomó y que también deberías, al menos, evaluar.

    Es un caso muy similar al que analizo acá.

    Pensando un poco en esto empecé a preguntarme: ¿Cualquier aplicación php se puede dockerizar?

    Qué significa dockerizar una aplicación PHP

    Cuando se habla de dockerizar (me encanta este verbo) una aplicación, se hace referencia a hacer las adaptaciones necesarias para poder correrla sobre contenedores docker, tanto en un etorno de desarrollo como en uno de producción.

    Estos ajustes dependen en gran medida de la naturaleza de la aplicación pero, a rasgos generales, se trata de:

    • Crear un Dockerfile (O al menos seleccionar una imagen pre-existente que sea compatible con las necesidades de la aplicación)
    • Cambiar, si las hubiera, referencias a localhost por otras que apunten a servicios dentro del ecosistema de docker
    • Crear una configuración de inicio de los servicios (Ya sea a través de Makefiles o similar o, mejor aún, a través de un archivo docker-compose)

    En principio creo que no hay mucho más, pero si te parece que me olvidé de algo importante, por favor dejame un comentario abajo.

    Qué se necesita para dockerizar una aplicación PHP

    Para dockerizar una aplicación PHP se necesita, primero que nada, contar, tanto en local como en producción, con el motor y el cliente de docker.

    Si se pretende usar docker-compose, también deberá estar presente en ambos lados.

    Más allá de eso, es conveniente contar con un buen IDE, aunque no es imprescindible.

    Técnicamente no se necesita nada más. Claro que saber lo que se está haciendo ayuda mucho.

    Cómo dockerizar una aplicación PHP

    Teniendo todo en su lugar el proceso es simple, aunque, dependiendo de la aplicación, puede ser más laborioso que en otros casos.

    Relevamiento

    Lo primero es entender cuáles son las dependencias:

    • ¿Qué versión de PHP usás en producción?
    • ¿Qué extensiones de PHP necesitás instaladas y habilitadas?
    • ¿Qué otros servicios de infraestructura se usan con tu aplicación? ¿MySQL? ¿Redis?
      • ¿Qué versiones?
    • ¿Cómo está la configuración del webserver?
    • ¿Cómo está la configuración de PHP?
    • ¿Qué permisos sobre archivos necesita la aplicación para funcionar?

    Con las respuestasa a estas preguntas podrás darte una idea de lo que requerirás en tu imagen docker.

    Instalación de las herramientas

    Si no las tienes disponibles, el próximo paso será instalar las herramientas necesarias (docker engine, docker client y docker-compose al menos).

    Adaptación de la aplicación

    Luego deberás preparar la aplicación:

    • Reemplazar referencias a servicios externos (Bases de datos, colas de mensajes, etc…) de IPs o nombres conocidos en el host a nombres conocidos dentro de la red de Docker
    • Reemplazar rutas absolutas por relativas
    • Reemplazar enlaces simbólicos por archivos concretos

    Creación de los archivos de Docker

    Es muy probable que tu aplicación tenga algunos requisitos específicos que no encuentres en una de las imágenes disponibles en el dockerhub. No te preocupes, siempre podés tomar una imagen lo más parecida posible a tus necesidades para usar como base y agregar tus cambios particulares.

    Si el sistema es suficientemente complejo (si requiere de otros componentes de infraestructura), es conveniente definir también un archivo docker-compose.yml para mayor comodidad.

    Pruebas y ajustes

    Una vez hayas pasado por los pasos anteriores será cuestión de probar la aplicación y validar que todo funciona.

    Los comandos exactos dependerán de cómo hayas armado los archivos de configuración, pero será algo así como:

    docker build . -t mi_php
    
    docker run -it mi_php 

    O, si usas docker-compose:

    docker-compose up --build

    Una vez estén levantados los servicios podrás entrar a http://localhost:8000 (Asumo que el puerto 8000 está mapeado al 80 en el contenedor) y ya podrás verificar que toda la aplicación funciona.

    Por qué dockerizar tus aplicaciones php

    Si te parece mucho trabajo, tenés razón, dockerizar una aplicación PHP puede ser molesto, especialmente cuando lo tienes que hacer por primera vez pero, si queda bien, es muy probable que no tengas que volver a hacerlo por mucho tiempo y, a la vez, tu aplicación quedará blindada ante cambios en el ambiente, tanto local como de producción.

    De pronto no se ve tan mal, ¿no?

  • Por qué NO deberías usar XAMPP

    Por qué NO deberías usar XAMPP

    Me llegó esta pregunta que me pareció interesante compartir:

    Estoy usando PHPStorm, y como sólo me pide el intérprete cuando trato de ejecutar en el navegador, todavía no lo he instaldo, quisiera saber si ya es mejor instalar el Xampp, si es recomendable y en caso de que no lo sea ¿por que?

    Por si no sabés de qué se trata XAMPP, es un paquete que trae, todo lo que típicamente se requiere para desarrollar con PHP:

    La X del comienzo puede ser reemplazada por L (Linux), W (Windows) o M (Mac).

    A primera vista parece la panacea, ¿no?

    «Es lo más fácil!»

    «Un par de clics y listo!»

    «¿Para qué complicarme instalando todo por separado si puedo tenerlo en un solo paquete»?

    Seguramente habrás escuchado este tipo de argumentos a su favor.

    Y sí, todo eso es verdad: XAMPP es un paquete sumamente cómodo… al principio.

    Los problemas llegan cuando:

    • Es momento de ir a producción
    • Necesitás trabajar con diferentes proyectos a la vez.

    Son estos los momentos en te das cuenta que el salvavidas estaba hecho de plomo.

    Cuál es el problema con XAMPP

    La sencillez que aporta XAMPP lo hace la opción más difundida entre los desarrolladores menos experimentados. Pero esa sencillez tiene un costo.

    El primero de los problemas es que, al ocultar la complejidad real que implica montar un servidor, se propicia el efecto «¡Te juro que en mi casa andaba!». Como no sabés realmente qué tenés instalado es difícil verificar que el hosting tenga lo mismo (Más detalle de qué es exactamente lo que deberías mirar acá).

    El segundo de los problemas es que hace muy difícil trabajar en diferentes proyectos donde cada uno tiene requerimientos de infraestructura diferentes (Por ejemplo, versiones diferentes del intérprete de PHP).

    Por supuesto que, si sos conciente de estas limitaciones y sabés trabajar con ellas XAMPP puede ser una opción aceptable.

    Qué usar en lugar de XAMPP

    Las opciones son varias, hay algunas mejores que XAMPP y otras peores:

    En general, docker es la mejor opción cuando se trata de montar entornos locales ya que es muy simple luego llevarlos a producción y/o compartir con tu equipo.

    Pero bueno, es cierto también que dominarlo no es una tarea muy sencilla.

    Si recién estás empezando (Y quiero decir que apenas estás dando tus primeros pasos), está bien que uses XAMPP pero es importante que tengas la idea de migrar lo antes posible.

  • 3 Herramientas para usar Docker con PHP

    3 Herramientas para usar Docker con PHP

    Escuchaste a más de un colega comentar que Docker es una gran herramienta y te estás empezando a preguntar si no te estás perdiendo de algo.

    Miraste un poco la documentación y, honestamente, el calificativo amigable le queda algo holgado… por decirlo amablemente.

    Todo ese tema de las imágenes, los contenedores, los volúmenes… es mucho.

    Y de entrada toparse con un archivo como:

    FROM php:7.1-apache
    
    LABEL vendor="Mautic"
    LABEL maintainer="Luiz Eduardo Oliveira Fonseca <luiz@powertic.com>"
    
    # Install PHP extensions
    RUN apt-get update && apt-get install --no-install-recommends -y \
        cron \
        git \
        wget \
        sudo \
        libc-client-dev \
        libicu-dev \
        libkrb5-dev \
        libmcrypt-dev \
        libssl-dev \
        libz-dev \
        unzip \
        zip \
        && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
        && rm -rf /var/lib/apt/lists/* \
        && rm /etc/cron.daily/*
    
    RUN docker-php-ext-configure imap --with-imap --with-imap-ssl --with-kerberos \
        && docker-php-ext-configure opcache --enable-opcache \
        && docker-php-ext-install imap intl mbstring mcrypt mysqli pdo_mysql zip opcache bcmath\
        && docker-php-ext-enable imap intl mbstring mcrypt mysqli pdo_mysql zip opcache bcmath
    
    # Install composer
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer
    
    # Define Mautic volume to persist data
    VOLUME /var/www/html
    
    # Define Mautic version and expected SHA1 signature
    ENV MAUTIC_VERSION 2.16.2
    ENV MAUTIC_SHA1 df6735df8d7d31cc6bc505c38ee8147b40b8311b
    
    # By default enable cron jobs
    ENV MAUTIC_RUN_CRON_JOBS true
    
    # Setting an root user for test
    ENV MAUTIC_DB_USER root
    ENV MAUTIC_DB_NAME mautic
    
    # Setting PHP properties
    ENV PHP_INI_DATE_TIMEZONE='UTC' \
        PHP_MEMORY_LIMIT=512M \
        PHP_MAX_UPLOAD=128M \
        PHP_MAX_EXECUTION_TIME=300
    
    # Download package and extract to web volume
    RUN curl -o mautic.zip -SL https://github.com/mautic/mautic/releases/download/${MAUTIC_VERSION}/${MAUTIC_VERSION}.zip \
        && echo "$MAUTIC_SHA1 *mautic.zip" | sha1sum -c - \
        && mkdir /usr/src/mautic \
        && unzip mautic.zip -d /usr/src/mautic \
        && rm mautic.zip \
        && chown -R www-data:www-data /usr/src/mautic
    
    # Copy init scripts and custom .htaccess
    COPY docker-entrypoint.sh /entrypoint.sh
    COPY makeconfig.php /makeconfig.php
    COPY makedb.php /makedb.php
    COPY mautic.crontab /etc/cron.d/mautic
    RUN chmod 644 /etc/cron.d/mautic
    
    # Enable Apache Rewrite Module
    RUN a2enmod rewrite
    
    # Apply necessary permissions
    RUN ["chmod", "+x", "/entrypoint.sh"]
    ENTRYPOINT ["/entrypoint.sh"]
    
    CMD ["apache2-foreground"]

    No da muchas ganas de incursionar, ¿cierto?

    Todo lo que querías era hacer una prueba sencilla. Como para comprobar por tus propios medios, si todo lo que te dijeron de Docker era realmente así y en cambio… tenés que ponerte a escribir archivos de texto inentendibles.

    Te tengo buenas noticias: hay varias herramientas muy simples que podés usar para generar los dichosos Dockerfile.

    Te presento algunas.

    Devilbox

    http://devilbox.org/ te permite crear un entorno moderno y altamente personalizado con soporte para LAMP sobre docker.

    Su instalación es sencilla:

    git clone https://github.com/cytopia/devilbox
    cd devilbox
    cp env-example .env
    docker-compose up

    Y listo.

    En tus manos un LAMP con Redis, Memcached, MongoDB, phpMyAdmin y un montón de herramientas de administración disponibles en http://localhost para hacer todo bien fácil.

    Ah, y claro, si querés ver qué hay detrás de la magia, el archivo docker-compose.yml está a tu disposición en el directorio donde clonaste el repo.

    PHPDocker

    https://phpdocker.io/ es un sitio donde podés, a través de un wizard, configurar la imagen Docker que querés generar:

    Una vez tenés todo donde debe estar descargás el archivo comprimido, lo descomprimís en el directorio que más te guste, docker-compose up y, voilá, tu entorno php Dockerizado está disponible con todos los condimentos que hayas seleccionado.

    Sail

    Sail es una herramienta perteneciente al framework Laravel. Si creas una nueva aplicación desde cero no tendrás más que ejecutar ./vendor/bin/sail up dentro del directorio raíz de tu proyecto para comenzar.

    Una vez descargado y configurado todo podrás entrar en http://localhost y disfrtuar de tu nuevo entorno de trabajo con Docker.

    Y, como siempre, el archivo docker-compose.yml estará allí para investigar/modificar.

    Y un par más…

    Un par de herramientas más que vale la pena conocer son:

    • Deck: una aplicación de escritorio basada en electron.js con la que puedes crear un ambiente entero para desarrollo. La iba a incluir en este listado pero al probarla falló la instalación… tal vez más adelante cuando esté más madura la agregue.
    • Las imágenes de ServerSideUp optimizadas para ir a producción.

    Ahora sí, todo listo para dockerizar tus aplicaciones!

  • Cómo mostrar tu proyecto PHP sin usar un hosting

    Cómo mostrar tu proyecto PHP sin usar un hosting

    Listo. Terminado. Finito.

    Ah… qué placer, ¿no?

    Después de horas frente a la pantalla, incontables tazas de café y miles de bugs resueltos, por fin llegará el merecido descanso… sólo falta hacer la demo para el cliente.

    Es que si no se hace el cliente no podrá dar su visto bueno y sin él… difícil que haga el último pago :p

    Podrías hacer un despliegue completo en su servidor pero… ¿y si algo sale mal?

    O peor, ¿qué tal si sale todo bien y el código ya está fuera de tu control?

    ¿Cómo hacer para que otra persona vea tu trabajo sin entregarle el código?


    Existen varias opciones según cuál sea tu modo de trabajar.

    Para este artículo asumiré que desarrollas en un servidor local (XAMPP o similar).

    Usar un servidor de pruebas

    Una opción perfectamente válida es utilizar un servidor intermedio en el cual puedas desplegar tu código.

    Idealmente este servidor será tuyo, con lo cual no tendrás que entregar tu código en forma prematura.

    La principal desventaja de este método es que puede tener un costo asociado: el del hosting.

    Por otra parte, preparar este espacio puede no ser una tarea trivial y hacerlo cada vez que tengas que realizar una demostración puede ser una pérdida de tiempo significativa.

    Abrir temporalmente el acceso a localhost

    Otra opción que puede resultar mucho más económica y sencilla es abrir temporalmente el acceso a tu computadora a través de internet.

    Existen diversas herramientas que puedes utilizar para asociar un nombre de dominio a tu dirección IP, de modo de evitarle a tu cliente tener que tipearla en su navegador.

    Un ejemplo de ese tipo de solución es No-IP.

    No es una mala opción, aunque puede ser un poco compleja de administrar (amén de requerir la instalación y ejecución constante de un cliente).

    Otra posiblidad bastante más atractiva es utilizar ngrok.

    Se trata de una utilidad muy sencilla que te permite levantar un túnel hacia tu computadora y pasarle a tu cliente una URL a la cual conectarse.

    Basta con ejecutar un comando como ngrok http 80 para obtener una pantalla como esta:

    Información de ngrok ejecutándose

    Aquí puedes ver claramente cómo la dirección http://7da0fd4f278a.ngrok.io apunta al puerto 80 en mi computadora local.

    Sólo se necesita ingresar a http://7da0fd4f278a.ngrok.io para ver lo mismo que yo veo usando http://localhost.

    No está mal, ¿cierto?

    Nada de FTP, nada de crear servidores temporales ni cosas parecidas.

    ¿Lo más interesante? Al terminar la prueba sólo se necesita dar Ctrl+C y asunto finalizado.

    Terminó la demo y terminó el acceso remoto.

    Simple, rápido y económico (o BBB si lo prefieres :))

    Adelante, la próxima demo que vayas a hacer no te compliques, crea ahora mismo una cuenta en ngrok, configura el cliente y olvídate del problema de los despliegues temporales

  • Cuál es la mejor librería para hacer PDF desde PHP

    Cuál es la mejor librería para hacer PDF desde PHP

    Apuesto que te habrás topado con la necesidad de emitir reportes en PDF alguna vez, ¿cierto?

    Por ejemplo para generar facturas electrónicas.

    De por sí es un tema integrarte con los webservices del fisco (Léase AFIP, SUNAT, DGI, DIAN, etc…) y cuando pensás que todo está resuelto, te encontrás con que falta subir otra colina más: generar los benditos pdfs.

    Seguro habrás pensado:

    «Tiene que haber una librería que permita hacer pdfs con PHP»

    Y tenés razón… y a la vez no.

    ¿Cómo?

    Pues… es que no existe una librería si no unas cuantas… y las diferencias entre una y otra no siempre están disponibles a simple vista.

    Casi podríamos decir que elegir la librería para generar pdfs es un proyecto en sí mismo.

    Así que, para ayudarte a tomar esta decisión te voy a presentar un cuadro con las opciones más conocidas y sus características principales para que puedas analizarlas en conjunto y elegir la más conveniente para tus circunstancias particulares.

    Código abiertoBasado en HTMLOrientada a ObjetosInstalación vía composerDocumentaciónDependencias
    domPDFBuenaPHP 7.1+
    DOM
    MBString
    php-font-lib
    php-svg-lib
    fPDFNoNoRegular??
    html2pdfBuenaPHP 5.6+
    gd
    MBString
    mPDFBuenaPHP 5.6 a 7.4
    gd
    MBString
    tcpdfNoRegularPHP 5.3+
    PDFLibNoNoNoMuy buena
    setaPDFNoNoMuy buenaPHP 5.6+
    DOM
    iconv o MBString
    OpenSSL
    SPL
    Zlib
    Comparativa entre librerías para hacer PDF con PHP

    Si estás en un ambiente de hosting compartido lo principal es verificar si la versión qué versión de php tienes y qué extensiones están disponibles.

    Luego debes entender qué buscas priorizar: la facilidad de implementar la librería o la eficiencia en el uso de los recursos.

    La mejor librería para generar PDFs de alto contenido gráfico

    Si necesitas generar PDFs con grandes detalles gráficos (Inclusión de imágenes de fondo por ejemplo), lo más probable es que te convenga inclinarte por una librería simple como domPDF, mPDF o html2pdf.

    El problema que tienen estas librerías es que se vuelven poco eficientes cuando se trata de generar muchos archivos en poco tiempo.

    La librería más rápida para generar PDFs con PHP

    Si el principal requisito de tu aplicación es la velocidad de procesamiento tu mejor opción es PDFLib.

    Dado que se trata de una extensión de php, su velocidad difícilmente pueda ser superada por algún competidor escrito en PHP.

    Claro que esta velocidad viene con un precio: se trata de una librería paga y, si estás en un ambiente de hosting compartido, puede ser complejo instalarla.

    Otras herramientas para generar PDF desde PHP

    Algunas otras herramientas que, si bien no son exactamente librerías de php, pueden servirte para generar PDFs en tus aplicaciones:

    Como puedes ver, la elección de la herramienta ideal no es sencilla pero vale la pena hacer un poco de investigación y, sobre todo, comprender la necesidad específica para determinar cuál es la opción más conveniente.

    Coméntame cómo ha sido tu experiencia y qué tuviste en cuenta para elegir la librería que usaste 😉