PHP strtok Recortando Strings Delimitadas por Caracter

$email = 'user.name@mocked.mail';
$user = strtok($email,'@');
var_dump($user); //user.name

$domain = strtok('');
var_dump($domain); //mocked.mail

Há algum tempo atrás trabalhei em um projeto que lidava com importação de dados e a sanitização era um processo imprescindível. Então me deparei com um comando muito curioso que eu não conhecia até então, o strtok.

Na sanitização de email é bastante factível subdividir a string principal em usuário e domínio, então ao invés de recorrer ao tradicional explode eu parti em busca de um novo recurso, algo mais prático.

Apliquei este comando especialmente em testes unitários combinados com o recurso DataProvider.

Veja como é simples conseguir o primeiro nome e sobrenome em dois hits apenas.

$email = 'first-name.last-name@mocked.mail';
$first = strtok(strtok($email,'@'),'.');
var_dump($first); //first-name

$last = strtok('');
var_dump($last); //last-name

Entenda como strtok realmente funciona. Ao informar o caracter delimitador a tokenização é buferizada e o primeiro elemento é disponibilizado e o restante da string permanece no buffer, mas sem ter sido particionada.

$csv = 'column1;column2;column3;column4;column5';
$first = strtok($csv, ';');
var_dump($first); //column1

$remainder = strtok('');
var_dump($remainder); //column2;column3;column4;column5

Para alcançar todos os elementos na cadeia é necessário ir particionando o conteúdo restante que se encontra no buffer, para isto basta informar apenas o delimitador na tokenização. Observe o código abaixo que demonstra um loop até que o retorno seja (bool)FALSE indicando que não existem mais elementos no buffer;

$csv = 'column1;column2;column3;column4;column5';
$token = strtok($csv, ';');

while ($token !== false) {
  echo $token."\n";
  $token = strtok(';');
}

Observe também que você pode utilizar múltiplos delimitadores no processamento e não há uma ordem que precise ser respeitada. A cada ciclo, ao encontrar qualquer um dos caracteres delimitadores o primeiro elemento é imediatamente retornado e o restante permanece no buffer até que ocorra o exaurimento completo do buffer.

$csv = 'column1;column2-column3|column4/column5';
$token = strtok($csv, '|-/;');

while ($token !== false) {
  echo $token."\n";
  $token = strtok('-|;/');
}

Agora você já sabe, precisou quebrar uma string?

Vai de strtok!

PHP Script Transformando Linhas de um Arquivo em Cláusula SQL IN

php process-file-lines-into-sql-in-clause.php --filename=original-file-with-ids-extracted-from-spreadsheet-column --quotes=none --split=1000

Acima a sintaxe de como invocar o script, abaixo o script ele mesmo.

<?php
const HINT_FILENAME = "php process-file-lines-into-sql-in-clause.php --filename=my-text-file-name.txt";
const HINT_QUOTES = HINT_FILENAME . " --quotes=double|single|none";
const HINT_SPLIT = HINT_QUOTES . " --split=1000|none";

$options = getopt(null, ["filename:","quotes:","split:"]);

(!array_key_exists('filename',$options))
    ? die("\n*** Missing argument --filename *** ... sample of usage:\n\n" . HINT_FILENAME . "\n\n")
    : null;

(!array_key_exists('quotes',$options))
    ? die("\n*** Missing argument --quotes *** ... sample of usage:\n\n" . HINT_QUOTES . "\n\n")
    : null;

(!array_key_exists('split',$options))
    ? die("\n*** Missing argument --split *** ... sample of usage:\n\n" . HINT_SPLIT . "\n\n")
    : null;

$filename = $options['filename'];
$quotes = $options['quotes'];
$split = $options['split'];

switch ($quotes) {
  case "double":
    $quotes = '"';
    break;
  case "single":
    $quotes = "'";
    break;
  case "none":
  default:
    $quotes = "";
}

$filename_original_renamed = date('Ymd') . '-source-' . $filename;
$file_content_rows = file($filename, FILE_IGNORE_NEW_LINES);

$split = is_numeric($split) ? (int)$split : count($file_content_rows);
$chunks = array_chunk($file_content_rows, $split);

