PHP Programación Orientada a obxectos
Sumario
Introdución
- Na actualidade empréganse fundamentalmente dúas formas de programación:
- Programación estruturada
- Programación orientada a obxectos.
- A programación estruturada baséase na utilización de 3 estruturas básicas: Secuencial, Condicional e Repetitiva.
- En base a estas 3 estruturas podemos 'programar' calquera solución informática.
- É un tipo de programación 'orientada' ao procedemento. Programamos pensando nos procesos que imos que ter que aplicar aos datos dos que dispoñemos, para que en base a unha entrada produzamos unha saída.
- Vexamos un exemplo.
- Imaxinemos que queremos programar unha solución informática para xestionar as contas bancarias.
- Partimos de que temos uns datos: un número de conta, un saldo, un titular, unha dirección.
- Creamos un programa no que definimos globalmente os datos, por exemplo en forma de array, no que cada elemento do array vai ser unha conta cos datos anteriores. Podemos ler ditos datos dende unha base de datos un dun arquivo.
- Isto o faríamos en primeiro lugar utilizando unha estrutura secuencial.
- Creamos os procedementos necesarios para tratar ditos datos, pensando no tratamentos que imos dar a ditos datos:
- Procedemento de ingresarCartos, ao que mandaríamos como parámetro un número de conta e uns cartos. Buscaríamos dito número de conta no array e aumentaríamos o seus ingresos.
- Procedemento de retirarCartos, ao que mandaríamos como parámetro un número de conta e uns cartos. Buscaríamos dito número de conta no array e diminuímos o seus ingresos.
- ....
- A programación orientada a obxectos, denominada POO, basease no uso de clases e obxectos (instancias de clase) para dar unha solución informática a un problema.
- É un tipo de programación 'orientada' ao dato. Programamos pensando na información que dispoñemos e 'modelizamos' ditos datos con clases.
- Unha clase vai estar formada por 'variables' ('atributos na POO') e 'procedementos' ('métodos na POO') para tratar os datos gardados nas variables. Se temos unha fábrica de galletas, a clase Galleta sería o molde da galleta. Có molde podemos crear todas as galletas, pero o molde non é ningunha galleta en particular.
- O obxecto dunha clase (denominado instancia de clase) ven ser cada un dos datos que queremos gardar na nosa solución informática. Dentro do mesmo código temos os valores que queremos gardar e os métodos que van poder tratar ditos valores. O conxunto dos valores que temos nun obxecto nun momento determinado denomínase estado do obxecto. Seguindo o exemplo da fábrica de galletas, cada obxecto (instancia de clase) ven ser unha galleta.
- Na POO obtemos un código máis lexible e reutilizable.
- A POO é máis achegada a nosa forma de pensar. Podemos ver calquera cousa do que nos rodea e abstraer as súas propiedades para formar na nosa cabeza a idea de clase.
- Así, se vemos un coche podemos abstraer as propiedades de cor, potencia, modelo,...! Acabamos de crear unha clase !. Para esas propiedades podemos crear múltiples instancias (obxectos) que van representar coches diferentes, cada un cuns valores diferentes (estado).
- Seguindo o exemplo anterior das contas bancarias...
- Creamos unha clase ContaBancaria que teña as seguintes variables (atributos ou propiedades) e procedementos (métodos):
- Atributos: Número de conta, titular, saldo,...
- Métodos:
- ingresarCartos
- retirarCartos
- Ditos métodos non necesitan levar parámetros xa que van tratar os datos que se atopan definidos na mesma clase.
- É dicir, se eu creo un obxecto desta clase, por exemplo:
- conta1 => NumConta: 1111, Titular: 'Angel', Saldo: 4000
- Os métodos que se atopan definidos na clase poden ser utilizados polo obxecto conta1. Ditos métodos van poder acceder aos datos da clase (número de conta e saldo).
- Creamos unha clase Banco, formado por, entre outros atributos, un array de obxectos ContaBancaria. Ese array gardaría todas as contas bancarias dun banco.
- En conclusión:
- Na programación estruturado os datos e os procedementos están separados e sen relación.
- Búscase o procesamento duns datos de entrada para obter unha saída.
- A idea e buscar unha solución informática a un problema pensando no uso de procedementos e funcións como guía.
- Na POO se ten como guía aos datos.
- Primeiro defínense as clases (datos) e despois implantase os métodos (procedementos) que van facer uso destes datos.
Creando clases
- Unha clase é un molde do que despois poderemos crear obxectos.
- As variables que están definidas dentro dunha clase reciben o nome de atributos ou propiedades e as funcións que están definidas dentro dunha clase reciben o nome de métodos. Os métodos da clase son usados para manipular as súas propias propiedades.
- As clases defínense empreñando a palabra clave class seguida do nome da clase e dun bloque de código entre chaves. A sintaxe é a seguinte:
1 <?php 2 class SimpleClass 3 { 4 // propiedade 5 public $var = 'valor por defecto'; 6 7 // métodos 8 public function amosarVar() { 9 echo $this->var; 10 } 11 } 12 ?>
- O nome da clase pode comezar por unha letra ou guión baixo seguido de letras, números e guións baixos. Non se pode utilizar ningunha palabra reservada de PHP.
- Como norma, o nome dunha clase comezará cunha letra maiúscula.
- Exemplos:
- class Pelota {}
- class ContaBancaria {}
- Cada clase estará definida nun arquivo php que levará o seu nome.
- Así, por exemplo, a clase Alumno terá un arquivo de nome Alumno.class.php
- Como norma, a extensión dunha clase será .class.php.
- Se dentro dunha páxina php necesito utilizar a clase Alumno terei que poñer:
1 <?php 2 require_once('Alumno.class.php'); 3 ?>
- Nota: Lembrar as formas que vimos para incluír arquivos externos.
- No exemplo estamos a supoñer que o arquivo 'Alumno.class.php' atópase no mesmo sitio que a clase php que está a facer uso del.
- Vexamos un exemplo de clase:
- Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros { 4 public $numSerie; 5 public $marca; 6 public $capacidade=1; // en TB 7 8 public function getCapacidadeKB(){ 9 return $this->capacidade*1024*1024; 10 } 11 }
- Como vemos, temos definidos dous atributos, número de serie e capacidade. Por defecto, a capacidade de todos os obxectos que cremos desta clase será de 1TB.
- Tamén vemos a definición dun método (getCapacidadeKB) que devolve a capacidade do disco en KB. Dentro deste método sinalar o uso da variable $this.
Nota: Veremos máis adiante o significado do modificador 'public'.
Variable $this
- En todas as linguaxe de POO sempre imos ter unha forma de referenciar a 'instancia' actual dentro do código da clase.
- Que significa a 'instancia actual' ?
- Cando cremos obxectos dunha clase, cada obxecto terá os seus datos.
- Así, podo crear dúas contas bancarias (dous obxectos):
- conta1 => NumConta: 1111, Titular: 'Angel', Saldo: 4000
- conta2 => NumConta: 2222, Titular: 'Pepe', Saldo: 2000
- Agora, cada obxecto vai poder chamar aos métodos definidos na clase.
- Así:
- O obxecto conta1 vai poder chamar ao método conta1.devolverSaldo(), o cal, fai un return do saldo (debería devolver 4000)
- O obxecto conta2 vai poder chamar ao método conta2.devolverSaldo(), o cal, fai un return do saldo (debería devolver 2000)
- Como vemos cada obxecto ten uns datos diferentes pero os dous chaman o mesmo método o cal está definido da mesma forma:
1 <?php 2 class ContaBancaria { 3 public $saldo; 4 5 public function devolverSaldo(){ 6 return $this->saldo; 7 } 8 } 9 ?>
- Coa variable $this podemos 'acceder' aos valores do obxecto que está a facer uso do método. Cando $conta1 chama a dito método, $this e $conta1; cando $conta2 chama ao método, $this é $conta2 cos seus datos específicos.
Rexistrando clases
- Para non ter que repetir un include ao principio de cada páxinas por cada unha das clases que necesitemos, podemos facer uso da autocarga de clases.
- Do que se trata é de que cando fagamos uso dunha clase, a páxina PHP a inclúa automaticamente sen ter que facelo nos manualmente. Para iso podemos facer uso da...
- Exemplo:
1 <?php 2 function cargadorClases($class) { 3 require $class . '.class.php'; 4 } 5 6 spl_autoload_register('cargadorClases'); 7 8 ?>
- Liña 6: Indicamos o nome da función que vai 'cargar' as clases cando non se atopen definidas na páxina actual.
- Liñas 2-4: Definimos a función indicada no método 'spl_autoload_register'. Dita función se define cunha parámetro, que vai ser o nome da clase a cargar cando non a atope.
- No interior de dita función formamos o nome do arquivo a cargar en base ao nome da clase e o incluímos cunha das formas xa vistas anteriormente.
- Podemos ter o código anterior dentro dun arquivo de include e incluílo por cada páxina que faga uso de clases.
- Así:
Arquivo: IncluirClases.inc.php:
1 <?php 2 function cargadorClases($class) { 3 require $class . '.class.php'; 4 } 5 6 spl_autoload_register('cargadorClases');
- Páxina Exemplo.php.
- Vai facer uso da clase DiscosDuros.
1 <?php 2 include 'IncluirClases.inc.php'; 3 ?> 4 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 5 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 6 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> 7 <head> 8 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 9 <title>Exemplos de uso de clases</title> 10 </head> 11 <body> 12 <?php 13 14 $disco1 = new DiscosDuros(); // Estamos a utilizar a clase DiscosDuros creando un obxecto de dita clase. 15 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 16 echo "<br />"; 17 $disco2 = new DiscosDuros(); 18 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 19 ?> 20 </body> 21 22 </html>
Creando obxectos
- Para crear un obxecto dunha clase usaremos a palabra new seguido do nome da clase. Isto devolverá un obxecto (instancia) da clase:
1 <?php 2 $obxecto1 = new Clase(); 3 $obxecto2 = new Clase(); 4 ?>
Nota: Lembrar que para facer uso dunha clase teremos que tela incluída previamente.
- Vexamos un exemplo:
1 <?php 2 function cargadorClases($class) { 3 require $class . '.class.php'; 4 } 5 6 spl_autoload_register('cargadorClases'); 7 8 ?> 9 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 10 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 11 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> 12 <head> 13 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 14 <title>Exemplos de uso de clases</title> 15 </head> 16 <body> 17 <?php 18 19 $disco1 = new DiscosDuros(); 20 $disco2 = new DiscosDuros(); 21 ?> 22 </body> 23 24 </html>
Nota: A partires de agora faremos un include do controlador que carga as clases cando se necesitan.
Accedendo as propiedades e métodos das clases
- Unha vez que temos un obxecto dunha clase xa podemos acceder as súas propiedades e métodos.
- Para iso temos que utilizar o operador -> desta forma:
1 obxecto1->propiedade; 2 obxecto1->nomeMétodo();
- Vexamos un exemplo:
1 <?php 2 $disco1 = new DiscosDuros(); 3 $disco1->numSerie="1111A"; 4 $disco1->marca="Seagate"; 5 echo "<br />"; 6 printf("NUM.SERIE %s",$disco1->numSerie); 7 echo "<br />"; 8 printf("MARCA %s",$disco1->marca); 9 echo "<br />"; 10 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 11 12 $disco2 = new DiscosDuros(); 13 $disco2->numSerie="2222B"; 14 $disco2->marca="Hitachi"; 15 echo "<br />"; 16 printf("NUM.SERIE %s",$disco2->numSerie); 17 echo "<br />"; 18 printf("MARCA %s",$disco2->marca); 19 echo "<br />"; 20 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 21 ?>
- Esta forma de acceder tamén pode empregarse dentro da clase para acceder aos seus propio métodos e atributos, pero utilizando a palabra reservada '$this'.
- Vexamos un exemplo:
1 <?php 2 3 class DiscosDuros { 4 public $numSerie; 5 public $marca; 6 public $capacidade=1; // en TB 7 8 public function getCapacidadeKB(){ 9 return $this->capacidade*1024*1024; 10 } 11 public function getCapacidadeBytes(){ 12 return $this->getCapacidadeKB()*1024; 13 } 14 15 public function getMarca(){ 16 return $this->marca; 17 } 18 }
Fai os exercicios indicados neste punto.
Herdanza
- Na POO existen tres propiedades fundamentais: Herdanda, Polimorfismo e Encapsulación.
- O concepto de herdanza é moi parecido ao que temos nos. Cando creamos unha clase que herda doutra, dita clase poderá acceder ás propiedades e métodos da clase pai.
- A clase pai denomínase superclase e a clase filla subclase.
- A sintaxe é a seguinte:
1 class Pai { 2 public $var1; 3 public $var2; 4 5 public function imprimirVar1(){ 6 echo $var1; 7 } 8 } 9 class Filla extends Pai { 10 public $var3; 11 public function imprimirVar3(){ 12 echo $var3; 13 } 14 }
- Liña 9: A clase Filla é subclase da clase Pai pola palabra clave extends.
- Se agora creamos un obxecto da clase Filla, poderemos acceder aos atributos e métodos da clase pai. Así:
1 $filla1 = new Filla(); 2 3 $filla1->var1='valor'; 4 $filla1->imprimirVar1(); 5 $filla1->var3=55; 6 $filla1->imprimirVar3();
- Nota: Cando vexamos os modificadores de visibilidade (public, protected, privated) veremos que unha subclase herda as propiedades e métodos declarados como public ou protected).
- A herdanza permite reutilizar eficientemente o código da clase base.
- Vexamos un exemplo cos discos creados anteriormente.
- Nunha tenda adicada aos compoñentes informáticos podemos ter discos duros, pantallas, procesadores,...
- Podemos crear unha clase Compoñente que teñas as propiedades comúns a todos eles (prezo, nome, num_serie,...)
- Exemplo de herdanza:
Arquivo: Componentes.class.php
1 <?php 2 3 class Componentes { 4 public $numSerie; 5 public $nome; 6 public $prezo; 7 public $marca; 8 9 10 public function getNumSerie(){ 11 return $this->numSerie; 12 } 13 public function getNome(){ 14 return $this->nome; 15 } 16 public function getPrezo(){ 17 return $this->prezo; 18 } 19 public function getMarca(){ 20 return $this->marca; 21 } 22 23 }
Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 public $capacidade=1; // en TB 5 6 public function getCapacidadeKB(){ 7 return $this->capacidade*1024*1024; 8 } 9 public function getCapacidadeBytes(){ 10 return $this->getCapacidadeKB()*1024; 11 } 12 13 }
Arquivo: Principal.php
1 <body> 2 <?php 3 $disco1 = new DiscosDuros(); // Tamén ten o da clase Compoñente 4 $disco1->numSerie="1111A"; 5 $disco1->marca="Seagate"; 6 $disco1->prezo=100.23; 7 echo "<br />"; 8 printf("NUM.SERIE %s",$disco1->numSerie); 9 echo "<br />"; 10 printf("MARCA %f",$disco1->marca); 11 echo "<br />"; 12 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 13 echo "<br />"; 14 printf("PREZO %s euros",number_format($disco1->getPrezo(),2)); 15 16 $disco2 = new DiscosDuros(); 17 $disco2->numSerie="2222B"; 18 $disco2->marca="Hitachi"; 19 $disco2->capacidade=5; 20 $disco2->prezo=150.73; 21 echo "<br />"; 22 printf("NUM.SERIE %s",$disco2->numSerie); 23 echo "<br />"; 24 printf("MARCA %s",$disco2->marca); 25 echo "<br />"; 26 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 27 echo "<br />"; 28 printf("PREZO %s euros", number_format($disco2->getPrezo(),2)); 29 ?> 30 </body>
- Resultado:
NUM.SERIE 1111A MARCA 0.000000 CAPACIDADE 1048576 EN KB PREZO 100.23 euros NUM.SERIE 2222B MARCA Hitachi CAPACIDADE 5242880 EN KB PREZO 150.73 euros
Facede os exercicios indicados neste punto.
Construtores
- O construtor é un método 'especial' que se chama de forma automática cando creamos un obxecto dunha clase, é dicir, cando empregamos a instrución new
- Utilízase sobre todo para mandar valores iniciais a un obxecto creado.
- A forma de definir un construtor é:
1 void __construct ([ mixed $args = "" [, $... ]] )
- Nota: A diferenza de Java non podemos ter definidos varios construtores con diferentes parámetros.
- Aplicamos este concepto ao exercicio anterior:
Arquivo: Componentes.class.php
1 <?php 2 3 class Componentes { 4 public $numSerie; 5 public $nome; 6 public $prezo; 7 public $marca; 8 9 10 function __construct($numSerie,$nome,$prezo,$marca) { 11 $this->numSerie=$numSerie; 12 $this->nome=$nome; 13 $this->prezo=$prezo; 14 $this->marca=$marca; 15 } 16 .................
Arquivo: Principal.php
1 <?php 2 $disco1 = new DiscosDuros("1111A","Disco Duro",100.23,"Seagete"); // Tamén ten o da clase Compoñente 3 $disco1->capacidade=1; 4 echo "<br />"; 5 printf("NUM.SERIE %s",$disco1->numSerie); 6 echo "<br />"; 7 printf("MARCA %s",$disco1->marca); 8 echo "<br />"; 9 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 10 echo "<br />"; 11 printf("PREZO %s euros",number_format($disco1->getPrezo(),2)); 12 13 echo "<br />"; 14 echo "<br />"; 15 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi"); 16 $disco2->capacidade=5; 17 echo "<br />"; 18 printf("NUM.SERIE %s",$disco2->numSerie); 19 echo "<br />"; 20 printf("MARCA %s",$disco2->marca); 21 echo "<br />"; 22 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 23 echo "<br />"; 24 printf("PREZO %s euros", number_format($disco2->getPrezo(),2)); 25 ?> 26 </body>
- Fixarse como o construtor está definido na clase Componentes, xa que é aí onde están definidos os atributos da clase.
- Como vemos, falta o atributo capacidade, pero ese atributo é específico da clase DiscoDuros. Polo tanto imos crear un construtor en dita clase, que vai ter como parámetros todos os anteriores máis o atributo 'capacidade'.
- A pregunta é, se xa temos na clase superior (superclase) un construtor que asina os atributos aos valores enviados ao construtor, non hai forma de chamar a dito construtor dende a clase filla ?
- A resposta é afirmativa: parent::__construct()
- Apliquemos isto ao exemplo anterior:
Arquivo: DiscoDuros.class.php
1 <?php 2 class DiscosDuros extends Componentes{ 3 public $capacidade=1; // en TB 4 5 function __construct($numSerie, $nome, $prezo, $marca,$capacidade) { 6 parent::__construct($numSerie, $nome, $prezo, $marca); 7 $this->capacidade=$capacidade; 8 } 9 public function getCapacidadeKB(){ 10 return $this->capacidade*1024*1024; 11 } 12 public function getCapacidadeBytes(){ 13 return $this->getCapacidadeKB()*1024; 14 } 15 16 }
- Liña 5: Definimos un construtor con todos os atributos que espera a clase DiscoDuros.
- Liña 6: Chamamos ao construtor da clase pai utilizando a palabra clave 'parent::'
- Liña 7: Asinamos á variable $capacidade o valor enviado como parámetro no construtor.
Arquivo: Principal.php
1 <?php 2 $disco1 = new DiscosDuros("1111A","Disco Duro",100.23,"Seagete",1); // Tamén ten o da clase Compoñente 3 echo "<br />"; 4 printf("NUM.SERIE %s",$disco1->numSerie); 5 echo "<br />"; 6 printf("MARCA %s",$disco1->marca); 7 echo "<br />"; 8 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 9 echo "<br />"; 10 printf("PREZO %s euros",number_format($disco1->getPrezo(),2)); 11 12 echo "<br />"; 13 echo "<br />"; 14 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6); 15 echo "<br />"; 16 printf("NUM.SERIE %s",$disco2->numSerie); 17 echo "<br />"; 18 printf("MARCA %s",$disco2->marca); 19 echo "<br />"; 20 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 21 echo "<br />"; 22 printf("PREZO %s euros", number_format($disco2->getPrezo(),2)); 23 ?>
- Fixarse como agora no 'new' dos obxectos enviamos todos os valores, incluído a capacidade.
Como implementar varias construtores
- En Java podemos ter varios construtores á vez definidos, de tal forma que un deles diferénciase doutro polo número ou tipo de argumentos.
- Isto en PHP non o podemos facer.
- Para solucionalo (código obtido deste enlace) podemos implementar o seguinte código:
1 function __construct() 2 { 3 $a = func_get_args(); 4 $i = func_num_args(); 5 if (method_exists($this,$f='__construct'.$i)) { 6 call_user_func_array(array($this,$f),$a); 7 } 8 }
- O que vai facer este código é chamar a un procedemento de nome __constructXX sendo XX o número de argumentos.
- Así se eu crea un obxecto dunha clase no que quero enviar dous argumentos poñería:
1 $obx1 = new Clase($val1,$val2);
- Isto chamará a o construtor por defecto o cal chamará a outro método:
1 function __construct2($par1,$par2){ 2 }
- Fixarse como o nome do método acaba en 2 que se corresponde có número de argumentos.
- Poderíamos aplicar isto ao exemplo dos DiscosDuros, de tal forma que podemos ter dous construtores, un cun os cinco parámetros e outro no que a capacidade colla un prezo por defecto.
Arquivo: DiscoDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 public $capacidade=1; // en TB 5 6 function __construct() 7 { 8 $a = func_get_args(); 9 $i = func_num_args(); 10 if (method_exists($this,$f='__construct'.$i)) { 11 call_user_func_array(array($this,$f),$a); 12 } 13 } 14 15 function __construct4($numSerie, $nome, $marca,$capacidade) { 16 parent::__construct($numSerie, $nome, 0, $marca); 17 $this->capacidade=$capacidade; 18 $this->prezo=100; 19 } 20 21 function __construct5($numSerie, $nome, $prezo, $marca,$capacidade) { 22 parent::__construct($numSerie, $nome, $prezo, $marca); 23 $this->capacidade=$capacidade; 24 }
- Agora podemos chamar aos dos construtores:
1 $disco1 = new DiscosDuros("1111A","Disco Duro","Seagete",2); 2 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6);
Destructores
- Ao igual que os construtores tamén dispoñemos dun método 'especial' denominado destrutor o cal se chama cando se deixa de ter referencias a un obxecto.
- Cando remata o script todas as referencias pérdense e polo tanto se chamará ao método __destructor.
- Vexamos un exemplo:
Arquivo: Componentes.class.php
1 <?php 2 3 class Componentes { 4 static $numComponentes = 0; 5 6 public $numSerie; 7 public $nome; 8 public $prezo; 9 public $marca; 10 11 12 function __construct($numSerie,$nome,$prezo,$marca) { 13 $this->numSerie=$numSerie; 14 $this->nome=$nome; 15 $this->prezo=$prezo; 16 $this->marca=$marca; 17 } 18 19 public function getNumSerie(){ 20 return $this->numSerie; 21 } 22 public function getNome(){ 23 return $this->nome; 24 } 25 public function getPrezo(){ 26 return $this->prezo; 27 } 28 public function getMarca(){ 29 return $this->marca; 30 } 31 function __destruct(){ 32 echo "<br />Acabouse"; 33 } 34 35 }
1 <?php 2 $disco1 = new DiscosDuros("1111A","Disco Duro","Seagete",2); // Tamén ten o da clase Compoñente 3 echo "<br />"; 4 printf("NUM.SERIE %s",$disco1->numSerie); 5 echo "<br />"; 6 printf("MARCA %s",$disco1->marca); 7 echo "<br />"; 8 printf("CAPACIDADE %s EN KB",$disco1->getCapacidadeKB()); 9 echo "<br />"; 10 printf("PREZO %s euros",number_format($disco1->getPrezo(),2)); 11 12 echo "<br />"; 13 $disco1=null; 14 echo "<br />"; 15 echo "Segundo disco"; 16 echo "<br />"; 17 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6); 18 echo "<br />"; 19 printf("NUM.SERIE %s",$disco2->numSerie); 20 echo "<br />"; 21 printf("MARCA %s",$disco2->marca); 22 echo "<br />"; 23 printf("CAPACIDADE %s EN KB",$disco2->getCapacidadeKB()); 24 echo "<br />"; 25 printf("PREZO %s euros", number_format($disco2->getPrezo(),2)); 26 ?>
- Dará como resultado:
1 NUM.SERIE 1111A 2 MARCA Seagete 3 CAPACIDADE 2097152 EN KB 4 PREZO 100.00 euros 5 Acabouse 6 Segundo disco 7 8 NUM.SERIE 2222B 9 MARCA Hitachi 10 CAPACIDADE 6291456 EN KB 11 PREZO 150.73 euros Acabouse
Facede os exercicios indicados neste punto
Polimorfismo e Sobrecarga
Polimorfismo
- Outra das propiedades da POO é o Polimorfismo.
- Normalmente o polimorfismo dáse en clases que manteñan unha relación de herdanza, aínda que tamén se pode dar en outro tipo de relacións (por exemplo utilizando interfaces).
- Basicamente, é a capacidade que teñen as clase de definir métodos có mesmo nome e o mesmo número (e tipo en outras linguaxes) de argumentos.
- Cando un obxecto chame a dito método ou use unha propiedade, saberá que método/propiedade utilizar en base a súa pertenza a un tipo de clase determinado.
- Vexamos un exemplo aplicado ao caso dos DiscosDuros.
- Imaxinemos que creamos un método de nome 'comprobarDatos' que o que fai é que no caso dos DiscosDuros, mira que a capacidade teña un valor positivo e no caso dos Compoñenetes, o prezo teña un valor positivo. Devolverá 1 ou 0 dependendo do valor.
- Se eu creo un obxecto da clase DiscosDuros e chamo a dito método, chamará ao definido na clase DiscosDuros por pertencer a ela. Dende a clase DiscosDuros vou poder chamar ao método da clase superior utilizando a expresión parent::.
Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 5 ......... 6 public function comprobarDatos(){ 7 if (parent::comprobarDatos()==0) return 0; 8 if ($this->capacidade < 0) { 9 echo "<br />Capacidade negativa !!!<br />"; 10 return 0; 11 } else { 12 return 1; 13 } 14 } 15 }
Arquivo: Componentes.class.php
1 <?php 2 class Componentes { 3 ................... 4 public function comprobarDatos(){ 5 if ($this->prezo<0) { 6 echo "<br />Prezo negativo!!!<br />"; 7 return 0; 8 } 9 else { 10 return 1; 11 } 12 } 13 14 }
Arquivo: Principal.php
1 <?php 2 $disco1 = new DiscosDuros(); 3 $disco1->prezo=-100; 4 $disco1->capacidade=10; 5 printf("O Prezo é negativo. Datos correctos?:%b",$disco1->comprobarDatos()); 6 $disco1->prezo=100; 7 $disco1->capacidade=-10; 8 printf("Capacidade negativa. Datos correctos?:%b",$disco1->comprobarDatos()); 9 ?>
Saída:
Prezo negativo!!! O Prezo é negativo. Datos correctos?:0 Capacidade negativa !!! Capacidade negativa. Datos correctos?:0
Sobrecarga
- Dicimos que un método está sobrecargado se está definido dúas ou máis veces nunha clase e se diferenza un doutro polo número (e tipo en outras linguaxes) de argumentos.
- En PHP non podemos ter sobrecarga de métodos xa que non deixar ter definidos varios métodos có mesmo nome.
- Un 'apaño' que podemos facer é o seguinte (parecido ao que fixemos cos construtores):
1 <?php 2 class Proba { 3 function __call($method_name, $arguments) { 4 $i = count($arguments); 5 if (method_exists($this,$f=$method_name.$i)) { 6 call_user_func_array(array($this,$f),$arguments); 7 } 8 } 9 private function metodo1($a){ 10 echo "<br />Chamada a 'metodo' cun argumento"; 11 } 12 private function metodo2($a,$b){ 13 echo "<br />Chamada a 'metodo' con dous argumentos";; 14 } 15 }
1 <?php 2 3 $p1 = new Proba(); 4 5 $p1->metodo(1); 6 $p1->metodo(1,'Proba'); 7 ?>
- Dará como resultado:
1 Chamada a 'metodo' cun argumento 2 Chamada a 'metodo' con dous argumentos
- No exemplo estamos a utilizar a función __call.
- Esta é unha función especial que se chama cando un obxecto chama a unha función que non estea definida. Recibe como datos o nome da función e os parámetros enviados na súa chamada.
Nota: Fixarse que as funcións están declaradas como 'private' para que dende a páxina que faga uso da clase non aparezan os métodos (en forma de axuda) no IDE de PHP cando tecleamos o nome do obxecto. Veremos a continuación a encapsulación.
Atributos e Métodos de clase
- Un método/atributo de clase pode ser chamado/usado sen necesidade de instanciar un obxecto.
- Chámase directamente dende o nome da clase.
- O concepto de clase o podemos asociar ao de variables globais nun programa tradicional.
- Se as variables dos obxectos son variables (atributos) 'locais' a cada un, é dicir, cada obxecto ten un valor propia para cada atributo da clase, nas variables de clase (estáticas) todos os obxectos teñen o mesmo valor, xa que fisicamente están apuntando á mesma dirección de memoria.
- Se un obxecto cambia o valor dun atributo de clase, se cambia para todos os obxectos.
- Para declarar unha variable ou método de clase, debemos antepoñer a palabra reservada static.
- Para acceder a unha variable de clase debemos de poñer: Clase::$variableClase, sendo Clase o nome da clase.
- Vexamos un exemplo:
1 class Clase { 2 static $variableStatic = 10; 3 }
1 2 echo Clase::$variableStatic;
- Ao igual que os atributos, podemos crear métodos de clase.
- Para acceder dende un método (de clase ou static) a un atributo static temos que facer uso da palabra clave: self::
- Vexamos un exemplo:
1 class Clase { 2 static $variableStatic = 10; 3 4 static function metodoStatic(){ 5 echo self::$variableStatic; 6 } 7 function metodo(){ 8 echo self::$variableStatic; 9 } 10 11 }
1 Clase::metodoStatic(); // Chama ao método de clase 2 $ob1 = new Clase(); 3 $obj1->metodo(); // Chama ao método e este accede á variable de clase.
- Fixarse como para chamar a un método de clase usamos a forma: Clase::métodoClase()
- Vexamos agora un exemplo de aplicación no exemplo dos DiscosDuros.
- Podemos crear unha variable de clase que garde o número de discos duros/compoñentes creados.
- Cada vez que eu creo un compoñente aumentarei o número e se o destrúo (non teño ningunha referencia) diminúo o contador. Farei o mesmo para a clase DiscosDuros e ademais crearei un método de clase que me amose o número total (de todos os compoñentes) e o número de discos duros.
- Solución:
- Arquivo: Componentes.class.php
1 <?php 2 3 class Componentes { 4 static $numComponentes = 0; 5 6 public $numSerie; 7 public $nome; 8 public $prezo; 9 public $marca; 10 11 12 function __construct($numSerie,$nome,$prezo,$marca) { 13 $this->numSerie=$numSerie; 14 $this->nome=$nome; 15 $this->prezo=$prezo; 16 $this->marca=$marca; 17 18 self::$numComponentes+=1; 19 } 20 static function imprimirTotal(){ 21 22 printf("Número de compoñentes:%s<br />",self::$numComponentes); 23 } 24 25 26 public function getNumSerie(){ 27 return $this->numSerie; 28 } 29 public function getNome(){ 30 return $this->nome; 31 } 32 public function getPrezo(){ 33 return $this->prezo; 34 } 35 public function getMarca(){ 36 return $this->marca; 37 } 38 function __destruct(){ 39 self::$numComponentes-=1; 40 echo "<br />Acabouse"; 41 } 42 43 }
- Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 public $capacidade=1; // en TB 5 public static $numDiscosDuros=0; 6 7 static function imprimirTotal(){ 8 Componentes::imprimirTotal(); 9 printf("Número de compoñentes:%s<br />",self::$numComponentes); 10 printf("Número de discos:%s<br />",self::$numDiscosDuros); 11 12 } 13 function __construct() 14 { 15 self::$numDiscosDuros+=1; 16 $a = func_get_args(); 17 $i = func_num_args(); 18 if (method_exists($this,$f='__construct'.$i)) { 19 call_user_func_array(array($this,$f),$a); 20 } 21 } 22 23 function __construct4($numSerie, $nome, $marca,$capacidade) { 24 parent::__construct($numSerie, $nome, 0, $marca); 25 $this->capacidade=$capacidade; 26 $this->prezo=100; 27 } 28 29 function __construct5($numSerie, $nome, $prezo, $marca,$capacidade) { 30 parent::__construct($numSerie, $nome, $prezo, $marca); 31 $this->capacidade=$capacidade; 32 } 33 34 function __destruct(){ 35 parent::__destruct(); 36 self::$numDiscosDuros-=1; 37 echo "<br />Acabouse"; 38 } 39 40 public function getCapacidadeKB(){ 41 return $this->capacidade*1024*1024; 42 } 43 public function getCapacidadeBytes(){ 44 return $this->getCapacidadeKB()*1024; 45 } 46 47 }
- Arquivo: Principal.php:
1 <?php 2 3 4 echo "<br />"; 5 echo "Primeiro disco"; 6 echo "<br />"; 7 $disco10 = new DiscosDuros("1111A","Disco Duro","Seagete",2); // Tamén ten o da clase Compoñente 8 echo DiscosDuros::imprimirTotal(); 9 10 $disco10=null; 11 echo "<br />"; 12 echo "Primeiro disco borrado"; 13 echo "<br />"; 14 echo DiscosDuros::imprimirTotal(); 15 $disco12 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6); 16 $disco13 = new DiscosDuros("3333B","Disco Duro",150.73,"Hitachi",6); 17 $disco14 = new DiscosDuros("4444B","Disco Duro",150.73,"Hitachi",6); 18 echo "<br />"; 19 echo "Tres novos discos"; 20 echo "<br />"; 21 echo DiscosDuros::imprimirTotal(); 22 ?>
Facede os exercicios indicados neste punto.
Constantes
- Un pouco relacionado co punto anterior, temos as constantes.
- Son valores fixos establecidos na definición da clase, que non se poden modificar en tempo de execución.
- Para declarar unha constante no interior dunha clase usamos a palabra reservada const antes do nome da constante que non debe levar o caracter $.
- Como norma, o nome de constante debe estar en en maiúsculas para diferenciar facilmente unha constante dunha variable.
class Clase { const NUM_MAXIMO=1000; }
- Para acceder á constante podemos facelo como se fora un atributo estático (polo tanto, na memoria está a ocupar unha posición que é compartida por todos os obxectos, como sucede nas variables de clase). Podemos acceder directamente utilizando o nome da clase ou a través dun obxecto da forma:
1 $a = new Clase(); 2 3 echo $a::NUM_MAXIMO; 4 echo Clase::NUM_MAXIMO;
Constantes predefinidas en PHP
- Tedes neste enlace algunhas constantes que vos poden servir na programación PHP.
- De todas elas, vos pode axudar na programación en PHP (sobre todo en aloxamentos compartidos de Internet):
- __FILE__: Ruta completa e nome do arquivo. Usado dentro dun INCLUDE devolverá o nome do ficheiro do INCLUDE.
- __DIR__: Directorio do ficheiro. Usado dentro dun INCLUDE, devolverá o directorio do arquivo incluído.
- Por que ?
- Se estades nun aloxamento compartido cun CMS (como o Wordpress que utilizamos na práctica e queredes facer un include dun arquivo php, dando a ruta absoluta do voso aloxamento, teredes que averiguar dita ruta. Unha das formas é facendo un echo dunha das constantes anteriores dende un Post ou páxina do CMS.
Encapsulación
- Outra das propiedades da POO é a Encapsulación.
- Mediante dita propiedade 'obrigamos' a que o uso dos datos e métodos se fagan de acordo ao implementado polo programador.
- Ocúltanse (a o que vai facer uso da clase) aqueles métodos e propiedades que non vai ter que utilizar e evítase que os datos poidan ter valores non válidos.
- En definitiva, a encapsulación é un mecanismo no que organizamos os datos e métodos de tal forma que evitamos o acceso a os datos de calquera outra forma diferente á especificada. A encapsulación garante a integridade dos datos que contén un obxecto.
- A encapsulación impleméntase cunha destas tres palabras clave:
- Público(public): é o modificador por defecto, co cal se non poñemos nada enténdese que o modificador é public. Calquera pode acceder ás propiedades e métodos declarados como public.
- Privado(private): o acceso as propiedades e métodos declarados como private están restrinxidos a clase na que foron creados.
- Protexido(protected): unicamente poden acceder a estas propiedades e métodos a propia clase e as súas clases derivadas, é dicir, clases que sexan 'fillas' da clase onde o atributo/método estea declarado como protected.
- Estes modificadores van poder ser aplicados tanto a propiedades como a métodos.
- Vexamos un exemplo aplicado ao caso dos DiscosDuros...
- Supoñamos que cando creo un DiscoDuro e doulle un prezo, quero que en función da capacidade aplique un desconto ou outro. Isto o fai un método que non quero que sexa 'visible' fora do código da clase.
- Polo tanto terei que declaralo como 'private'. Como o desconto o vou poder aplicar a todos os compoñentes, o vou definir na clase Componentes, e o vou facer 'protected' para que o poida referenciar a subclase 'DiscosDuros' pero que non poida acceder directamente dende fora da clase Componentes.
Arquivo: Componentes.class.java
1 <?php 2 3 class Componentes { 4 public $numSerie; 5 public $nome; 6 public $prezo; 7 public $marca; 8 9 protected $desconto=0; 10 11 function __construct($numSerie,$nome,$prezo,$marca) { 12 $this->numSerie=$numSerie; 13 $this->nome=$nome; 14 $this->prezo=$prezo; 15 $this->marca=$marca; 16 } 17 18 public function getDesconto(){ 19 return $this->desconto; 20 } 21 22 ......................
Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 public $capacidade=1; // en TB 5 6 function __construct($numSerie, $nome, $prezo, $marca,$capacidade) { 7 parent::__construct($numSerie, $nome, $prezo, $marca); 8 $this->capacidade=$capacidade; 9 10 if($capacidade>5){ 11 $this->desconto=10; // Faremos un desconto dun 10% 12 } else { 13 $this->desconto=3; // Faremos un desconto dun 3% 14 } 15 } 16 ................
Arquivo: Principal.php
1 <?php 2 3 $disco1 = new DiscosDuros("1111A","Disco Duro",100.23,"Seagete",1); // Tamén ten o da clase Compoñente 4 echo "<br />"; 5 printf("DISCO 1 DESCONTO %s%%",$disco1->getDesconto()); 6 7 echo "<br />"; 8 echo "<br />"; 9 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6); 10 printf("DISCO 2 DESCONTO %s%%",$disco2->getDesconto()); 11 ?>
- Dará como resultado:
1 DISCO 1 DESCONTO 3% 2 3 DISCO 2 DESCONTO 10%
- Fixarse como a única forma de acceder a unha variable 'private' dende o exterior de dita clase, é a través dun método (no exemplo 'getDesconto')
Protexendo os atributos
- Normalmente, na POO imos querer que as propiedades dunha clase non poidan ser accedidas directamente, xa que imos querer ter un 'control' sobre os valores asinados.
- A forma de realizar este control é que a asignación de valores se faga a través dun método, no que comprobamos o valor a asignar. Isto xa o podemos facer no construtor, pero se queremos modificar os valores durante a execución do programa, teremos que facer que os atributos sexan privados e crear os métodos para asinar os valores.
- Inicialmente poderíamos facer algo parecido a isto (aplicado ao exemplo dos DiscosDuros e Componentes)
Arquivo: DiscosDuros.class.php
1 <?php 2 3 class DiscosDuros extends Componentes{ 4 private $capacidade=1; // en TB 5 6 function __construct($numSerie, $nome, $prezo, $marca,$capacidade) { 7 parent::__construct($numSerie, $nome, $prezo, $marca); 8 $this->capacidade=$capacidade; 9 10 if($capacidade>5){ 11 $this->desconto=10; // Faremos un desconto dun 10% 12 } else { 13 $this->desconto=3; // Faremos un desconto dun 3% 14 } 15 16 } 17 public function cambiarCapacidade($novoValor){ 18 if ($novoValor<0) { // Non deixamos unha capacidade negativa 19 return 0; 20 } 21 else { 22 $this->capacidade=$novoValor; 23 return 1; 24 } 25 } 26 public function getCapacidade(){ 27 return $this->capacidade; 28 }
- Liña 4: Declaramos a capacidade como privada.
- Liñas 17-25: Definimos un método cambiarCapacidade que vai comprobar se a capacidade é negativa. Se o é, retorna 0. En caso contrario, cambia a capacidade e retorno 1
- Liñas 26-28: Definimos un método para que devolva a capacidade.
Arquivo: Principal.php
1 <?php 2 3 $disco1 = new DiscosDuros("1111A","Disco Duro",100.23,"Seagete",1); // Tamén ten o da clase Compoñente 4 echo "<br />"; 5 if ($disco1->cambiarCapacidade(4)){ 6 echo "Capacidade cambiada <br />"; 7 printf("Nova capacidade Disco 1:%d",$disco1->getCapacidade()); 8 } 9 else { 10 echo "A capacidade do DISCO 1 non pode ser negativa"; 11 } 12 13 echo "<br />"; 14 echo "<br />"; 15 $disco2 = new DiscosDuros("2222B","Disco Duro",150.73,"Hitachi",6); 16 if ($disco1->cambiarCapacidade(-2)){ 17 echo "Capacidade cambiada <br />"; 18 printf("Nova capacidade Disco 2:%d",$disco1->getCapacidade()); 19 } 20 else { 21 echo "A capacidade do DISCO 2 non pode ser negativa"; 22 }
- Da como resultado:
Capacidade cambiada Nova capacidade Disco 1:4 A capacidade do DISCO 2 non pode ser negativa
- PHP é un pouco 'especial' e atopamos diferentes artigos no que non se recomenda o uso de métodos get e set para cada propiedade por motivos de optimización de código.
- Eu penso que vai depender da aplicación e de como ides necesitar modificar os valores das propiedades dunha clase.
- Se por exemplo, non ides modificar os valores e soamente ides darlles uns valores iniciais (irían no construtor, onde podedes validalos) non necesitaríamos crear un método 'set' para darlle un valor a unha propiedade e como no código non teríamos asignacións, poderíamos non ter un método 'get'.
- Se a nosa clase vai ser utilizada en outros proxectos e o uso indebido das propiedades pode dar lugar a problemas, deberíamos ter un método 'set' por cada propiedades para controlar que os valores son os correctos.
Métodos máxicos
- PHP incorpora dous métodos 'especiais' chamados máxicos que son chamados automaticamente (parecido ao que fixemos coa función __call) cando accedemos a unha propiedade dunha clase calquera:
- Método __get: Se chama automaticamente cando intentamos acceder a unha propiedade calquera dunha clase. Leva como parámetro o nome do atributo do que queremos obter o valor.
- Por exemplo:
1 class Clase{ 2 3 private $propiedade; 4 5 public function __get($atributo) { 6 return $this->atributo; 7 } 8 }
1 $obj = new Clase(); 2 echo $obj->propiedade;
- Método __set: Set chámase automaticamente cando intentamos dar un valor a unha propiedade dunha clase. Ten como parámetros de entrada o nome do atributo e o valor que se lle quere dar.
- Por exemplo:
1 class Clase{ 2 3 private $propiedade1; 4 private $propiedade2; 5 6 public function __set($atributo,$valor) { 7 $this->atributo=$valor; 8 } 9 } 10 }
1 $obj = new Clase(); 2 $obj->propiedade1=10; 3 $obj->propiedade2=10;
- Relacionado cos dous métodos anteriores temos a función property_exists. A necesitamos utilizar para comprobar se o atributo que estamos intentar modificar ou obter o seu valor existe.
- Teremos que facer uso da constante __CLASS__, que devolve o nome da clase 'actual'.
- No caso de __get:
1 class Clase{ 2 3 private $propiedade; 4 5 public function __get($atributo) { 6 if (property_exists(__CLASS__, $atributo)) { 7 return $this->atributo; 8 } 9 return NULL; 10 } 11 }
1 $obj = new Clase(); 2 echo $obj->propiedade;
- No caso de __set:
1 class Clase{ 2 3 private $propiedade1; 4 private $propiedade2; 5 6 public function __set($atributo,$valor) { 7 if (property_exists(__CLASS__, $atributo)) { 8 $this->atributo=$valor; 9 } 10 else { 11 echo "Non existe o atributo $atributo"; 12 } 13 } 14 } 15 }
1 $obj = new Clase(); 2 $obj->propiedade1=10; 3 $obj->propiedade2=10;
- O tratamento que queirades darlle cando o atributo non exista vai depender do programador. Poderíamos retornar un 0/1 como fixamos antes cos métodos que asignaban un valor a unha propiedade private.
- No exemplo aparecería un echo.
Funcións relacionadas coas clases e métodos
- PHP dispón de funcións que nos van permitir saber o nome da clase a que pertence un obxecto, preguntar por se unha clase está definida, saber se un método existe nunha clase,...
- Todas estas funcións as tedes descritas neste enlace.
- Outra función relacionada coas clases e que poderemos necesitar dela é a función instanceof.
- A súa función é comprobar se unha variable é dunha clase determinada.
- Un código de exemplo sería o seguinte:
1 $p = new Clase(); 2 3 if ($p instanceof Clase){ 4 echo "O obxecto \$p é da clase Clase"; 5 }
-- Ángel D. Fernández González -- (2016).