Per convenzione si definisce una classe per file e la classe e il file hanno lo stesso nome.

Se voglio creare un oggetto generico, non di una specifica classe, posso usare la classe predefinita stdClass.

<?php

$str = '{"a": 1, "b": 2. "c": 3}';
$arr = json_decode($str); //non passare il secondo parametro alla funzione json_decode
var_dump($arr); //stampa oggetto della classe stdClass
var_dump($arr->a); //stampa 1

$obj = new \stdClass();
$obj->a = 1;
$obj->b = 2;

$arr = [1, 2, 3];
$obj = (object) $arr; // (object) fa il type casting ad oggetto e trasforma l'array in un oggetto della classe stdClass

?>

Oppure posso creare un oggetto di una classe anonima (in realtà un nome gli viene assegnato in automatico, si può verificare con il metodo get_class($obj);):

<? php
$obj = new class(1, 2, 3, ) {
    public function __construct(public int $x, public int $y, public int $z) {
}
}
?>

Proprietà e metodi

Le proprietà possono essere tipizzate nel momento in cui vengono dichiarate. Non è necessario assegnare un valore al momento della dichiarazione, si può fare anche nel costruttore, il valore di default è NULL. 

All'interno della classe si possono definire anche delle costanti con la parola chiave const e poi si accede alle costanti con NomeClasse::NOME_COSTANTE (oppure self al posto di NomeClasse se siamo dentro la classe stessa). Le costanti appartengono alla classe e non al singolo oggetto. In alternativa si possono definire delle proprietà statiche della classe. Si possono anche aggiungere gli access modifier, di default le costanti sono public.

<?php

class Circle
{
    const PI = 3.14; //la const è della classe non dell'oggetto
}

//fuori dalla classe
echo Circle::PI;

//si può anche mettere il nome della classe in una variabile
$className = 'Circle';
echo $className::PI;

?>

Le classi figlie possono fare l'override delle costanti ridefinendole con lo stesso nome.

Con la parola chiave readonly si possono rendere le proprietà non modificabili dopo che sono state inizializzate (ma non le rende immutabili).

Access Modifier

public significa disponibile per chiunque, anche al di fuori della classe

private significa che la proprietà o il metodo sono accessibili solo all'interno della classe

protected significa che la proprietà è accessibile alle sottoclassi

Se non è specificato è public di default.

Se una proprietà non deve essere modificata dall'esterno allora deve essere messa come private e poi si utilizza un metodo pubblico che restituisca quella variabile.

Costruttore

Primo metodo:

<?php

class Transaction
{
    private float $amount;
    private string $description;

    public function __construct(float $amout, string $description) 
    {
        $this->amount = $amount;
        $this->description = $description;
    }
}

?>

Secondo metodo (constructor property promotion):

<?php

class Transaction
{
    public function __construct(
        private float $amount, //mettendo qui access modifier funziona come dichiarazione e assegnazione
        private string $description
        // si può anche assegnare qui private string $description = 'ciao';
    ) {
        echo $amount;
    }
}

?>

Si possono anche mescolare i due metodi e combinare proprietà promosse con proprietà non promosse.

Namespaces

Dato che è possibile avere più classi con lo stesso nome c'è bisogno di un modo per differenziare a quale classe ci stiamo riferendo quando creiamo l'oggetto, quindi in ogni classe definiamo un namespace usando il comando namespace e poi lo importiamo dove stiamo istanziando l'oggetto utilizzando il comando use.

Quando siamo in una classe dove abbiamo definito un namespace e dobbiamo usare un metodo che si trova nel namespace globale dobbiamo mettere prima un \. Oppure si può importare con use la classe globale. Se usiamo la funzione incude() per includere un altro file dobbiamo anche in quello usare use per importare le classi che ci servono.

Autoloading

Al posto di usare require_once per importare altre classi si può usare la funzione spl_autoload_register($callback) che importa automaticamente il file corrispondente quando si usa una classe che non è stata importata. 

Esempio senza autoloading: 

<?php

//in functions.php
function load_model($class_name)
{
	$path_to_file = 'models/' . $class_name . '.php';

	if (file_exists($path_to_file)) {
		require $path_to_file;
	}
}

//in index.php
require_once 'functions.php';

load_model('Person');

$person = new Person();

?>

Esempio con autoloading:

<?php

//in functions.php
function load_model($class_name)
{
	$path_to_file = 'models/' . $class_name . '.php';

	if (file_exists($path_to_file)) {
		require $path_to_file;
	}
    else {
    error_log("Autoload fallito: il file " . $path_to_file . " non esiste");
    }
}

spl_autoload_register('load_model');

//in index.php
require 'functions.php';
//non chiamiamo più load_model('Person');
$person = new Person();

//controllo di sicurezza, perchè magari a livello di nome di file la classe c'è
//ma per qualche motivo dentro il file la classe si chiama diversamente
if (!class_exists('Person', false)) { //secondo parametro a false non fa chiamare l'autoloader
    error_log("Impossibile trovare la classe Person dopo l'autoload.");
}

?>

@TODO fare approfondimento su Composer https://github.com/elenat82/Acquia-Drupal-Site-Builder-Exam-Prep/blob/master/05_modules_and_themes/01_installing_contributed_module.md

Parole chiave $this e self

All'interno di un metodo statico non si può usare $this perchè il metodo statico verrà richiamato sulla classe e non sull'oggetto quindi non ci sarebbe nessun oggetto a cui $this potrebbe riferirsi, si usa la parola chiave self per indicare la classe stessa.

<?php

class MyClass
{
	public static function staticMethod()
	{
	}
    //all'interno della classe
    self::staticMethod(arguments);
}

//all'esterno della classe 
MyClass::staticMethod();

?>

Anche le proprietà possono essere statiche:

<?php

class MyClass
{
	public static $staticProperty; //qui con il $

    //all'interno della classe
    self::staticProperty; //qui senza il $
}

// fuori dalla classe
MyClass::$staticProperty; //qui con il $

?>

Confronto tra oggetti

Quando una variabile viene creata il nome e il valore vengono memorizzati separatamente e il nome è salvato nella symbols table. Ogni elemento della symbols table punta ad una sorta di contenitore che contiene il valore della variabile, quindi possiamo dire che una variabile è un puntatore al container che contiene il valore. Questo contenitore è chiamato z val o zen value.

Introspection

Immutabilità

L'immutabilità è un principio di programmazione in cui un oggetto, una volta creato, non può più cambiare il suo stato interno, per modificare un valore non si cambia l'oggetto originale ma si crea una nuova versione che contiene la modifica.

class Money
{
    public function __construct(
        public readonly int $amount, //si può mettere public, tanto è readonly, se no si mette private con getter public
        public readonly string $currency
    ) {}

    //non mettiamo nessun setter!

    // Metodo Wither, cioè che non modifica $this ma crea un clone
    public function withAmount(int $newAmount): self
    {
        return new self($newAmount, $this->currency);
    }
}

$price = new Money(100, 'EUR');

// questo da errore
$price->amount = 200; 

//questo funziona
$newPrice = $price->withAmount(200);

echo $price->amount;    // 100 
echo $newPrice->amount; // 200

?>