echo "\n\nYour file lines are being processed and converted to a string of SQL IN clause";
echo "\nTotal Rows=" . count($file_content_rows);
echo "\n";

$glue = (empty($quotes)) ? ',' : $quotes . ',' . $quotes;

foreach ($chunks as $chunk => $chunk_rows) {
    $chunk_label = count($chunks) > 1 ? '-chunk-' . str_pad($chunk+1, 3, '0', STR_PAD_LEFT) : '-lines-' . $split;
    $filename_processed_renamed = date('Ymd') . '-processed-' . $filename . $chunk_label;
    $stringifyEmails = $quotes . implode($glue, $chunk_rows) . $quotes;
    file_put_contents($filename_processed_renamed, $stringifyEmails);

    echo "\nFile content processed and save onto a renamed file... " . $filename_processed_renamed;
}

rename($filename, $filename_original_renamed);

echo "\n\nYour original source file has been renamed to... " . $filename_original_renamed;
echo "\n\n";

Pessoal sem delongas… Sabem quando você tem aquela planilha lotada de IDs que passaram pra você fazer um SQL?

Pois é, até dá pra formatar na mão os IDs como sendo uma lista separadas por vírgulas e eventualmente recobertos por aspas ou algo assim.

Mas e se a planilha tiver 100.000 linhas ou mais? E se o SQL que você precisa rodar for na verdade um DML? E se você precisar executar as ações em volumes menores pra não sobrecarregar o servidor? Hã?

Pra isso eu desenvolvi este super script Tabajara que resolve o seu problema, veja que legal o pocesso.

Passo 1) copie e cole a coluna de IDs num arquivo texto tipo Brackets, Notepad++, VI ou outro editor qualquer de sua preferência, e salve no disco.

1140178
1141171
1166736
1177320
1193872
1212530

Passo 2) execute o script conforme demonstrado no primeiro bloco deste artigo, logo vou explicar as opções.

php process-file-lines-into-sql-in-clause.php --filename=original-file-with-ids-extracted-from-spreadsheet-column --quotes=none --split=none

Passo 3) colete no próprio diretório os arquivos processados com conteúdo no formato SQL IN.

1140178,1141171,1166736,1177320,1193872,1212530

Passo 4) se precisar, colete seu arquivo original que foi renomeado com o termo “source“.

Explicando as opções do Script:

  • filename nome do arquivo fonte contendo os respectivos elementos, sempre 1 por linha
  • quotes deve ser informado um valor entre double|single|none para que elemento seja recoberto por aspas no caso de ser uma string
  • split deve ser informado um valor inteiro com a quantidade de registros máximos a serem gerados no arquivo processado. No caso serão gerados tantos arquivos quanto necessário que serão separados como chunks, contendo sempre a quantidade máxima exceto o último que poderá conter um volume menor.

Pronto, você está pronto para copiar a string com os IDs para serem inseridos numa cláusula SQL IN. Fique sabendo também que exatamente a mesma formatação para lista do SQL IN serve para criar um array no PHP, basta recobrir com squared brackets [] ao invés de brackets ().

Pra finalizar quero citar que o mesmo resultado pode ser alcançado com editores de texto ou IDEs com o auxílio do mouse e bastante paciência.

Eu também sei que dá pra fazer isso com o editor de texto VI usando macro substituições, mas eu nunca fiz e não sei fazer, mas eu sei quem sabe!

Mas isso é assunto para outro artigo!

PHP ARRAY Parsing String Como Array Bidimensional no Hacker Rank

//Hacker Rank Input
//1 10 11 9 8 7 6 5 2 8 12, 7 58 2 34 76 88 2 5 3 8 11, 9 65 3 8 98 36 22 87 66 99 0

/* Read and parse input */
$handle = fopen ('php://stdin', 'r');
$string_array = fgets($handle);

$array = explode(', ', $string_array);

array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// do your job with bidimensional $array

Via de regra, quando estamos no ambiente Hacker Rank, para obtermos um array de um enunciado de um problema em PHP temos que fazer o parsing de uma string dada as características do input pelo STDIN.

As coisas podem se complicar bastante se o parsing não for bem executado, e é claro que com a pressão da competição, é o que geralmente acontece.

