source_url | revision | status | license |
---|---|---|---|
1718313707 |
ready |
- Versão: 0.11.0
- Data: 21/04/2024
- Pessoas autoras: Niels Dossche [email protected]
- Situação: Em votação
- Publicada pela primeira vez em: https://wiki.php.net/rfc/xmlreader_writer_streams
As classes XMLReader
e
XMLWriter
lidam com XML de maneira
orientada a fluxo.
A primeira implementa um analisador pull XML.
Isso significa que, em vez de manter os dados na memória ou construir a árvore
do documento, o documento é transmitido e a pessoa desenvolvedora pode instruir
a classe XMLReader
a analisar
fragmentos no cursor atual e processar ou ignorar os dados.
A vantagem é que pessoas desenvolvedoras podem processar e filtrar documentos
grandes exigindo poucos recursos.
Ela é mais frequentemente usada como um bloco de construção de baixo nível para
manipulação mais complexa de grandes documentos XML.
Da mesma forma, a classe XMLWriter
grava um documento XML em um fluxo ou na memória usando métodos como
startElement
e
writeElement
.
Há, no entanto, uma estranha limitação para estas classes: elas não podem operar
em um fluxo já aberto!
Isso é bizarro, pois as APIs (tanto internas quanto voltadas para a pessoa
desenvolvedora) são orientadas a fluxo.
Fluxos que já estão abertos são comuns ao trabalhar, por exemplo, com
requisições HTTP, dados passados de um framework ou apenas dados XML
incorporados em um fluxo existente.
A falta de uma API que funcione com fluxos já abertos faz com que as pessoas
desenvolvedoras dependam de soluções alternativas, por exemplo, lendo o fluxo
inteiramente para a memória e depois usando as APIs da classe
XMLReader
, ou escrevendo um arquivo
XML usando a classe XMLWriter
e tendo
que passá-lo para um fluxo já aberto.
Isso é um desperdício e desnecessariamente difícil.
Esta RFC visa corrigir esse problema e também corrigir algumas outras
inconsistências.
Proponho adicionar dois novos métodos, um à classe
XMLReader
e outro à classe
XMLWriter
, para criar uma instância a
partir de um fluxo.
Aqui está como eles seriam:
class XMLReader {
/** @param resource $stream */
public static function fromStream($stream, ?string $encoding = null, int $flags = 0, ?string $documentUri = null): static {}
}
class XMLWriter {
/** @param resource $stream */
public static function toStream($stream): static {}
}
As assinaturas são fortemente inspiradas no método existente
public static XMLReader::open(string $uri, ?string $encoding = null, int $flags = 0): bool|XMLReader
que opera em arquivos.
No entanto, uma grande diferença é que XMLReader::fromStream()
é somente
estático, enquanto os outros métodos de abertura da classe
XMLReader
podem ser chamados
estaticamente ou não estaticamente e alterar o comportamento do valor de retorno
dependendo disso.
A desvantagem dos métodos estáticos existentes é que eles só podem retornar uma
instância XMLReader
, portanto quando
XMLReader
é herdada por uma subclasse
da pessoa desenvolvedora, nos deparamos com o problema de ela não retornar um
objeto do tipo correto.
Resolvemos isso escolhendo static
como tipo de retorno e deixando o método
chamar internamente o construtor do tipo estático (sem argumentos).
Como parece que estamos nos afastando dos métodos sobrecarregados, decidi
disponibilizar apenas a variante estática do método.
O parâmetro $documentUri
é usado principalmente quando a libxml
gera
mensagens de erro, de modo que você pode colocar um nome de origem lá.
A assinatura do método XMLWriter::toStream()
deve ser autoexplicativa.
Ele também foi modelado como os outros métodos de abertura, mas é
consideravelmente mais simples.
Você também notará a ausência de um argumento de codificação, porque isso já é
tratado pelo método
XMLWriter::startDocument()
.
Ao implementar essa RFC, encontrei alguns comportamentos estranhos em relação ao
parâmetro ?string $encoding
dos métodos existentes
XMLReader::open()
e
XMLReader::XML()
.
O primeiro comportamento estranho é que eles emitem um alerta em vez de lançar
um ValueError
quando a codificação
contém bytes NULL
.
Isso é inconsistente com como outros métodos lidam com isso.
Proponho promover este alerta para
ValueError
.
O segundo comportamento estranho é que nomes de codificação inválidos são
totalmente ignorados.
Isso significa que eles não emitirão um alerta nem nada, apenas silenciosamente
não definirão a codificação.
Isso pode esconder bugs.
Proponho também lançar um ValueError
neste caso informando
Argument #X ($encoding) must be a valid character encoding
.
Uma versão anterior desta RFC propunha adicionar métodos openStream()
a ambas
as classes, mas a nomenclatura não era ideal e o comportamento de ser um método
de instância não era apreciado.
Portanto, isso foi alterado para métodos somente estáticos que retornam uma
instância da respectiva classe.
Estamos adicionando novos construtores nomeados estáticos às classes
XMLWriter
e
XMLReader
.
No entanto, XMLWriter
ainda não possui
construtores estáticos e XMLReader
possui esses métodos híbridos estáticos e de instância de que falamos
anteriormente.
Esses métodos existentes também não podem ser usados em subclasses porque
retornam XMLWriter
ou
XMLReader
em vez de
static
.
A ideia é adicionar os seguintes construtores nomeados estáticos para consistência com os novos métodos propostos, com os mesmos argumentos de sua contraparte existente:
XMLReader::fromUrl(string $url, ?string $encoding = null, int $flags = 0): static
como uma nova versão deXMLReader::open(...)
XMLReader::fromString(string $source, ?string $encoding = null, int $flags = 0): static
como uma nova versão deXMLReader::XML(...)
XMLWriter::toMemory(): static
como uma nova versão deXMLWriter::openMemory(...)
XMLWriter::toUrl(string $url): static
como uma nova versão deXMLWriter::openUri(...)
Observe que usei Url
aqui em vez de Uri
porque esse é o termo mais preciso:
ele realmente localiza o recurso em vez de apenas identificá-lo.
Isso não visa descontinuar nenhum método existente. Iremos apenas atualizar a documentação para direcionar as pessoas desenvolvedoras aos novos construtores.
Existem três quebras menores de compatibilidade com versões anteriores.
A primeira é o fato de estarmos adicionando novos métodos.
Se uma pessoa desenvolvedora estender a classe
XMLReader
ou
XMLWriter
e sua classe implementar um
método com o mesmo nome, mas com uma assinatura incompatível, ocorrerá um erro
de compilação.
Analisei os 2.500 principais pacotes do Composer e nenhum usou nenhum dos nomes
de métodos propostos nas subclasses das classes
XMLReader
ou
XMLWriter
.
Isso significa que os 2.500 principais pacotes não sofrem quebra de
compatibilidade com versões anteriores por causa disso.
Isso não significa que não haverá nenhum, mas dá uma boa indicação.
A segunda quebra de compatibilidade com versões anteriores é causada pelo
lançamento de um ValueError
ao usar
codificações inválidas, em vez de ignorá-las silenciosamente.
Se não sinalizarmos a codificação inválida de alguma forma para a pessoa
desenvolvedora, isso poderá ocultar falhas sutilmente.
Por exemplo, isso pode ocultar erros de digitação ou passar silenciosamente
entradas inválidas da pessoa desenvolvedora para os respectivos métodos.
Forçar as pessoas desenvolvedoras a lidar explicitamente com esse erro resultará
em um código mais robusto no final.
A terceira quebra de compatibilidade com versões anteriores é a promoção do
alerta de byte NULL
para um
ValueError
.
Isso torna as classes XMLReader
e
XMLWriter
mais consistentes com outras
extensões que lançam erros em vez de emitir um alerta.
A migração para pessoas desenvolvedoras deve ser bastante simples: em vez de
silenciar o alerta e/ou verificar o valor de retorno do método, elas devem usar
uma construção try-catch
para
tratar o erro.
// Poderia ser qualquer fluxo, mas isso é para simplificar.
$h = fopen("php://memory", "w+");
fwrite($h, "<root><!--meu comentário--><child/></root>");
fseek($h, 0);
$reader = XMLReader::fromStream($h);
while ($reader->read()) {
switch ($reader->nodeType) {
case XMLReader::ELEMENT:
echo "Elemento: ", $reader->name, "\n";
break;
case XMLReader::COMMENT:
echo "Comentário: ", $reader->value, "\n";
break;
}
}
// Poderia ser qualquer fluxo, mas isso é para simplificar.
$h = fopen("php://output", "w");
$writer = XMLWriter::toStream($h);
$writer->startElement("root");
$writer->writeAttribute("align", "left");
$writer->writeComment("olá");
$writer->endElement();
$amount = $writer->flush();
echo "\nQuantidade de bytes gravados: ";
var_dump($amount);
Próximo PHP 8.x, é o PHP 8.4 no momento em que esta RFC foi escrita.
Somente ext/xmlreader
e ext/xmlwriter
serão afetadas.
Nenhuma ainda.
O resto, por que temos esta seção?
Nenhum ainda.
Duas votações primárias, cada uma exigindo maioria de 2/3: uma para a proposta principal e outra para a proposta de consistência.
A votação começou em 13/06/2024 e terminará em 28/06/2024.
Nome | Sim | Não |
---|---|---|
adiel (adiel) | X | |
ashnazg (ashnazg) | X | |
crell (crell) | X | |
girgias (girgias) | X | |
jimw (jimw) | X | |
kguest (kguest) | X | |
mbeccati (mbeccati) | X | |
nielsdos (nielsdos) | X | |
ocramius (ocramius) | X | |
theodorejb (theodorejb) | X | |
Total | 10 | 0 |
Nome | Sim | Não |
---|---|---|
adiel (adiel) | X | |
ashnazg (ashnazg) | X | |
crell (crell) | X | |
galvao (galvao) | X | |
girgias (girgias) | X | |
jimw (jimw) | X | |
kguest (kguest) | X | |
mbeccati (mbeccati) | X | |
nielsdos (nielsdos) | X | |
ocramius (ocramius) | X | |
theodorejb (theodorejb) | X | |
Total | 11 | 0 |
PR da implementação: php/php-src#14030
Depois que o projeto for implementado, esta seção deverá conter:
- as versões nas quais foi feito o merge
- um link para os commits do git
- um link para a entrada no manual do PHP para o recurso
- um link para a seção de especificação da linguagem (se houver)
Nenhum ainda.
- 0.11.0: Incorporou feedback sobre métodos estáticos.
- 0.10.1: Correções de linguagem.
- 0.10.0: Estático novamente.
- 0.9.2: Adicionou exemplos de uso das novas APIs.
- 0.9.1: Tornou
XMLReader::openStream()
não estático, para funcionar com classes sobrescritas. - 0.9: Versão inicial em discussão.