PHP 7 – Acelerando el desarrollo web

Esta es la entrada 3 de 3 en la serie: PHP nuevas características

PHP

La Comunidad PHP sigue incorporando nuevas características y funcionalidades a este popular lenguaje de programación con el objetivo de hacerlo más atractivo, funcional y útil. La versión 7 (liberada el pasado 3 de diciembre) es considerada una de las mayores actualizaciones en toda la historia de PHP ya que el motor (Zend Engine) fue refactorizado con el objetivo de lograr mejor rendimiento y mejor uso de la memoria. También se introdujeron nuevas características que facilitan y aceleran el desarrollo web.

Rendimiento mejorado

Gracias a la version 3.0 del motor Zend, PHP 7 es 2 veces más rápido y consume 50% menos memoria que PHP 5.6. Para ver gráficos de comparación consulte:

Nuevos tipos de variables como argumentos

PHP está convirtiéndose en un lenguaje mixto o sea dinámico y tipeado ejemplo de esto es que ahora se aceptan nuevos tipos de variables como argumentos de funciones. Si el valor dado es de un tipo incorrecto se lanzará una excepción TypeError. Antes de PHP 7 podíamos utilizar nombre de clases o interfaz, self, array, callable ahora PHP7 añade los siguientes tipos: int, float, bool y string.

El valor de la directiva strict_types afecta el comportamiento de las declaraciones de tipo ya sea de argumentos o de retorno, en el modo por defecto PHP convertirá automaticamente de un tipo a otro. En el modo estricto el valor devuelto debe ser del tipo correcto.

Modo débil (por defecto)

Fork me on Github
<?php
function test_param(int $a, bool $is_ready, string $str) {
    echo $a, ' => ', gettype($a), "\n";
    echo $is_ready, ' => ', gettype($is_ready), "\n";
    echo $str, ' => ', gettype($str), "\n";
}

class Test {
    public function __toString() {
        return Test::class;
    }
}

test_param(5.5, true, new Test());

// Print
// 5 => integer
// 1 => boolean
// Test => string

Modo estricto

Fork me on Github
<?php
declare(strict_types = 1);

function test_param(int $a, bool $is_ready, string $str) {
    echo $a, ' => ', gettype($a), "\n";
    echo $is_ready, ' => ', gettype($is_ready), "\n";
    echo $str, ' => ', gettype($str), "\n";
}

class Test {
    public function __toString() {
        return Test::class;
    }
}

test_param(5, true, new Test());

// Print
// Fatal error: Uncaught TypeError: Argument 3 passed to test_param()
// must be of the type string, object given...

Declaraciones tipo de retorno

Ahora a las funciones se le puede especificar un tipo de retorno de forma similar a las declaraciones de tipo de argumento.

Modo débil (por defecto)

Fork me on Github
<?php
function sum($a, $b): int {
    return $a + $b;
}

function sum_f($a, $b): float {
    return $a + $b;
}

class Test {
    public function __toString() {
        return __CLASS__;
    }
}

function return_str(): string {
    return (new Test());
}

// Print 5
echo sum(3.5, 2), PHP_EOL;

// Print 5.5
echo sum_f(3.5, 2), PHP_EOL;

// Print Test
echo return_str(), PHP_EOL;

Modo estricto

Fork me on Github
<?php
declare(strict_types=1);

function sum($a, $b): int {
    return $a + $b;
}

function sum_f($a, $b): float {
    return $a + $b;
}

class Test {
    public function __toString() {
        return __CLASS__;
    }
}

function return_str(): string {
    return (new Test());
}

// Print
// Fatal error: Uncaught TypeError: Return value of sum() must be of the type
// integer, float returned...
echo sum(3.5, 2), PHP_EOL;

// Print 5.5
echo sum_f(3.5, 2), PHP_EOL;

// Print
// Fatal error: Uncaught TypeError: Return value of return_str() must be of the
// type string, object returned...
echo return_str(), PHP_EOL;

Operador de fusión de nulo

El operador de fusión de nulo (??) se ha añadido como un atajo al operador ternario cuando este último es usado con isset. Devuelve el primer operando si existe y no es nulo en caso contrario devuelve el segundo operando.

Fork me on Github
<?php
// Fetches the value of $_GET['user'] and returns 'nobody' if it does not exist.
$username = $_GET['user'] ?? 'nobody';

// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Puede concatenarse varios ?? devolviendo el primer valor definido y que no es nulo.

Fork me on Github
<?php
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';