O problema não é do problema em si, por que depois que passa a competição e a pressão você olha pra ele e diz… PQP!

Então fica aí a dica, basta observar com calma, e principalmente observar os delimitadores. Neste caso o primeiro delimitador não é simplesmente uma vírgula, mas sim dois caracteres vírgula e espaço combinados.

# wrong delimiter applied on first level 
$array = explode(',', $string_array);

// [0]=> string(24) "1 10 11 9 8 7 6 5 2 8 12"
// [1]=> string(26) " 7 58 2 34 76 88 2 5 3 8 11"
// [2]=> string(29) " 9 65 3 8 98 36 22 87 66 99 0

Deste ponto em diante as coisas se complicam, pois apesar de termos um array unidimensional de três elementos, o próximo explode vai gerar o segundo nível com mais elementos do que o esperado por conter um espaço a mais.

# wrong quantity of elements on second level
array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// [0]=> array(11)
// [1]=> array(12)
// [2]=> array(12)

Agora não tem mais desculpas, essa é pra não esquecer mais.

Dang it!

PHP Criando DateTime com Construtor sem Máscaras de Formatação

$string_date = '2010-08-07 08:39:00';
[$y, $m, $d] = [null, null, null];

switch(true) {
  case preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $string_date, $fragments):
    list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{4})\/(\d{2})\/(\d{2})/', $string_date, $fragments):
    list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{2})-(\d{2})-(\d{4})/', $string_date, $fragments):
    list($d, $m, $y) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{2})\/(\d{2})\/(\d{4})/', $string_date, $fragments):
    list($m, $d, $y) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  default:
    throw new Exception('String Date supplied with unknown format');
}

if (!checkdate($m, $d, $y)) {
  throw new Exception ('String Date supplied with invalid date content');
}

try {
  $object_datetime = new DateTime($string_date);
} catch (\Exception $exception) {
  echo 'String Date supplied impossible to convert to Date';
  $object_datetime = null;
}

null !== $object_date
  ? $object_date->setTime(0,0)
  ; null;

var_dump($object_datetime);

Pessoal, este código surgiu da necessidade de se converter um campo de data fornecido como string numa integração entre sistemas.

O fato é que quando se depende de informações externas o processo pode facilmente quebrar quando as regras estabelecidas não forem estritamente respeitadas.

No meu caso um campo que deveria ser enviado no formato date Y-m-d acabou sendo enviado num formato DateTime Y-m-d H:i:s. O problema se agravou pelo fato de que apenas um dos fornecedores acabou enviando desta forma e os demais fornecedores enviavam a informação no formato acordado.

Dúvida cruel… solicitar ao fornecedor para corrigir o envio da informação? Ou flexibilizar a entrada do dados de forma inteligente?

Opção dois na veia! O fato é que todos os fornecedores continuavam enviando uma data válida pra nós, só que com formatos ou máscaras diferentes, mas ainda assim eram datas válidas.

Baseado neste preceito a solução foi substituir a criação do objeto DateTime calcada no createFromFormat a partir de máscaras previamente conhecidas por simplesmente passar a string date recebida para o construtor do DateTime.

// stop creating date based on known format, this will fail when datetime content is sent
$object_datetime = DateTime::createFromFormat('Y-m-d', $string_date);

// start creating datetime based on constructor, this will work with any valid date/time content
$object_datetime = new DateTime($string_date);

A grande sacada consiste em validar se uma data válida foi enviada, no caso fazemos esta validação para a porção Y-m-d somente. Se a validação passar neste nível a probabilidade da informação ser uma data válida inclusive na sua porção hora será muito grande.

Para exemplificar a flexibilidade de entrada desta informação, segue abaixo alguns exemplos de formatos válidos que irão funcionar com o construtor.

$object_datetime = new DateTime('2010-08');
var_dump($object_datetime); //2010-08-01 00:00:00.000000

$object_datetime = new DateTime('2010-08 08:39');
var_dump($object_datetime); //2010-08-01 08:39:00.000000

$object_datetime = new DateTime('2010-08 08:39:53');
var_dump($object_datetime); //2010-08-01 08:39:53.000000

$object_datetime = new DateTime('2010-08T08:39:53Z');
var_dump($object_datetime); //2010-08-01 08:39:53.000000

