PHP
Classi e oggetti
Ricerca…
introduzione
Classi e oggetti sono utilizzati per rendere il codice più efficiente e meno ripetitivo raggruppando attività simili.
Una classe viene utilizzata per definire le azioni e la struttura dati utilizzate per creare oggetti. Gli oggetti vengono quindi creati utilizzando questa struttura predefinita.
Sintassi
-
class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { }
// Dichiarazione di classe -
interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { }
// Dichiarazione dell'interfaccia -
use <Trait1> [, <Trait2>, ...]
; // Usa i tratti -
[ public | protected | private ] [ static ] $<varName>;
// Dichiarazione di attributo -
const <CONST_NAME>;
// Dichiarazione costante -
[ public | protected | private ] [ static ] function <methodName>([args...]) { }
// Dichiarazione del metodo
Osservazioni
Classi e componenti dell'interfaccia
Le classi possono avere proprietà, costanti e metodi.
- Le proprietà contengono variabili nell'ambito dell'obiettivo. Possono essere inizializzati sulla dichiarazione, ma solo se contengono un valore primitivo.
- Le costanti devono essere inizializzate sulla dichiarazione e possono contenere solo un valore primitivo. I valori costanti sono fissati al momento della compilazione e non possono essere assegnati in fase di esecuzione.
- I metodi devono avere un corpo, anche uno vuoto, a meno che il metodo non sia dichiarato astratto.
class Foo {
private $foo = 'foo'; // OK
private $baz = array(); // OK
private $bar = new Bar(); // Error!
}
Le interfacce non possono avere proprietà, ma possono avere costanti e metodi.
- Le costanti di interfaccia devono essere inizializzate sulla dichiarazione e possono contenere solo un valore primitivo. I valori costanti sono fissati al momento della compilazione e non possono essere assegnati in fase di esecuzione.
- I metodi di interfaccia non hanno corpo.
interface FooBar {
const FOO_VALUE = 'bla';
public function doAnything();
}
interfacce
introduzione
Le interfacce sono definizioni delle API pubbliche che le classi devono implementare per soddisfare l'interfaccia. Funzionano come "contratti", specificando cosa fa un insieme di sottoclassi, ma non come lo fanno.
La definizione dell'interfaccia è molto simile alla definizione della classe, cambiando la class
parola chiave per l' interface
:
interface Foo {
}
Le interfacce possono contenere metodi e / o costanti, ma non attributi. Le costanti dell'interfaccia hanno le stesse restrizioni delle costanti di classe. I metodi di interfaccia sono implicitamente astratti:
interface Foo {
const BAR = 'BAR';
public function doSomething($param1, $param2);
}
Nota: le interfacce non devono dichiarare costruttori o distruttori, poiché si tratta di dettagli di implementazione a livello di classe.
Realizzazione
Qualsiasi classe che deve implementare un'interfaccia deve farlo utilizzando la parola chiave implements
. Per fare ciò, la classe deve fornire un'implementazione per ogni metodo dichiarato nell'interfaccia, rispettando la stessa firma.
Una singola classe può implementare più di un'interfaccia alla volta.
interface Foo {
public function doSomething($param1, $param2);
}
interface Bar {
public function doAnotherThing($param1);
}
class Baz implements Foo, Bar {
public function doSomething($param1, $param2) {
// ...
}
public function doAnotherThing($param1) {
// ...
}
}
Quando le classi astratte implementano le interfacce, non hanno bisogno di implementare tutti i metodi. Qualsiasi metodo non implementato nella classe base deve quindi essere implementato dalla classe concreta che lo estende:
abstract class AbstractBaz implements Foo, Bar {
// Partial implementation of the required interface...
public function doSomething($param1, $param2) {
// ...
}
}
class Baz extends AbstractBaz {
public function doAnotherThing($param1) {
// ...
}
}
Si noti che la realizzazione dell'interfaccia è una caratteristica ereditata. Quando si estende una classe che implementa un'interfaccia, non è necessario ridichiarla nella classe concreta, perché è implicita.
Nota: prima di PHP 5.3.9, una classe non poteva implementare due interfacce che specificavano un metodo con lo stesso nome, poiché ciò avrebbe causato ambiguità. Le versioni più recenti di PHP consentono questo finché i metodi duplicati hanno la stessa firma [1] .
Eredità
Come le classi, è possibile stabilire una relazione di ereditarietà tra le interfacce, utilizzando le stesse extends
parole chiave. La differenza principale è che l'ereditarietà multipla è consentita per le interfacce:
interface Foo {
}
interface Bar {
}
interface Baz extends Foo, Bar {
}
Esempi
Nell'esempio seguente abbiamo una semplice interfaccia di esempio per un veicolo. I veicoli possono andare avanti e indietro.
interface VehicleInterface {
public function forward();
public function reverse();
...
}
class Bike implements VehicleInterface {
public function forward() {
$this->pedal();
}
public function reverse() {
$this->backwardSteps();
}
protected function pedal() {
...
}
protected function backwardSteps() {
...
}
...
}
class Car implements VehicleInterface {
protected $gear = 'N';
public function forward() {
$this->setGear(1);
$this->pushPedal();
}
public function reverse() {
$this->setGear('R');
$this->pushPedal();
}
protected function setGear($gear) {
$this->gear = $gear;
}
protected function pushPedal() {
...
}
...
}
Quindi creiamo due classi che implementano l'interfaccia: bici e auto. Bici e auto internamente sono molto diverse, ma entrambi sono veicoli e devono implementare gli stessi metodi pubblici forniti da VehicleInterface.
Typehinting consente a metodi e funzioni di richiedere interfacce. Supponiamo di avere una classe di garage, che contiene veicoli di ogni tipo.
class ParkingGarage {
protected $vehicles = [];
public function addVehicle(VehicleInterface $vehicle) {
$this->vehicles[] = $vehicle;
}
}
Poiché addVehicle
richiede un $vehicle
di tipo VehicleInterface
- non un'implementazione concreta - possiamo inserire sia Bikes che Cars, che ParkingGarage può manipolare e utilizzare.
Costanti di classe
Le costanti di classe forniscono un meccanismo per mantenere valori fissi in un programma. Cioè, forniscono un modo di dare un nome (e un controllo associato in fase di compilazione) ad un valore come 3.14
o "Apple"
. Le costanti di classe possono essere definite solo con la parola chiave const
: la funzione define non può essere utilizzata in questo contesto.
Ad esempio, può essere conveniente avere una rappresentazione abbreviata per il valore di π in un programma. Una classe con valori const
fornisce un modo semplice per contenere tali valori.
class MathValues {
const PI = M_PI;
const PHI = 1.61803;
}
$area = MathValues::PI * $radius * $radius;
È possibile accedere alle costanti di classe utilizzando l'operatore double colon (il cosiddetto operatore di risoluzione scope) su una classe, in modo simile alle variabili statiche. A differenza delle variabili statiche, tuttavia, le costanti di classe hanno i loro valori fissati in fase di compilazione e non possono essere riassegnati a (ad esempio MathValues::PI = 7
produrrebbe un errore fatale).
Le costanti di classe sono anche utili per definire cose interne a una classe che potrebbe essere necessario modificare in un secondo momento (ma non cambiano abbastanza frequentemente per giustificare l'archiviazione, ad esempio, in un database). Possiamo riferimento a questa internamente usando l' self
resolutor portata (che funziona in entrambe le implementazioni instanced e statiche)
class Labor {
/** How long, in hours, does it take to build the item? */
const LABOR_UNITS = 0.26;
/** How much are we paying employees per hour? */
const LABOR_COST = 12.75;
public function getLaborCost($number_units) {
return (self::LABOR_UNITS * self::LABOR_COST) * $number_units;
}
}
Le costanti di classe possono contenere solo valori scalari nelle versioni <5.6
A partire da PHP 5.6 possiamo usare espressioni con costanti, il che significa che le istruzioni matematiche e le stringhe con concatenazione sono costanti accettabili
class Labor {
/** How much are we paying employees per hour? Hourly wages * hours taken to make */
const LABOR_COSTS = 12.75 * 0.26;
public function getLaborCost($number_units) {
return self::LABOR_COSTS * $number_units;
}
}
A partire da PHP 7.0, le costanti dichiarate con define
ora possono contenere array.
define("BAZ", array('baz'));
Le costanti di classe sono utili per qualcosa di più della semplice memorizzazione di concetti matematici. Ad esempio, se si prepara una torta, potrebbe essere conveniente avere una singola classe Pie
grado di prendere diversi tipi di frutta.
class Pie {
protected $fruit;
public function __construct($fruit) {
$this->fruit = $fruit;
}
}
Possiamo quindi utilizzare la classe Pie
modo
$pie = new Pie("strawberry");
Il problema che si pone qui è che, quando si crea un'istanza della classe Pie
, non viene fornita alcuna indicazione sui valori accettabili. Ad esempio, quando si crea una torta "boysenberry", potrebbe essere errata "boisenberry". Inoltre, potremmo non supportare una torta di prugne. Invece, sarebbe utile avere una lista di tipi di frutta accettabili già definiti da qualche parte avrebbe senso cercarli. Dì una classe chiamata Fruit
:
class Fruit {
const APPLE = "apple";
const STRAWBERRY = "strawberry";
const BOYSENBERRY = "boysenberry";
}
$pie = new Pie(Fruit::STRAWBERRY);
Elencare i valori accettabili come costanti di classe fornisce un prezioso suggerimento sui valori accettabili che un metodo accetta. Garantisce inoltre che gli errori di ortografia non possano superare il compilatore. Mentre new Pie('aple')
e new Pie('apple')
sono entrambi accettabili per il compilatore, new Pie(Fruit::APLE)
produrrà un errore del compilatore.
Infine, l'uso delle costanti di classe significa che il valore effettivo della costante può essere modificato in un singolo punto e qualsiasi codice che utilizza la costante ha automaticamente gli effetti della modifica.
Mentre il metodo più comune per accedere a una costante di classe è MyClass::CONSTANT_NAME
, è possibile che vi si acceda anche:
echo MyClass::CONSTANT;
$classname = "MyClass";
echo $classname::CONSTANT; // As of PHP 5.3.0
Le costanti di classe in PHP sono convenzionalmente denominate tutte in maiuscolo con caratteri di sottolineatura come separatori di parole, sebbene qualsiasi nome di etichetta valido possa essere usato come nome di costante di classe.
A partire da PHP 7.1, le costanti di classe ora possono essere definite con diverse visioni dall'ambito pubblico predefinito. Ciò significa che è ora possibile definire sia le costanti protette che quelle private per impedire che le costanti di classe sfuggano inutilmente all'ambito pubblico (vedere Metodo e visibilità delle proprietà ). Per esempio:
class Something {
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
define vs costant class
Sebbene questa sia una costruzione valida:
function bar() { return 2; };
define('BAR', bar());
Se provi a fare lo stesso con le costanti di classe, riceverai un errore:
function bar() { return 2; };
class Foo {
const BAR = bar(); // Error: Constant expression contains invalid operations
}
Ma puoi fare:
function bar() { return 2; };
define('BAR', bar());
class Foo {
const BAR = BAR; // OK
}
Per ulteriori informazioni, vedere le costanti nel manuale .
Usando :: class per recuperare il nome della classe
PHP 5.5 ha introdotto la sintassi della ::class
per recuperare il nome completo della classe, prendendo in considerazione lo scope namespace e le istruzioni use
.
namespace foo;
use bar\Bar;
echo json_encode(Bar::class); // "bar\\Bar"
echo json_encode(Foo::class); // "foo\\Foo"
echo json_encode(\Foo::class); // "Foo"
Quanto sopra funziona anche se le classi non sono nemmeno definite (cioè questo frammento di codice funziona da solo).
Questa sintassi è utile per le funzioni che richiedono un nome di classe. Ad esempio, può essere utilizzato con class_exists
per verificare che esista una classe. Nessun errore verrà generato indipendentemente dal valore di ritorno in questo frammento:
class_exists(ThisClass\Will\NeverBe\Loaded::class, false);
Legame statico tardivo
In PHP 5.3+ e versioni successive è possibile utilizzare il binding statico avanzato per controllare da quale classe viene chiamata una proprietà o un metodo statico. È stato aggiunto per superare il problema inerente al risolutore di self::
scope. Prendi il seguente codice
class Horse {
public static function whatToSay() {
echo 'Neigh!';
}
public static function speak() {
self::whatToSay();
}
}
class MrEd extends Horse {
public static function whatToSay() {
echo 'Hello Wilbur!';
}
}
Ci si aspetterebbe che la classe MrEd
sovrascriva la funzione genitore whatToSay()
. Ma quando gestiamo questo otteniamo qualcosa di inaspettato
Horse::speak(); // Neigh!
MrEd::speak(); // Neigh!
Il problema è che self::whatToSay();
si può riferire solo alla classe del Horse
, il che significa che non obbedisce a MrEd
. Se passiamo al resolutor static::
scope, non abbiamo questo problema. Questo metodo più recente dice alla classe di obbedire all'istanza che la chiama. Così otteniamo l'eredità che ci aspettiamo
class Horse {
public static function whatToSay() {
echo 'Neigh!';
}
public static function speak() {
static::whatToSay(); // Late Static Binding
}
}
Horse::speak(); // Neigh!
MrEd::speak(); // Hello Wilbur!
Classi astratte
Una classe astratta è una classe che non può essere istanziata. Le classi astratte possono definire metodi astratti, che sono metodi senza alcun corpo, solo una definizione:
abstract class MyAbstractClass {
abstract public function doSomething($a, $b);
}
Le classi astratte dovrebbero essere estese da una classe di bambini che può quindi fornire l'implementazione di questi metodi astratti.
Lo scopo principale di una classe come questa è fornire un tipo di modello che consenta alle classi di bambini di ereditare da "forzare" una struttura a cui aderire. Approfondiamo questo con un esempio:
In questo esempio implementeremo un'interfaccia Worker
. Per prima cosa definiamo l'interfaccia:
interface Worker {
public function run();
}
Per facilitare lo sviluppo di ulteriori implementazioni di Worker, creeremo una classe worker astratta che già fornisce il metodo run()
dall'interfaccia, ma specifica alcuni metodi astratti che devono essere compilati da qualsiasi classe child:
abstract class AbstractWorker implements Worker {
protected $pdo;
protected $logger;
public function __construct(PDO $pdo, Logger $logger) {
$this->pdo = $pdo;
$this->logger = $logger;
}
public function run() {
try {
$this->setMemoryLimit($this->getMemoryLimit());
$this->logger->log("Preparing main");
$this->prepareMain();
$this->logger->log("Executing main");
$this->main();
} catch (Throwable $e) {
// Catch and rethrow all errors so they can be logged by the worker
$this->logger->log("Worker failed with exception: {$e->getMessage()}");
throw $e;
}
}
private function setMemoryLimit($memoryLimit) {
ini_set('memory_limit', $memoryLimit);
$this->logger->log("Set memory limit to $memoryLimit");
}
abstract protected function getMemoryLimit();
abstract protected function prepareMain();
abstract protected function main();
}
Prima di tutto, abbiamo fornito un metodo astratto getMemoryLimit()
. Qualsiasi classe che provenga da AbstractWorker
deve fornire questo metodo e restituire il limite di memoria. AbstractWorker
imposta quindi il limite di memoria e lo registra.
In secondo luogo, AbstractWorker
chiama i prepareMain()
e main()
, dopo aver registrato che sono stati chiamati.
Infine, tutte queste chiamate al metodo sono state raggruppate in un blocco try
- catch
. Quindi, se uno qualsiasi dei metodi astratti definiti dalla classe figlia lancia un'eccezione, cattureremo quell'eccezione, la registreremo e la ricolloceremo. Ciò impedisce a tutte le classi child di dover implementare esse stesse.
Ora consente di definire una classe figlio che si estende da AbstractWorker
:
class TranscactionProcessorWorker extends AbstractWorker {
private $transactions;
protected function getMemoryLimit() {
return "512M";
}
protected function prepareMain() {
$stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500");
$stmt->execute();
$this->transactions = $stmt->fetchAll();
}
protected function main() {
foreach ($this->transactions as $transaction) {
// Could throw some PDO or MYSQL exception, but that is handled by the AbstractWorker
$stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id = {$transaction['id']} LIMIT 1");
$stmt->execute();
}
}
}
Come puoi vedere, TransactionProcessorWorker
stato piuttosto semplice da implementare, poiché dovevamo solo specificare il limite di memoria e preoccuparci delle azioni effettive che era necessario eseguire. Non è necessaria alcuna gestione degli errori in TransactionProcessorWorker
perché viene gestito in AbsractWorker
.
Nota importante
Quando si eredita da una classe astratta, tutti i metodi contrassegnati come astratti nella dichiarazione della classe del genitore devono essere definiti dal bambino (o anche il bambino stesso deve essere contrassegnato come astratto); inoltre, questi metodi devono essere definiti con la stessa (o meno limitata) visibilità. Ad esempio, se il metodo astratto è definito come protetto, l'implementazione della funzione deve essere definita come protetta o pubblica, ma non privata.
Tratto dalla documentazione di PHP per l'astrazione della classe .
Se non si definiscono i metodi delle classi astratte padre all'interno della classe figlio, verrà generato un errore PHP irreversibile come il seguente.
Errore irreversibile: la classe X contiene 1 metodo astratto e deve quindi essere dichiarata astratta o implementare i restanti metodi (X :: x) in
Namespacing e Autoloading
Tecnicamente, il caricamento automatico funziona eseguendo una richiamata quando una classe PHP è richiesta ma non trovata. Tali callback di solito tentano di caricare queste classi.
Generalmente, l'autoloading può essere interpretato come il tentativo di caricare file PHP (in particolare file di classe PHP, dove un file sorgente PHP è dedicato per una classe specifica) da percorsi appropriati in base al nome completo della classe (FQN) quando è necessaria una classe .
Supponiamo di avere queste classi:
File di classe per application\controllers\Base
:
<?php
namespace application\controllers { class Base {...} }
File di classe per application\controllers\Control
:
<?php
namespace application\controllers { class Control {...} }
File di classe per application\models\Page
:
<?php
namespace application\models { class Page {...} }
Sotto la cartella di origine, queste classi devono essere posizionate nei percorsi come loro FQN rispettivamente:
- Cartella di origine
-
applications
-
controllers
-
Base.php
-
Control.php
-
-
models
-
Page.php
-
-
-
Questo approccio consente di risolvere a livello di codice il percorso del file di classe in base al FQN, utilizzando questa funzione:
function getClassPath(string $sourceFolder, string $className, string $extension = ".php") {
return $sourceFolder . "/" . str_replace("\\", "/", $className) . $extension; // note that "/" works as a directory separator even on Windows
}
La funzione spl_autoload_register
ci consente di caricare una classe quando necessario utilizzando una funzione definita dall'utente:
const SOURCE_FOLDER = __DIR__ . "/src";
spl_autoload_register(function (string $className) {
$file = getClassPath(SOURCE_FOLDER, $className);
if (is_readable($file)) require_once $file;
});
Questa funzione può essere ulteriormente estesa per utilizzare i metodi di fallback di caricamento:
const SOURCE_FOLDERS = [__DIR__ . "/src", "/root/src"]);
spl_autoload_register(function (string $className) {
foreach(SOURCE_FOLDERS as $folder) {
$extensions = [
// do we have src/Foo/Bar.php5_int64?
".php" . PHP_MAJOR_VERSION . "_int" . (PHP_INT_SIZE * 8),
// do we have src/Foo/Bar.php7?
".php" . PHP_MAJOR_VERSION,
// do we have src/Foo/Bar.php_int64?
".php" . "_int" . (PHP_INT_SIZE * 8),
// do we have src/Foo/Bar.phps?
".phps"
// do we have src/Foo/Bar.php?
".php"
];
foreach($extensions as $ext) {
$path = getClassPath($folder, $className, $extension);
if(is_readable($path)) return $path;
}
}
});
Si noti che PHP non tenta di caricare le classi ogni volta che viene caricato un file che utilizza questa classe. Può essere caricato nel mezzo di uno script o anche in funzioni di spegnimento. Questo è uno dei motivi per cui gli sviluppatori, specialmente quelli che usano il caricamento automatico, dovrebbero evitare di sostituire i file sorgente in esecuzione nel runtime, specialmente nei file phar.
Associazione dinamica
L'associazione dinamica, anche definita come sovrascrittura del metodo, è un esempio di polimorfismo del tempo di esecuzione che si verifica quando più classi contengono diverse implementazioni dello stesso metodo, ma l'oggetto su cui verrà chiamato il metodo è sconosciuto fino al runtime .
Ciò è utile se determinate condizioni determinano quale classe verrà utilizzata per eseguire un'azione, in cui l'azione viene denominata la stessa in entrambe le classi.
interface Animal {
public function makeNoise();
}
class Cat implements Animal {
public function makeNoise
{
$this->meow();
}
...
}
class Dog implements Animal {
public function makeNoise {
$this->bark();
}
...
}
class Person {
const CAT = 'cat';
const DOG = 'dog';
private $petPreference;
private $pet;
public function isCatLover(): bool {
return $this->petPreference == self::CAT;
}
public function isDogLover(): bool {
return $this->petPreference == self::DOG;
}
public function setPet(Animal $pet) {
$this->pet = $pet;
}
public function getPet(): Animal {
return $this->pet;
}
}
if($person->isCatLover()) {
$person->setPet(new Cat());
} else if($person->isDogLover()) {
$person->setPet(new Dog());
}
$person->getPet()->makeNoise();
Nell'esempio sopra, la classe Animal
( Dog|Cat
) che makeNoise
è sconosciuta fino al runtime a seconda della proprietà all'interno della classe User
.
Metodo e visibilità della proprietà
Esistono tre tipi di visibilità che è possibile applicare ai metodi ( funzioni classe / oggetto ) e proprietà ( variabili classe / oggetto ) all'interno di una classe, che forniscono il controllo di accesso per il metodo o la proprietà a cui sono applicati.
È possibile leggere estesamente su questi nella Documentazione di PHP per la visibilità OOP .
Pubblico
Dichiarare un metodo o una proprietà come public
consente al metodo o alla proprietà di accedere:
- La classe che l'ha dichiarata.
- Le classi che estendono la classe dichiarata.
- Qualsiasi oggetto, classe o codice esterno al di fuori della gerarchia di classi.
Un esempio di questo accesso public
sarebbe:
class MyClass {
// Property
public $myProperty = 'test';
// Method
public function myMethod() {
return $this->myProperty;
}
}
$obj = new MyClass();
echo $obj->myMethod();
// Out: test
echo $obj->myProperty;
// Out: test
protetta
Dichiarare un metodo o una proprietà come protected
consente al metodo o alla proprietà di accedere:
- La classe che l'ha dichiarata.
- Le classi che estendono la classe dichiarata.
Ciò non consente a oggetti, classi o codici esterni al di fuori della gerarchia di classi di accedere a questi metodi o proprietà. Se qualcosa che utilizza questo metodo / proprietà non ha accesso ad esso, non sarà disponibile e verrà generato un errore. Solo le istanze del sé dichiarato (o sottoclassi delle stesse) hanno accesso ad esso.
Un esempio di questo accesso protected
potrebbe essere:
class MyClass {
protected $myProperty = 'test';
protected function myMethod() {
return $this->myProperty;
}
}
class MySubClass extends MyClass {
public function run() {
echo $this->myMethod();
}
}
$obj = new MySubClass();
$obj->run(); // This will call MyClass::myMethod();
// Out: test
$obj->myMethod(); // This will fail.
// Out: Fatal error: Call to protected method MyClass::myMethod() from context ''
L'esempio precedente nota che è possibile accedere solo agli elementi protected
all'interno del proprio ambito. In sostanza: "Ciò che è nella casa può essere accessibile solo dall'interno della casa".
Privato
Dichiarare un metodo o una proprietà come private
consente al metodo o alla proprietà di accedere:
- La classe che lo ha dichiarato Solo (non sottoclassi).
Un metodo o una proprietà private
è visibile e accessibile solo all'interno della classe che lo ha creato.
Si noti che gli oggetti dello stesso tipo avranno accesso agli altri membri privati e protetti anche se non sono le stesse istanze.
class MyClass {
private $myProperty = 'test';
private function myPrivateMethod() {
return $this->myProperty;
}
public function myPublicMethod() {
return $this->myPrivateMethod();
}
public function modifyPrivatePropertyOf(MyClass $anotherInstance) {
$anotherInstance->myProperty = "new value";
}
}
class MySubClass extends MyClass {
public function run() {
echo $this->myPublicMethod();
}
public function runWithPrivate() {
echo $this->myPrivateMethod();
}
}
$obj = new MySubClass();
$newObj = new MySubClass();
// This will call MyClass::myPublicMethod(), which will then call
// MyClass::myPrivateMethod();
$obj->run();
// Out: test
$obj->modifyPrivatePropertyOf($newObj);
$newObj->run();
// Out: new value
echo $obj->myPrivateMethod(); // This will fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context ''
echo $obj->runWithPrivate(); // This will also fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context 'MySubClass'
Come notato, puoi accedere al metodo / proprietà private
solo dalla classe definita.
Chiamare un costruttore genitore durante l'istanziazione di un bambino
Un comune errore delle classi figlie è che, se il genitore e il figlio contengono entrambi un metodo di costruzione ( __construct()
), verrà eseguito solo il costruttore della classe figlio . Potrebbero esserci occasioni in cui è necessario eseguire il metodo genitore __construct()
dal suo figlio. Se è necessario, è necessario utilizzare il resolutor parent::
scope:
parent::__construct();
Ora sfruttando il fatto che all'interno di una situazione del mondo reale assomiglierebbe a qualcosa:
class Foo {
function __construct($args) {
echo 'parent';
}
}
class Bar extends Foo {
function __construct($args) {
parent::__construct($args);
}
}
Quanto sopra eseguirà il genitore __construct()
con conseguente esecuzione echo
.
Parola chiave finale
Def: Final Keyword impedisce alle classi figlie di sovrascrivere un metodo anteponendo la definizione alla finale. Se la classe stessa viene definita definitiva, non può essere estesa
Metodo finale
class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
public function moreTesting() {
echo "ChildClass::moreTesting() called\n";
}
}
// Results in Fatal error: Cannot override final method BaseClass::moreTesting()
Classe finale:
final class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
// Here it doesn't matter if you specify the function as final or not
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
}
// Results in Fatal error: Class ChildClass may not inherit from final class (BaseClass)
Costanti finali: a differenza di Java, la parola chiave final
non viene utilizzata per le costanti di classe in PHP. Utilizza invece la parola chiave const
.
Perché devo usare final
?
- Prevenire una massiccia catena di successione ereditaria
- Composizione incoraggiante
- Forza lo sviluppatore a pensare all'API pubblica dell'utente
- Forza lo sviluppatore a ridurre l'API pubblica di un oggetto
- Una classe
final
può sempre essere resa estendibile -
extends
incapsulamento delle pause - Non hai bisogno di quella flessibilità
- Sei libero di cambiare il codice
Quando evitare il final
: le lezioni finali funzionano efficacemente solo con le seguenti ipotesi:
- Esiste un'astrazione (interfaccia) implementata dalla classe finale
- Tutte le API pubbliche della classe finale fanno parte di tale interfaccia
$ questo, auto e statico più il singleton
Usa
$this
per fare riferimento all'oggetto corrente. Usa teself
per fare riferimento alla classe corrente. In altre parole, usa$this->member
per i$this->member
non static, usaself::$member
per i membri static.
Nell'esempio seguente, sayHello()
e sayGoodbye()
stanno utilizzando self
e $this
differenza può essere osservata qui.
class Person {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function getTitle() {
return $this->getName()." the person";
}
public function sayHello() {
echo "Hello, I'm ".$this->getTitle()."<br/>";
}
public function sayGoodbye() {
echo "Goodbye from ".self::getTitle()."<br/>";
}
}
class Geek extends Person {
public function __construct($name) {
parent::__construct($name);
}
public function getTitle() {
return $this->getName()." the geek";
}
}
$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();
static
riferisce a qualsiasi classe nella gerarchia in cui hai chiamato il metodo. Consente un migliore riutilizzo delle proprietà di classe statiche quando le classi vengono ereditate.
Considera il seguente codice:
class Car {
protected static $brand = 'unknown';
public static function brand() {
return self::$brand."\n";
}
}
class Mercedes extends Car {
protected static $brand = 'Mercedes';
}
class BMW extends Car {
protected static $brand = 'BMW';
}
echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();
Questo non produce il risultato desiderato:
sconosciuto
sconosciuto
sconosciuto
Questo perché il self
riferisce alla classe Car
ogni volta che viene chiamato il metodo brand()
.
Per fare riferimento alla classe corretta, è necessario utilizzare invece static
:
class Car {
protected static $brand = 'unknown';
public static function brand() {
return static::$brand."\n";
}
}
class Mercedes extends Car {
protected static $brand = 'Mercedes';
}
class BMW extends Car {
protected static $brand = 'BMW';
}
echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();
Questo produce l'output desiderato:
sconosciuto
BMW
Mercedes
Vedi anche Legame statico tardivo
Il singleton
Se si dispone di un oggetto che è costoso da creare o rappresenta una connessione ad alcune risorse esterne che si desidera riutilizzare, ad esempio una connessione al database in cui non esiste alcun pool di connessioni o un socket per altri sistemi, è possibile utilizzare le parole chiave static
e self
in un classe per renderlo un singleton. Ci sono forti opinioni sul fatto che il modello singleton debba o non debba essere usato, ma ha i suoi usi.
class Singleton {
private static $instance = null;
public static function getInstance(){
if(!isset(self::$instance)){
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
// Do constructor stuff
}
}
Come puoi vedere nel codice di esempio, stiamo definendo una proprietà statica privata $instance
per contenere il riferimento all'oggetto. Poiché questo è statico, questo riferimento è condiviso tra TUTTI gli oggetti di questo tipo.
Il metodo getInstance()
utilizza un metodo noto come lazy instantiation per ritardare la creazione dell'oggetto all'ultimo momento possibile in quanto non si desidera che gli oggetti inutilizzati che si trovano in memoria non vengano mai utilizzati. Inoltre, consente di risparmiare tempo e CPU durante il caricamento della pagina senza dover caricare più oggetti del necessario. Il metodo sta verificando se l'oggetto è impostato, creando in caso contrario e restituendolo. Ciò garantisce che venga creato un solo oggetto di questo tipo.
Stiamo anche impostando il costruttore come privato per garantire che nessuno lo crei con la new
parola chiave dall'esterno. Se devi ereditare da questa classe, modifica le parole chiave private
in protected
.
Per usare questo oggetto devi solo scrivere quanto segue:
$singleton = Singleton::getInstance();
Ora ti imploro di usare l'iniezione di dipendenze dove puoi e mirare a oggetti liberamente accoppiati, ma a volte non è ragionevole e il modello singleton può essere utile.
Caricamento automatico
Nessuno vuole require
o include
ogni volta che viene utilizzata una classe o un'eredità. Poiché può essere doloroso e facile da dimenticare, PHP offre il cosiddetto autoloading. Se stai già utilizzando Composer, leggi l' autoload utilizzando Composer .
Che cosa è esattamente l'autoloading?
Il nome dice praticamente tutto. Non è necessario per ottenere il file in cui la classe richiesta è memorizzato in, ma carico di PHP automatica mente s esso.
Come posso fare questo in PHP di base senza codice di terze parti?
Esiste la funzione __autoload
, ma è meglio usare spl_autoload_register
. Queste funzioni saranno considerate da PHP ogni volta che una classe non è definita all'interno dello spazio dato. Quindi aggiungere un autoload a un progetto esistente non è un problema, in quanto le classi definite (tramite require
ie) funzioneranno come prima. Per motivi di precisione, i seguenti esempi useranno funzioni anonime, se si utilizza PHP <5.3, è possibile definire la funzione e passare il suo nome come argomento a spl_autoload_register
.
Esempi
spl_autoload_register(function ($className) {
$path = sprintf('%s.php', $className);
if (file_exists($path)) {
include $path;
} else {
// file not found
}
});
Il codice sopra tenta semplicemente di includere un nome file con il nome della classe e l'estensione aggiunta ".php" usando sprintf
. Se FooBar
deve essere caricato, sembra che FooBar.php
esista e, in tal caso, includerlo.
Naturalmente questo può essere esteso per adattarsi alle esigenze individuali del progetto. Se _
all'interno di un nome di classe viene utilizzato per raggruppare, ad esempio User_Post
e User_Image
entrambi si riferiscono a User
, entrambe le classi possono essere mantenute in una cartella denominata "Utente" in questo modo:
spl_autoload_register(function ($className) {
// replace _ by / or \ (depending on OS)
$path = sprintf('%s.php', str_replace('_', DIRECTORY_SEPARATOR, $className) );
if (file_exists($path)) {
include $path;
} else {
// file not found
}
});
La classe User_Post
verrà ora caricata da "Utente / Post.php", ecc.
spl_autoload_register
può essere adattato alle varie esigenze. Tutti i tuoi file con le classi sono denominati "class.CLASSNAME.php"? Nessun problema. Numerosi annidamenti ( User_Post_Content
=> "Utente / Posta / Contenuto.php")? Nessun problema neanche.
Se si desidera un meccanismo di caricamento automatico più elaborato e ancora non si desidera includere Composer, è possibile lavorare senza aggiungere librerie di terze parti.
spl_autoload_register(function ($className) {
$path = sprintf('%1$s%2$s%3$s.php',
// %1$s: get absolute path
realpath(dirname(__FILE__)),
// %2$s: / or \ (depending on OS)
DIRECTORY_SEPARATOR,
// %3$s: don't wory about caps or not when creating the files
strtolower(
// replace _ by / or \ (depending on OS)
str_replace('_', DIRECTORY_SEPARATOR, $className)
)
);
if (file_exists($path)) {
include $path;
} else {
throw new Exception(
sprintf('Class with name %1$s not found. Looked in %2$s.',
$className,
$path
)
);
}
});
Usando autoloader come questo, puoi scrivere felicemente il codice in questo modo:
require_once './autoload.php'; // where spl_autoload_register is defined
$foo = new Foo_Bar(new Hello_World());
Utilizzando le classi:
class Foo_Bar extends Foo {}
class Hello_World implements Demo_Classes {}
Questi esempi includeranno classi da foo/bar.php
, foo.php
, hello/world.php
e demo/classes.php
.
Classi anonime
Le classi anonime sono state introdotte in PHP 7 per consentire la creazione di oggetti one-off veloci. Possono prendere gli argomenti del costruttore, estendere altre classi, implementare interfacce e usare i tratti proprio come le classi normali possono.
Nella sua forma più semplice, una classe anonima ha il seguente aspetto:
new class("constructor argument") {
public function __construct($param) {
var_dump($param);
}
}; // string(20) "constructor argument"
Annidare una classe anonima all'interno di un'altra classe non gli dà accesso a metodi o proprietà private o protette di quella classe esterna. L'accesso ai metodi e alle proprietà protette della classe esterna può essere ottenuto estendendo la classe esterna dalla classe anonima. L'accesso alle proprietà private della classe esterna può essere ottenuto passandole attraverso il costruttore della classe anonima.
Per esempio:
class Outer {
private $prop = 1;
protected $prop2 = 2;
protected function func1() {
return 3;
}
public function func2() {
// passing through the private $this->prop property
return new class($this->prop) extends Outer {
private $prop3;
public function __construct($prop) {
$this->prop3 = $prop;
}
public function func3() {
// accessing the protected property Outer::$prop2
// accessing the protected method Outer::func1()
// accessing the local property self::$prop3 that was private from Outer::$prop
return $this->prop2 + $this->func1() + $this->prop3;
}
};
}
}
echo (new Outer)->func2()->func3(); // 6
Definizione di una classe base
Un oggetto in PHP contiene variabili e funzioni. Gli oggetti appartengono tipicamente a una classe, che definisce le variabili e le funzioni che tutti gli oggetti di questa classe conterranno.
La sintassi per definire una classe è:
class Shape {
public $sides = 0;
public function description() {
return "A shape with $this->sides sides.";
}
}
Una volta definita una classe, è possibile creare un'istanza utilizzando:
$myShape = new Shape();
Le variabili e le funzioni sull'oggetto sono accessibili in questo modo:
$myShape = new Shape();
$myShape->sides = 6;
print $myShape->description(); // "A shape with 6 sides"
Costruttore
Le classi possono definire un metodo speciale __construct()
, che viene eseguito come parte della creazione dell'oggetto. Questo è spesso usato per specificare i valori iniziali di un oggetto:
class Shape {
public $sides = 0;
public function __construct($sides) {
$this->sides = $sides;
}
public function description() {
return "A shape with $this->sides sides.";
}
}
$myShape = new Shape(6);
print $myShape->description(); // A shape with 6 sides
Estendere un'altra classe
Le definizioni di classe possono estendere le definizioni di classe esistenti, aggiungendo nuove variabili e funzioni e modificando quelle definite nella classe genitore.
Ecco una classe che estende l'esempio precedente:
class Square extends Shape {
public $sideLength = 0;
public function __construct($sideLength) {
parent::__construct(4);
$this->sideLength = $sideLength;
}
public function perimeter() {
return $this->sides * $this->sideLength;
}
public function area() {
return $this->sideLength * $this->sideLength;
}
}
La classe Square
contiene variabili e comportamenti sia per la classe Shape
che per la classe Square
:
$mySquare = new Square(10);
print $mySquare->description()/ // A shape with 4 sides
print $mySquare->perimeter() // 40
print $mySquare->area() // 100