El operador ?? tiene un significado similar al operador || usado en otros lenguages de programación como Javascript, Perl o Bash, pero téngase en cuenta que el operador || además comprueba si el valor es falso mientras que ?? solo comprueba si el valor existe y no es nulo.

Fork me on Github
#!/bin/bash
# Print yes
perl -e '$a = $b || "yes"; print($a);'

# Print yes
php -r '$a = $b ?? "yes"; print_r($a);'

# Print yes
perl -e '$a = 0 || "yes"; print($a);'

# Print 0
php -r '$a = 0 ?? "yes"; print_r($a);'

# Print I have a value
perl -e '$a = "I have a value" || "yes"; print($a);'

# Print I have a value
php -r '$a = "I have a value" ?? "yes"; print_r($a);'

Operador nave espacial

El operador nave espacial se utiliza para comparar dos expresiones. Si comparamos $a con $b, entonces devuelve -1 si $a < $b, 0 si $a = $b y 1 si $a > $b.

Fork me on Github
<?php
echo 1 <=> 1; //  0
echo 1 <=> 2; // -1
echo 2 <=> 1; //  1

echo 1.5 <=> 1.5; //  0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; //  1
 
echo "a" <=> "a"; //  0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; //  1

Asignar un arreglo a una constante

A partir de PHP 7 se puede asignar un arreglo a una constante definida usando la sentencia define.

Fork me on Github
<?php
define('DAYS', [
  'sun' => 'Sunday',
  'mon' => 'Monday',
  'tue' => 'Tuesday',
  'wed' => 'Wednesday',
  'thu' => 'Thursday',
  'fri' => 'Friday',
  'sat' => 'Saturday'
]);

// PrSunday
echo DAYS['sun'], PHP_EOL;

Clases anónimas

PHP 5.3 introdujo el concepto de funciones anónimas ahora PHP 7 introduce el concepto de clases anónimas y como su nombre lo indica es una clase que no tiene nombre. Una clase anónima realizará una función especifíca y nunca debe violar el principio de sola responsabilidad. Puede usar una clase anónima si:

  • Su clase no necesita ser documentada
  • Su clase tiene poco métodos y propiedades
  • Solo se necesita una instancia durante la ejecución de su aplicación
  • Se usa inmediatamente después de su definición
  • Necesita crear objetos al vuelo
  • El nombre no añade claridad y legibilidad al código fuente
  • Desea añadir o modificar un comportamiendo de una clase existente
  • Desea evitar el impacto de cargar la definición de clase desde un fichero externo (disco duro)

Desde mi punto de vista una clase anónima puede ser muy útil a la hora de devolver valores múltiples en una función de retorno y tiene un uso similar al objecto {} en Javascript.

Fork me on Github
<?php

define('PLAYERS', [
  '1' => ['Garry Kasparov', 2851],
  '2' => ['Anatoly Karpov', 2780],
  '3' => ['Magnus Carlsen', 2882],
  '4' => ['Bobby Fischer',  2785]
]);

interface PlayerDetailsInterface 
{
  public function getName();
  public function getRanking();
}

/**
 * Get Chess player detail
 * 
 * @return PlayerDetailsInterface
 */
function getChessPlayerDetails($id): PlayerDetailsInterface
{
  // Do some processing, consult some internet DB, Webservice or another resource
  $player = PLAYERS[$id];
  $name = $player[0];
  $ranking = $player[1];
  
  return new class($name, $ranking) implements PlayerDetailsInterface
  {
    private $name;
    private $ranking;
    
    public function __construct($name, $ranking) 
    {
      $this->name = $name;
      $this->ranking = $ranking;
    }
    
    public function getName() 
    {
      return $this->name;
    }
    
    public function getRanking() 
    {
      return $this->ranking;
    }
  };
}

foreach (array_keys(PLAYERS) as $id) {
  /**@var PlayerDetailsInterface $detail */
  $detail = getChessPlayerDetails($id);
  echo $detail->getName(), ': ', $detail->getRanking(), PHP_EOL;
}

/* Print
Garry Kasparov: 2851
Anatoly Karpov: 2780
Magnus Carlsen: 2882
Bobby Fischer: 2785
*/

Puede ver otros casos de usos en: Clases anónimas

Declaraciones de use en grupo

Las clases, funciones y constantes que se importen desde el mismo espacio de nombre ahora pueden ser agrupadas en una única sentencia use.

Agrupando clases