$object_datetime = new DateTime('2010-08-07');
var_dump($object_datetime); //2010-08-07 00:00:00.000000

$object_datetime = new DateTime('2010-08-07 08:39');
var_dump($object_datetime); //2010-08-07 08:39:00.000000

$object_datetime = new DateTime('2010-08-07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('2010-08-07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Considere ainda que podemos receber barras ao invés de hífen como separador que irá funcionar corretamente com todas as variações acima apresentadas.

$object_datetime = new DateTime('2010/08/07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('2010/08/07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Outra grande vantagem de se utilizar o construtor do DateTime é que ele vai funcionar com o formato nativo americano m/d/Y quando separado por barras e com o formato nativo britânico m-d-Y quando separado por hífens.

$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('07-08-2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Lembrando que em nenhum momento tivemos que informar máscaras de formatação para efetivamente criar o objeto DateTime. Simplesmente mandamos a string date para o construtor e pronto.

E caso a informação final deva ser uma data sem a porção hora, simplesmente zeramos esta porção depois do objeto criado, assim:

$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime->setTime(0,0);
var_dump($object_datetime); //2010-08-07 00:00:00.000000

Depois desta demonstração de flexibilidade de formatos acredito que as vantagens de criação do DateTime utilizando o construtor tenham ficado claras.

E é claro que o código do início do artigo merece um refactoring para ser mais CleanCode e também para suportar os demais formatos aceitos pelo construtor do DateTime, mas para fins didáticos preferi mantê-lo mais simples mesmo.

E agora? Troca ou não troca o DateTime::createFromFormat pelo new DateTime constructor? Você decide…

Happy code!

PHP Array Comparando Variáveis Escalares Combinadas num IF Único

$expected_email = "myself@email.com";
$expected_birthdate = "25/12/0001";

$received_email = "not matching email";
$received_birthdate = "not matching date";

if ([$received_email, $received_birthdate] != [$expected_email, $expected_birthdate]) {
    echo 'Expected combination Email/Birthdate does not match';
}

Olha só que legal, nunca tinha pensado em montar um IF como esses comparando uma lista de variáveis escalares com uma outra lista também de variáveis escalares.

Basicamente isto é uma comparação de arrays e o match se dá pela comparação de forma posicional, ou seja, índice 0 com índice 0 e índice 1 com índice 1.

Caso precise comparar inclusive os tipos de dados basta adicionar mais um igual a operador.

if ([$received_email, $received_birthdate] !== [$expected_email, $expected_birthdate]) {
    echo 'Expected combination Email/Birthdate does not match';
}

Qual a vantagem desse código sobre um código convencional? Não sei dizer muito bem qual seria…

if ($received_email != $expected_email || $received_birthdate != $expected_birthdate) {
    echo 'Expected combination Email/Birthdate does not match';
}

O mais importante é que funciona conforme o projetado.

Happy PHP!

PHP array_filter Verificando se Conteúdo Possui Suporte ao array_flip

static function hasArrayFlipSupport(array $array): bool {
   return empty(
      array_filter(
         $array,
         static function ($value, $key) {
            return (!is_string($value) && !is_int($value)) || (!is_string($key) && !is_int($key));
         },
         ARRAY_FILTER_USE_BOTH
      )
   );
}

Recentemente estive trabalhando (e ainda estou) num projeto de importação de arquivos de dados, uma rotina bastante comum ainda nos dias de hoje apesar de toda a evolução tecnológica que temos vivido.

Pois bem, com a elaboração de classes mais genéricas que devem processar uma variedade quase infinita de possibilidades as coisas se complicam um pouco e precisam de mais rotinas de garantia de funcionalidade.

No meu caso, em um dado momento o uso do array_flip se mostrou ideal, só precisava inverter o array tornando as valores como chaves e as chaves como valores… simples né? Só que não.

O array_flip somente suporta a operação utilizando dados do tipo STRING e INT, e essa regra vale para a dupla chave/valor do array. Lembrando que NULL também não é suportado para a operação.

Então eu elaborei este método para verificar se o conteúdo do array suportaria o array_flip. Basicamente verifica-se o conteúdo de todo o array, ambos chave e valor, se eles possuem os tipos de dados suportados.

E foi neste momento que o array_filter se mostrou parrudo e eficiente. Por padrão o array_filter avalia apenas o valor do elemento.

array_filter($array, static function($value) { return true; });

Mas você consegue facilmente mudar este comportamento para avaliar a chave simplesmente adicionando o terceiro parâmetro ARRAY_FILTER_USE_KEY.

array_filter($array, static function($key) { return true; }, ARRAY_FILTER_USE_KEY);

Mas no meu caso eu precisei mesmo avaliar ambos simultaneamente, assim o trunfo final foi informar o terceiro parâmetro ARRY_FILTER_USE_BOTH.

array_filter($array, static function($value, $key) { return true; }, ARRAY_FILTER_USE_BOTH);

Observe que, ao acionarmos o uso de ambos chave e valor, a sequência de de entrada da dupla chave/valor ocorre na ordem inversa valor/chave. Eu não tenho detalhes para explicar este comportamento, mas acredito que seja para manter uma certa compatibilidade com o uso padrão do array_filter, o qual filtra o valor por padrão.

Alguns irão dizer que estas características são sinais de ausência de padronização e arquitetura no PHP, mas para mim esse é o tremendo charme da linguagem!

World Class Technology S2 PHP!

PHP Array Combinando a Dupla Key Value como String

function getArgsAsString(array $args): string {
    return implode(', ', array_map(static function($v, $k){ return $k.'='.$v; }, $args, array_keys($args)));
}

Oxxê! Até o título deste artigo ficou estranho, mas primeiro vou explicar onde eu precisei usar isso pra ver se facilita o entendimento do porquê esse código pode ser útil pra você.

Quando escrevemos queries SQL em PHP é bastante comum fazermos o bind dos argumentos por nome associativo ao invés de posição. Essa técnica facilita não só a legibilidade do código como também ajuda a manter um código limpo e enxuto.

Pois bem, em um dado momento eu me recordo que precisei registrar um log da operação de SQL, daí além de registrar a string contendo a Query Statement, precisei também salvar os argumentos utilizados na operação. Foi neste momento que me dei conta de que o PHP não fornece uma solução nativa para este caso.

Rapidamente pensei nas funções nativas de parsing de Array do PHP, então o array_map caiu como uma luva. Lembrando que o array_map despreza os índices associativos do array original produzindo um novo array com índices sequenciais, mas como meu objetivo era produzir uma string com a combinação KEY=VALUE, eu não tive problemas com este fato.

Porém, é possível fazermos a mesma operação preservando os índices associativos originais do array, para isto basta usar a sintaxe abaixo com o comando array_walk.

array_walk($mapped_row, function(&$value, $key) {
    $value = sprintf('%s=%s', $value, $key);
}); 

$args_string = implode(', ', $args);

Lembrando que array_walk atua sobre o array original modificando o mesmo, diferentemente do array_map que produz um array completamente novo. Se você tiver problemas em modificar o seu array original crie então uma função passando o array como parâmetro.

Diferentemente de Objetos, os Arrays não são passados por referência por default, assim uma nova array será criada antes do parsing do array_walk, o que irá preservar o array original intacto.

function getArgsAsString(array $args): string {
    array_walk($args, function(&$value, $key) {
        $value = sprintf('%s=%s', $value, $key);
    });
    
    return implode(', ', $args);
}

Bom, fora este detalhe você pode ficar a vontade para utilizar a sintaxe que preferir… aproveite!

PHP Array Usando RESET para Extrair a Primeira Linha de um Array

$y[0] = [ 'col1' => 'A', 'col2' => 'B', 'col3' => 'C' ];
$y[1] = [ 'col1' => 'value1', 'col2' => 'value2', 'col3' => 'value3' ];
$y[2] = [ 'col1' => 'value4', 'col2' => 'value5', 'col3' => 'value6' ];
$y[3] = [ 'col1' => 'value7', 'col2' => 'value8', 'col3' => 'value9' ];

$header = reset($y);
var_dump($header);

// array(3) { 
//   ["col1"] => string(1) "A"
//   ["col2"] => string(1) "B"
//   ["col3"] => string(1) "C"
// }

Pode parecer uma tarefa super simples pegar o primeiro elemento de um array, e realmente é!!! Desde a semana passada o reset() é o meu jeito preferido de fazer isso.

Recentemente quando estive implementando a importação de um arquivo tipo planilha de dados naturalmente tive a necessidade de processar a primeira linha a qual continha o HEADER dos dados. Então me deparei com um código existente que me pareceu muito estranho a princípio, dá uma olhada nisso…

$mapped_headers = $this->map_headers(reset($spreadsheet_rows));

Para quem é familiar a este tipo de processamento sabe que estamos falando de um array bidimensional no melhor estilo linhas x colunas. O que eu não entendi foi aquele reset() ali na invocação do map_headers… que janho!!!

Como de praxe fui debugar e entender como a combinação funcionava… Para o meu espanto, somente agora eu entendi que o comando reset, além de resetar o ponteiro interno do array, retorna também o primeiro elemento do array.

Isto é fantástico!!! Eu sempre me perguntei por que o PHP não tinha um comando first() para pegar o primeiro elemento de um array já que temos o comando current() e o comando end().

Uma alternativa ao reset() seria inverter a array e usar o comando end(), mas acho que não tem necessidade de complicar o código com essa miscelânea pra atingir o mesmo resultado.

$r = array_reverse($y);
$header = end($r);
var_dump($header); 

// array(3) { 
//   ["col1"] => string(1) "A"
//   ["col2"] => string(1) "B"
//   ["col3"] => string(1) "C"
// }

Outra combinação possível seria combinar current() com reset(), mas também seria um desperdício já que o reset já faz o trabalho esperado.

reset($y);
$header = current($y);
var_dump($header); 

// array(3) { 
//   ["col1"] => string(1) "A"
//   ["col2"] => string(1) "B"
//   ["col3"] => string(1) "C"
// }

Outra alternativa segura de pegarmos o primeiro elemento da array seria fatiar o array com array_slice(), lembrando que este comando extrai o elemento como bidimensional preservando o índice da linha. Ainda assim teria que combinar com array_shift() ou array_pop() para pegar o primeiro elemento como array simples.

$first_row = array_slice($y, 0, 1);
var_dump($first_row); 

// array(1) {
//   [0]=>
//   array(3) {
//     ["col1"]=> string(1) "A"
//     ["col2"]=> string(1) "B"
//     ["col3"]=> string(1) "C"
//   }
// }

$header = array_shift($first_row);
var_dump($header); 

// array(3) { 
//   ["col1"] => string(1) "A"
//   ["col2"] => string(1) "B"
//   ["col3"] => string(1) "C"
// }

Pra finalizar uma última alternativa segura que podemos considerar seria utilizar array_values(). Sabemos que a primeira linha do array representam os headers não importa qual é a chave utilizada na indexação destas linhas. Então array_values() garante que o índice da primeira linha seja ZERO através do re-sequenciamento do array sem alterar a ordem de seus elementos.

$y['Z'] = [ 'col1' => 'A', 'col2' => 'B', 'col3' => 'C' ];
$y['X'] = [ 'col1' => 'value1', 'col2' => 'value2', 'col3' => 'value3' ];
$y['S'] = [ 'col1' => 'value4', 'col2' => 'value5', 'col3' => 'value6' ];
$y['J'] = [ 'col1' => 'value7', 'col2' => 'value8', 'col3' => 'value9' ];

$header = array_values($y)[0];
var_dump($header); 

// array(3) { 
//   ["col1"] => string(1) "A"
//   ["col2"] => string(1) "B"
//   ["col3"] => string(1) "C"
// }

Não exploramos outras alternativas como loop foreach() e for() mas nem precisa, com o PHP certamente encontraremos pelo menos mais meia dúzia de outras formas para atingir o mesmo resultado.

Agora convenhamos, o reset() arrasou!

PHP Executar Dinamicamente Métodos Estáticos de uma Classe

class MyClass {
  public static function myFunc(bool $happy) {
    echo "\n";
    echo $happy ? 'I am happy' : 'I am sad';
  }
}

$class = MyClass::class;
$method = 'myFunc';
$arg = true;

$class::{$method}($arg);

Recentemente me deparei com uma situação inusitada… executar métodos estáticos de uma classe de forma dinâmica!

Ao escrever um teste unitário resolvi preparar a suite com Data Provider então naturalmente as opções deste data provider requeriam diferentes métodos para diferentes parâmetros, daí a solução natural foi parametrizar o próprio método como um dos parâmetros… na execução dos testes bastaria simplesmente chamar o método como fazemos com classes instanciáveis, certo?

Pois é, só que ao errar a sintaxe de execução eu acabei indo buscar artigos que explicassem como fazer isso com métodos estáticos e acabei indo para um caminho utilizando o call_user_func.

call_user_func([$class, $method], $arg);

A solução funciona bem e até tem uma sintaxe bastante limpa e bonita, nada contra, mas até o momento não encontrei necessidade real de lançar uso desta função.

Depois encontrei no próprio repositório outras implementações utilizando o call_user_func, só que aplicando uma sintaxe um pouco diferente concatenando a classe e método num único parâmetro.

call_user_func($class . '::' . $method, $arg);

Bom, considero a sintaxe do primeiro bloco a mais correta de ser aplicada como solução para este caso, afinal num mundo competitivo como o nosso, elegância é tudo!

World class technology S2 PHP.

PHP Higienizando Variáveis com Operador Elvis ?:

function defaultValues(int $index, string $sku, string $code, string name, int $quantity, float $price)
    $index = $index ?: 1;
    $sku = $sku ?: 'NOT INFORMED';
    $code = $code ?: 'NOT INFORMED';
    $name = $name ?: 'NOT INFORMED';
    $quantity = $quantity ?: 0;
    $price = $price ?: 0.0;
}

Há algum tempo eu vinha observando em meus códigos um uso excessivo do operador ternário para limpeza e padronização de variáveis, dai recentemente eu dei uma olhada com mais carinho no operador Elvis. Em alguns artigos eu encontrei explicações de que ele seria a parte FALSE do operador ternário, mas eu nunca tinha aplicado ele na prática de uma forma tão contundente.

Então surgiu uma nova oportunidade e fui olhar a documentação novamente e “voila”. Não é que serviu direitinho pra fazer higienização de variáveis?!

Mas por que não usar o operador ternário? Porque a higienização requer uma ação apenas e o ternário exige duas.

E o operador Null Coalesce? Bom, o null coalesce testa somente nulls e no meu caso conteúdo vazio também precisaria ser higienizado.

A melhor opção então recaiu sobre o operador Elvis, por que ele executa ação para todo e qualquer conteúdo avaliado como FALSEY. Grosseiramente falando o FALSEY equivale a qualquer conteúdo convertido para boolean que resulta em falso… veja os exemplos abaixo:

var_dump(null ?: 'null is falsey');      // "null is falsey"
var_dump(true ?: 'true is truthy');      // bool(true)
var_dump(false ?: 'false is falsey');    // "false is falsey"
var_dump('' ?: 'empty string is falsey');// "empty string is falsey"
var_dump(' '?: 'space is truthy');       // " "
var_dump(empty(null) ?: 'empty null is truthy'); // bool(true)
var_dump(0 ?: 'zero int is falsey');     // "zero int is falsey"
var_dump(0.0 ?: 'zero float is falsey'); // "zero float is falsey"

Agora compare os resultados com o NULL COALESCE…

var_dump(null ?? 'null is coalesced');        // "null is coalesced"
var_dump(true ?? 'true is not coalesced');    // bool(true)
var_dump(false ?? 'false is not coalesced');  // bool(false)
var_dump('' ?? 'empty string is not coalesced'); // ""
var_dump(' ' ?? 'space is not coalesced');       // " "
var_dump(empty(null) ?? 'empty null is not coalesced'); // bool(true)
var_dump(0 ?? 'zero is not coalesced');         // int(0)
var_dump(0.0 ?? 'zero float is not coalesced'); // float(0)

Claramente a escolha correta é usar o operador Elvis para a higienização pois nulls, zeros integer or float, boolean false, empty string, todos sem excessão irão disparar a ação para higienização, evitando assim o uso de ifs e outros testes desnecessários.

Happy CleanCode!

Elvis Operator