Files
archived-doc-pt-br/language/oop5/basic.xml
2024-04-22 22:25:13 -03:00

840 lines
21 KiB
XML
Executable File

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: f94d903985119d3ac00f4528551df947f57b667f Maintainer: ae Status: ready --><!-- CREDITS: ae,amandavale,narigone,fabioluciano,lhsazevedo -->
<sect1 xml:id="language.oop5.basic" xmlns="http://docbook.org/ns/docbook">
<title>O básico</title>
<sect2 xml:id="language.oop5.basic.class">
<title>class</title>
<para>
A definição de uma classe começa com a
palavra chave <literal>class</literal>, seguida do nome da classe,
seguido de um par de chaves que englobam as definições
de propriedades e métodos pertencentes à classe.
</para>
<para>
O nome de uma classe tem de ser válido, que não seja
uma <link linkend="reserved">palavra reservada do PHP</link>. Um nome de classe válido
começa com uma letra ou sublinhado, seguido de qualquer sequência de
letras, números e sublinhados. Como uma expressão regular,
pode ser expressada assim:
<code>^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$</code>.
</para>
<para>
Uma classe pode conter suas
próprias <link linkend="language.oop5.constants">constantes</link>, <link linkend="language.oop5.properties">variáveis</link>
(chamadas de "propriedades") e funções (chamadas de "métodos").
</para>
<example>
<title>Definição Simples de uma Classe</title>
<programlisting role="php">
<![CDATA[
<?php
class SimpleClass
{
// declaração de propriedade
public $var = 'um valor padrão';
// declaração de método
public function displayVar() {
echo $this->var;
}
}
?>
]]>
</programlisting>
</example>
<para>
A pseudo-variável <varname>$this</varname> está disponível quando um
método é chamado a partir de um contexto de um objeto.
<varname>$this</varname> é o valor do objeto chamado.
</para>
<warning>
<para>
Chamar um método não estático de maneira estática lança um
<classname>Error</classname>.
Anteriormente ao PHP 8.0.0, isto iria gerar um aviso de descontinuação,
e <varname>$this</varname> estaria indefinido.
</para>
<example xml:id="language.oop5.basic.class.this">
<title>Alguns exemplos da pseudo variável <varname>$this</varname></title>
<programlisting role="php">
<![CDATA[
<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this está definida (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this não está definida.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
]]>
</programlisting>
&example.outputs.7;
<screen>
<![CDATA[
$this está definida (A)
Deprecated: Non-static method A::foo() should not be called statically in %s on line 27
$this não está definida.
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this não está definida.
Deprecated: Non-static method B::bar() should not be called statically in %s on line 32
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this não está definida.
]]>
</screen>
&example.outputs.8;
<screen>
<![CDATA[
$this está definida (A)
Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
Stack trace:
#0 {main}
thrown in %s on line 27
]]>
</screen>
</example>
</warning>
<sect3 xml:id="language.oop5.basic.class.readonly">
<title>Classes somente leitura</title>
<para>
A partir do PHP 8.2.0, uma classe pode ser marcada com o modificador
<modifier>readonly</modifier>.
Marcar uma classe como <modifier>readonly</modifier> irá acrescentar o modificador
<link linkend="language.oop5.properties.readonly-properties"><modifier>readonly</modifier></link>
em cada propriedade declarada, e prevenir a criação de
<link linkend="language.oop5.properties.dynamic-properties">propriedades dinâmicas</link>.
Além disso será impossível de acrescentar suportes a propriedades dinâmicas utilizando o atributo
<classname>AllowDynamicProperties</classname>. Acrescentar o atributo em classes somente leitura
irá disparar um erro de compilação.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
#[\AllowDynamicProperties]
readonly class Foo {
}
// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
?>
]]>
</programlisting>
</informalexample>
<para>
Como as propriedades estáticas ou sem tipo não podem ser marcadas com o
modificador <literal>readonly</literal>, as classes somente leitura também não podem
declará-las:
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
readonly class Foo
{
public $bar;
}
// Fatal error: Readonly property Foo::$bar must have type
?>
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
readonly class Foo
{
public static int $bar;
}
// Fatal error: Readonly class Foo cannot declare static properties
?>
]]>
</programlisting>
</informalexample>
<para>
Uma classe <modifier>readonly</modifier> pode ser
<link linkend="language.oop5.basic.extends">estendida</link>
se, e somente se, a classe filha também ser uma classe
<modifier>readonly</modifier>.
</para>
</sect3>
</sect2>
<sect2 xml:id="language.oop5.basic.new">
<title>new</title>
<para>
Para criar uma instância de uma classe, a instrução <literal>new</literal> deve
ser utilizada. Um objeto sempre será criado a não ser que a classe tenha um
<link linkend="language.oop5.decon">construtor</link> definido que dispare uma
<link linkend="language.exceptions">exceção</link> em caso de erro. Classes
devem ser definidas antes de instanciadas (e em alguns casos isso é
obrigatório).
</para>
<para>
Se uma variável contendo uma <type>string</type> com o nome da classe for utilizado com
<literal>new</literal>, uma nova instância da classe será criada. Se
a classe estiver dentro de um namespace, seu nome qualificado completo deve ser utilizado
ao fazer isto.
</para>
<note>
<para>
Se não há argumentos a serem passados para o construtor da classe,
os parênteses após o nome da classe podem ser omitidos.
</para>
</note>
<example>
<title>Criando uma instância</title>
<programlisting role="php">
<![CDATA[
<?php
$instance = new SimpleClass();
// Também pode ser feito com uma variável:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
]]>
</programlisting>
</example>
<para>
A partir do PHP 8.0.0, usar <literal>new</literal> com expressões arbitrárias
é suportado. Isso permite instanciação mais complexa se a expressão
produzir uma <type>string</type>. As expressões devem ser envolvidas em parênteses.
</para>
<example>
<title>Criando uma instância usando uma expressão arbitrária</title>
<para>
No exemplo fornecido mostramos múltiplos exemplos de expressões arbitrárias válidas que produzem um nome de classe.
Isto mostra uma chamada para uma função, concatenação de string, e a constante <constant>::class</constant>.
</para>
<programlisting role="php">
<![CDATA[
<?php
class ClasseA extends \stdClass {}
class ClasseB extends \stdClass {}
class ClasseC extends ClasseB {}
class ClasseD extends ClasseA {}
function obterAlgumaClasse(): string
{
return 'ClasseA';
}
var_dump(new (obterAlgumaClasse()));
var_dump(new ('Classe' . 'B'));
var_dump(new ('Classe' . 'C'));
var_dump(new (ClasseD::class));
?>
]]>
</programlisting>
&example.outputs.8;
<screen>
<![CDATA[
object(ClasseA)#1 (0) {
}
object(ClasseB)#1 (0) {
}
object(ClasseC)#1 (0) {
}
object(ClasseD)#1 (0) {
}
]]>
</screen>
</example>
<para>
No contexto da classe, é possível criar um novo objeto com
<literal>new self</literal> e <literal>new parent</literal>.
</para>
<para>
Ao atribuir uma instância de uma classe já criada, a uma variável nova,
a variável nova irá acessar a mesma instância do objeto que foi atribuído. Este
comportamento se mantém ao se passar instâncias a uma função. Uma cópia
de um objeto criado pode ser feita
<link linkend="language.oop5.cloning">clonando</link> o mesmo.
</para>
<example>
<title>Atribuição de Objetos</title>
<programlisting role="php">
<![CDATA[
<?php
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->var = '$assigned terá esse valor';
$instance = null; // $instance e $reference tornam-se nulos
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
NULL
NULL
object(SimpleClass)#1 (1) {
["var"]=>
string(30) "$assigned terá esse valor"
}
]]>
</screen>
</example>
<para>
É possível usar algumas formas de criar instâncias de um objeto:
</para>
<example>
<title>Criando novos objetos</title>
<programlisting role="php">
<![CDATA[
<?php
class Test
{
public static function getNew()
{
return new static();
}
}
class Child extends Test {}
$obj1 = new Test(); // Usando o nome da classe
$obj2 = new $obj1(); // Usando a variável que contém o objeto
var_dump($obj1 !== $obj2);
$obj3 = Test::getNew(); // Usando o método da classe
var_dump($obj3 instanceof Test);
$obj4 = Child::getNew(); // Usando o método através da classe filha
var_dump($obj4 instanceof Child);
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
bool(true)
bool(true)
bool(true)
]]>
</screen>
</example>
<para>
É possível acessar um membro do objeto recém criado
em uma expressão simples:
</para>
<example>
<title>Acessando um membro de um novo objeto criado</title>
<programlisting role="php">
<![CDATA[
<?php
echo (new DateTime())->format('Y');
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
2016
]]>
</screen>
</example>
<note>
<simpara>
Anteriormente ao PHP 7.1, os argumentos não são avaliados se não houver
um construtor definido.
</simpara>
</note>
</sect2>
<sect2 xml:id="language.oop5.basic.properties-methods">
<title>Propriedades e métodos</title>
<para>
Propriedades e métodos de classe vivem em "namespaces" separados, de forma que é
possível ter uma propriedade e método com mesmos nomes. A referência a
propriedades e métodos tem a mesma notação, e a decisão de se uma propriedade será acessada
ou uma chamada a um método feita, depende somente do contexto,
ou seja, se está tentando acessar uma variável ou chamar um método.
</para>
<example>
<title>Acesso a propriedade vs. chamar um método</title>
<programlisting role="php">
<![CDATA[
<?php
class Foo
{
public $bar = 'propriedade';
public function bar() {
return 'métod';
}
}
$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
propriedade
método
]]>
</screen>
</example>
<para>
Isto significa que chamar diretamente uma <link linkend="functions.anonymous">função
anônima</link> atribuída a uma propriedade não é possível.
Em vez disso, por exemplo, a propriedade deve primeiro ser atribuída a uma
variável. É possível chamar uma propriedade diretamente
colocando-a entre parênteses.
</para>
<example>
<title>Chamando uma função anônima armazenada em uma propriedade</title>
<programlisting role="php">
<![CDATA[
<?php
class Foo
{
public $bar;
public function __construct() {
$this->bar = function() {
return 42;
};
}
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL;
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
42
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.basic.extends">
<!-- TODO Example about class constant redefinition -->
<!-- TODO Split into it's own page? -->
<title>extends</title>
<para>
Uma classe pode herdar constantes, métodos e propriedades de outra classe usando a
palavra-chave <literal>extends</literal> na declaração
da classe. Não é possível herdar múltiplas classes; uma
classe só pode herdar uma classe base.
</para>
<para>
Os métodos e propriedades herdados podem ser sobrescritos
declarando-os com o mesmo nome definido na classe
base. Entretanto, se a classe mãe definiu um método ou constante
como <link linkend="language.oop5.final">final</link>,
eles não poderão ser sobrescritos. É possível acessar os métodos sobrescritos
ou propriedades estáticas referenciado-os
com <link linkend="language.oop5.paamayim-nekudotayim">parent::</link>.
</para>
<note>
<simpara>
A partir do PHP 8.1.0, constantes podem ser declaradas como finais.
</simpara>
</note>
<example>
<title>Herança simples de classe</title>
<programlisting role="php">
<![CDATA[
<?php
class ExtendClass extends SimpleClass
{
// Redefine o método pai
function displayVar()
{
echo "Classe Herdeira\n";
parent::displayVar();
}
}
$extended = new ExtendClass();
$extended->displayVar();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Classe Herdeira
um valor padrão
]]>
</screen>
</example>
<sect3 xml:id="language.oop.lsp">
<title>Regras de compatibilidade de assinaturas</title>
<para>
Ao sobrescrever um método, sua assinatura precisa ser compatível com
a do método original. Caso contrário um erro fatal é emitido, ou, antes do PHP 8.0.0, um alerta
<constant>E_WARNING</constant> seria gerado.
Uma assinatura é compatível se ela respeita as regras de
<link linkend="language.oop5.variance">variância</link>, se ela transforma um
parâmetro obrigatório em opcional, se ela adiciona novos parâmetros opcionais,
e se ela não restringe ou apenas aumenta a visibilidade.
Isso é conhecido como Princípio de Substituição de Liskov, ou LSP.
O <link linkend="language.oop5.decon.constructor">construtor</link>,
e membros <literal>private</literal> não precisam seguir essas regras de
compatibilidade, e portanto não há emissão de erros fatais no caso
de assinaturas incompatíveis.
</para>
<example>
<title>Métodos compatíveis</title>
<programlisting role="php">
<![CDATA[
<?php
class Base
{
public function foo(int $a) {
echo "Valid\n";
}
}
class Extend1 extends Base
{
function foo(int $a = 5)
{
parent::foo($a);
}
}
class Extend2 extends Base
{
function foo(int $a, $b = 5)
{
parent::foo($a);
}
}
$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Valid
Valid
]]>
</screen>
</example>
<para>
Os exemplos a seguir demonstram que um método derivado, que remove um parâmetro ou transforma um parâmetro opcional
em mandatório, não é compatível com o método original.
</para>
<example>
<title>Erro fatal quando um método derivado remove um parâmetro</title>
<programlisting role="php">
<![CDATA[
<?php
class Base
{
public function foo(int $a = 5) {
echo "Válido?\n";
}
}
class Extend extends Base
{
function foo()
{
parent::foo(1);
}
}
]]>
</programlisting>
&example.outputs.8.similar;
<screen>
<![CDATA[
Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13
]]>
</screen>
</example>
<example>
<title>Erro fatal quando um método derivado transforma um parâmetro opcional em mandatório</title>
<programlisting role="php">
<![CDATA[
<?php
class Base
{
public function foo(int $a = 5) {
echo "Válido?\n";
}
}
class Extend extends Base
{
function foo(int $a)
{
parent::foo($a);
}
}
]]>
</programlisting>
&example.outputs.8.similar;
<screen>
<![CDATA[
Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
]]>
</screen>
</example>
<warning>
<para>
Renomear um parâmetro em uma classe derivada não é uma quebra
de assinatura. Entretanto isso é desencorajado porque ocasionará um
<classname>Error</classname> se
<link linkend="functions.named-arguments">argumentos nomeados</link>
forem utilizados na chamada.
</para>
<example>
<title>Erro ao utilizar argumentos renomeados em uma classe derivada</title>
<programlisting role="php">
<![CDATA[
<?php
class A {
public function test($foo, $bar) {}
}
class B extends A {
public function test($a, $b) {}
}
$obj = new B;
// Passando parâmetros de acordo com o contrato de A::test()
$obj->test(foo: "foo", bar: "bar"); // ERROR!
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
thrown in /in/XaaeN on line 14
]]>
</screen>
</example>
</warning>
</sect3>
</sect2>
<sect2 xml:id="language.oop5.basic.class.class">
<title>::class</title>
<para>
A palavra-chave <literal>class</literal> também pode ser utilizada para resolução
de nome de classes.
Pode-se obter o nome completo e qualificado da classe <literal>ClassName</literal>
utilizando <literal>ClassName::class</literal>. Isso é particularmente útil em classes com
<link linkend="language.namespaces">namespaces</link>.
</para>
<para>
<example xml:id="language.oop5.basic.class.class.name">
<title>Resolução de nome da classe</title>
<programlisting role="php">
<![CDATA[
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
NS\ClassName
]]>
</screen>
</example>
</para>
<note>
<para>A resolução do nome de classe através de <literal>::class</literal> é
uma transformação em tempo de compilação. Isso significa que no momento em que o texto do nome
da classe é criado, o auto carregamento ainda não ocorreu. Como consequência, nomes de classe
são expandidos mesmo se a classe não existir. Não é emitido erro nestes
casos.
</para>
<example xml:id="language.oop5.basic.class.class.fail">
<title>Resolução de um nome de classe ausente</title>
<programlisting role="php">
<![CDATA[
<?php
print Does\Not\Exist::class;
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Does\Not\Exist
]]>
</screen>
</example>
</note>
<para>
A partir do PHP 8.0.0, a constante <literal>::class</literal> também pode ser utilizada em
objetos. Essa resolução acontece em tempo de execução, e não em tempo de compilação. O efeito é
o mesmo de chamar <function>get_class</function> em um objeto.
</para>
<example xml:id="language.oop5.basic.class.class.object">
<title>Resolução de nome de objeto</title>
<programlisting role="php">
<![CDATA[
<?php
namespace NS {
class ClassName {
}
}
$c = new ClassName();
print $c::class;
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
NS\ClassName
]]>
</screen>
</example>
</sect2>
<sect2 xml:id="language.oop5.basic.nullsafe">
<title>Métodos e propriedades nullsafe</title>
<para>
A partir do PHP 8.0.0, propriedades e métodos também podem ser acessados com o operador
"nullsafe": <literal>?-></literal>. O operador nullsafe
funciona da mesma forma que os acessos de métodos e variáveis, exceto que se
o objeto referenciado é &null;, então &null;
será retornado ao invés de uma exceção ser lançada. Se uma derreferência for parte de uma
cadeia de chamadas, o resto da cadeia é ignorado.
</para>
<para>
O resultado é similar a encapsular cada acesso dentro de um teste <function>is_null</function>,
mas mais compacto.
</para>
<para>
<example>
<title>Operador nullsafe</title>
<programlisting role="php">
<![CDATA[
<?php
// A partir do PHP 8.0.0, esta linha:
$result = $repository?->getUser(5)?->name;
// É o equivalente ao seguinte código:
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);
if (is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}
?>
]]>
</programlisting>
</example>
</para>
<note>
<para>
O operador nullsafe é melhor utilizado quando null é considerado um valor válido
e esperado de uma propriedade ou retorno de método. Para indicar um erro,
uma exceção lançada é preferível.
</para>
</note>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->