Fork me on Github
<?php
// Before/Antes PHP 7
// use Symfony\Component\Form\Extension\Core\Type\TextType;
// use Symfony\Component\Form\Extension\Core\Type\DateType;
// use Symfony\Component\Form\Extension\Core\Type\SubmitType;

// PHP 7
use Symfony\Component\Form\Extension\Core\Type\{TextType,DateType,SubmitType};

Agrupando funciones

Fork me on Github
<?php
namespace Wordpress {

  function wp_is_writable( $path ) {
    if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) )
        return win_is_writable( $path );
    else
        return @is_writable( $path );
  }

  function bool_from_yn( $yn ) {
    return ( strtolower( $yn ) == 'y' );
  }
}

// Global namespace
namespace {
  // Before/Antes PHP 7
  // use function Wordpress\wp_is_writable;
  // use function Wordpress\bool_from_yn;

  // PHP 7
  use function Wordpress\{wp_is_writable, bool_from_yn};

  if (wp_is_writable(__DIR__)) {
    echo __DIR__, ' is writeable', PHP_EOL;
  }

  echo bool_from_yn('y'), PHP_EOL;
}

Agrupando constantes

Fork me on Github
<?php
namespace Config {
  const DB_DRIVER = 'mysql';
  const DB_HOST   = 'localhost';
  const DB_PORT   = '3306';
  const DB_USER   = 'root';
}

// Global namespace
namespace {
  // Before/Antes PHP 7
  // use const Config\DB_DRIVER;
  // use const Config\DB_HOST;
  // use const Config\DB_PORT;
  // use const Config\DB_USER;

  // PHP 7
  use const Config\{DB_DRIVER, DB_HOST, DB_PORT, DB_USER};

  echo 'Connection details', PHP_EOL;
  echo 'Driver: ', DB_DRIVER, ', Host: ', DB_HOST, ', Port: ', DB_PORT, ', user: ', DB_USER, PHP_EOL;
}

Generadores

El concepto de generadores se introdujo a partir de PHP 5.5. PHP 7 agrega 2 nuevas características a los generadores

Expresiones return en generadores

Ahora los generadores pueden devolver una expresión final usando la sentencia return (la devolución por referencia no está permitida). Este valor se puede obtener empleando el nuevo método Generator::getReturn(), el cual solamente se puede utilizar una vez que el generador ha finalizado de producir valores.

Fork me on Github
<?php

$gen = (function() {
    yield 1;
    yield 2;

    return 3;
})();

foreach ($gen as $val) {
    echo $val, PHP_EOL;
}

echo $gen->getReturn(), PHP_EOL;

// Print
// 1
// 2
// 3

Delegación de generadores

Los generadores ahora pueden delegar a otro generador, objeto Traversable o array utilizando la sentencia yield from.

Fork me on Github
<?php
function gen()
{
    yield 1;
    yield 2;
    yield from gen2();
}

function gen2()
{
    yield 3;
    yield 4;
}

foreach (gen() as $val)
{
    echo $val, PHP_EOL;
}

// Print
// 1
// 2
// 3
// 4

Sintaxis de escape unicode

Ahora es posible escapar códigos unicode en formato hexadecimal y obtener el símbolo correspondiente. El siguiente script comprueba si el entorno de ejecución es cli e imprime los símbolos correspondientes en caso contrario asume que el resultado se visualizará en el navegador web. Nótese que en caso de visualizar los símbolos en el navagedor se le puede aplicar estilos y usarlo como íconos en botones y otros componentes web.

Fork me on Github
<?php
$is_cli = false !== strpos(PHP_SAPI, 'cli');
$nl = $is_cli ? PHP_EOL : nl2br(PHP_EOL);

if ($is_cli) {
    echo "\u{2665}", $nl;
    echo "\u{267B}", $nl;
    echo "\u{260E}", $nl;
} else {
    $style = '<a href="." style="font-size: 32px; color: %s; text-decoration: none;">%s</span>' . $nl;
    echo sprintf($style, 'green', "\u{2665}");
    echo sprintf($style, 'red', "\u{267B}");
    echo sprintf($style, 'blue', "\u{260E}");
    echo '<button type="button">', "\u{267B}", '</button>';
}

/**
 * Print
 *
 * ♥
 * ♻
 * ☎
 *
 */

Conclusión

Ya PHP no es un wrapper del lenguaje C para la web pues las Comunidad va agregando nuevas funcionalidades que hacen que PHP tenga características particulares.

Lectura recomendada




Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *