Εισαγωγή στον αντικειμενοστραφή προγραμματισμό

Ο όρος “OOP” σημαίνει αντικειμενοστραφής προγραμματισμός, ο οποίος είναι ένας τρόπος οργάνωσης και δόμησης του κώδικα. Ο OOP μας επιτρέπει να βλέπουμε ένα πρόγραμμα ως μια συλλογή αντικειμένων που επικοινωνούν μεταξύ τους, αντί για μια ακολουθία εντολών και συναρτήσεων.

Στο OOP, ένα “αντικείμενο” είναι μια μονάδα που περιέχει δεδομένα και συναρτήσεις που λειτουργούν με αυτά τα δεδομένα. Τα αντικείμενα δημιουργούνται με βάση “κλάσεις”, οι οποίες μπορούν να εκληφθούν ως σχέδια ή πρότυπα για αντικείμενα. Αφού έχουμε μια κλάση, μπορούμε να δημιουργήσουμε την “παρουσία” της, η οποία είναι ένα συγκεκριμένο αντικείμενο κατασκευασμένο από αυτή την κλάση.

Ας δούμε πώς μπορούμε να δημιουργήσουμε μια απλή κλάση στην PHP. Όταν ορίζουμε μια κλάση, χρησιμοποιούμε τη λέξη-κλειδί “class”, ακολουθούμενη από το όνομα της κλάσης και στη συνέχεια από τις τεθλασμένες αγκύλες που περικλείουν τις συναρτήσεις της κλάσης (που ονομάζονται “μέθοδοι”) και τις μεταβλητές της κλάσης (που ονομάζονται “ιδιότητες” ή “χαρακτηριστικά”):

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση με όνομα Car με μια συνάρτηση (ή “μέθοδο”) που ονομάζεται honk.

Κάθε τάξη θα πρέπει να επιλύει μόνο μία κύρια εργασία. Εάν μια τάξη κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να χωριστεί σε μικρότερες, εξειδικευμένες τάξεις.

Οι κλάσεις συνήθως αποθηκεύονται σε ξεχωριστά αρχεία για να διατηρείται ο κώδικας οργανωμένος και εύκολος στην πλοήγηση. Το όνομα του αρχείου πρέπει να ταιριάζει με το όνομα της κλάσης, οπότε για την κλάση Car, το όνομα του αρχείου θα είναι Car.php.

Όταν ονομάζετε κλάσεις, είναι καλό να ακολουθείτε τη σύμβαση “PascalCase”, δηλαδή κάθε λέξη στο όνομα αρχίζει με κεφαλαίο γράμμα και δεν υπάρχουν υπογραμμίσεις ή άλλα διαχωριστικά. Οι μέθοδοι και οι ιδιότητες ακολουθούν τη σύμβαση “camelCase”, δηλαδή ξεκινούν με πεζά γράμματα.

Ορισμένες μέθοδοι στην PHP έχουν ειδικούς ρόλους και προτάσσονται με __ (δύο υπογράμμιση). Μία από τις πιο σημαντικές ειδικές μεθόδους είναι ο “κατασκευαστής”, με την ένδειξη __construct. Ο κατασκευαστής είναι μια μέθοδος που καλείται αυτόματα κατά τη δημιουργία μιας νέας περίπτωσης μιας κλάσης.

Συχνά χρησιμοποιούμε τον κατασκευαστή για να ορίσουμε την αρχική κατάσταση ενός αντικειμένου. Για παράδειγμα, κατά τη δημιουργία ενός αντικειμένου που αναπαριστά ένα άτομο, μπορεί να χρησιμοποιήσετε τον κατασκευαστή για να ορίσετε την ηλικία, το όνομα ή άλλα χαρακτηριστικά του.

Ας δούμε πώς μπορείτε να χρησιμοποιήσετε έναν κατασκευαστή στην PHP:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25

Σε αυτό το παράδειγμα, η κλάση Person έχει μια ιδιότητα (μεταβλητή) $age και έναν κατασκευαστή που ορίζει αυτή την ιδιότητα. Η μέθοδος howOldAreYou() παρέχει στη συνέχεια πρόσβαση στην ηλικία του ατόμου.

Η ψευδομεταβλητή $this χρησιμοποιείται μέσα στην κλάση για την πρόσβαση στις ιδιότητες και τις μεθόδους του αντικειμένου.

Η λέξη-κλειδί new χρησιμοποιείται για τη δημιουργία μιας νέας περίπτωσης μιας κλάσης. Στο παραπάνω παράδειγμα, δημιουργήσαμε ένα νέο άτομο ηλικίας 25 ετών.

Μπορείτε επίσης να ορίσετε προεπιλεγμένες τιμές για τις παραμέτρους του κατασκευαστή, εάν δεν έχουν καθοριστεί κατά τη δημιουργία ενός αντικειμένου. Για παράδειγμα:

class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20

Σε αυτό το παράδειγμα, αν δεν καθορίσετε μια ηλικία κατά τη δημιουργία ενός αντικειμένου Person, θα χρησιμοποιηθεί η προεπιλεγμένη τιμή 20.

Το ωραίο είναι ότι ο ορισμός της ιδιότητας με την αρχικοποίησή της μέσω του κατασκευαστή μπορεί να συντομευτεί και να απλοποιηθεί ως εξής:

class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}

Για λόγους πληρότητας, εκτός από τους κατασκευαστές, τα αντικείμενα μπορούν να έχουν καταστροφείς (μέθοδος __destruct) που καλούνται πριν από την αποδέσμευση του αντικειμένου από τη μνήμη.

Χώροι ονομάτων

Οι χώροι ονομάτων μας επιτρέπουν να οργανώνουμε και να ομαδοποιούμε συναφείς κλάσεις, συναρτήσεις και σταθερές αποφεύγοντας συγκρούσεις ονομάτων. Μπορείτε να τα φανταστείτε όπως τους φακέλους σε έναν υπολογιστή, όπου κάθε φάκελος περιέχει αρχεία που σχετίζονται με ένα συγκεκριμένο έργο ή θέμα.

Οι χώροι ονομάτων είναι ιδιαίτερα χρήσιμοι σε μεγαλύτερα έργα ή όταν χρησιμοποιείτε βιβλιοθήκες τρίτων, όπου μπορεί να προκύψουν συγκρούσεις ονοματοδοσίας κλάσεων.

Φανταστείτε ότι έχετε μια κλάση με όνομα Car στο έργο σας και θέλετε να την τοποθετήσετε σε ένα χώρο ονομάτων με όνομα Transport. Θα το κάνατε ως εξής:

namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

Αν θέλετε να χρησιμοποιήσετε την κλάση Car σε ένα άλλο αρχείο, πρέπει να καθορίσετε από ποιο χώρο ονομάτων προέρχεται η κλάση:

$car = new Transport\Car;

Για λόγους απλούστευσης, μπορείτε να καθορίσετε στην αρχή του αρχείου ποια κλάση από ένα συγκεκριμένο χώρο ονομάτων θέλετε να χρησιμοποιήσετε, επιτρέποντάς σας να δημιουργήσετε περιπτώσεις χωρίς να αναφέρετε την πλήρη διαδρομή:

use Transport\Car;

$car = new Car;

Κληρονομικότητα

Η κληρονομικότητα είναι ένα εργαλείο του αντικειμενοστρεφούς προγραμματισμού που επιτρέπει τη δημιουργία νέων κλάσεων με βάση τις υπάρχουσες, κληρονομώντας τις ιδιότητες και τις μεθόδους τους και επεκτείνοντας ή επαναπροσδιορίζοντας τες ανάλογα με τις ανάγκες. Η κληρονομικότητα εξασφαλίζει την επαναχρησιμοποίηση του κώδικα και την ιεραρχία των κλάσεων.

Με απλά λόγια, αν έχουμε μια κλάση και θέλουμε να δημιουργήσουμε μια άλλη που να προέρχεται από αυτήν αλλά με κάποιες τροποποιήσεις, μπορούμε να “κληρονομήσουμε” τη νέα κλάση από την αρχική.

Στην PHP, η κληρονομικότητα υλοποιείται με τη χρήση της λέξης-κλειδί extends.

Η κλάση μας Person αποθηκεύει πληροφορίες για την ηλικία. Μπορούμε να έχουμε μια άλλη κλάση, την Student, η οποία επεκτείνει την Person και προσθέτει πληροφορίες σχετικά με τον τομέα σπουδών.

Ας δούμε ένα παράδειγμα:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function printInformation()
	{
		echo "Age: {$this->age} years\n";
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		parent::printInformation();
		echo "Field of study: {$this->fieldOfStudy} \n";
	}
}

$student = new Student(20, 'Computer Science');
$student->printInformation();

Πώς λειτουργεί αυτός ο κώδικας;

  • Χρησιμοποιήσαμε τη λέξη-κλειδί extends για να επεκτείνουμε την κλάση Person, δηλαδή η κλάση Student κληρονομεί όλες τις μεθόδους και τις ιδιότητες από την Person.
  • Η λέξη-κλειδί parent:: μας επιτρέπει να καλούμε μεθόδους από τη γονική κλάση. Σε αυτή την περίπτωση, καλέσαμε τον κατασκευαστή από την κλάση Person πριν προσθέσουμε τη δική μας λειτουργικότητα στην κλάση Student. Και ομοίως, τη μέθοδο του προγόνου printInformation() πριν καταχωρίσουμε τις πληροφορίες του μαθητή.

Η κληρονομικότητα προορίζεται για καταστάσεις όπου υπάρχει μια σχέση “είναι ένα” μεταξύ κλάσεων. Για παράδειγμα, μια Student είναι μια Person. Μια γάτα είναι ένα ζώο. Μας επιτρέπει σε περιπτώσεις όπου περιμένουμε ένα αντικείμενο (π.χ. “Person”) στον κώδικα να χρησιμοποιήσουμε αντί αυτού ένα παράγωγο αντικείμενο (π.χ. “Student”).

Είναι σημαντικό να συνειδητοποιήσουμε ότι ο πρωταρχικός σκοπός της κληρονομικότητας δεν είναι να αποτρέψει την επανάληψη του κώδικα. Αντιθέτως, η κακή χρήση της κληρονομικότητας μπορεί να οδηγήσει σε πολύπλοκο και δύσκολα συντηρήσιμο κώδικα. Εάν δεν υπάρχει σχέση “είναι ένα” μεταξύ των κλάσεων, θα πρέπει να εξετάσουμε τη σύνθεση αντί της κληρονομικότητας.

Σημειώστε ότι οι μέθοδοι printInformation() στις κλάσεις Person και Student δίνουν ελαφρώς διαφορετικές πληροφορίες. Και μπορούμε να προσθέσουμε άλλες κλάσεις (όπως η Employee) που θα παρέχουν άλλες υλοποιήσεις αυτής της μεθόδου. Η ικανότητα των αντικειμένων διαφορετικών κλάσεων να ανταποκρίνονται στην ίδια μέθοδο με διαφορετικούς τρόπους ονομάζεται πολυμορφισμός:

$people = [
	new Person(30),
	new Student(20, 'Computer Science'),
	new Employee(45, 'Director'),
];

foreach ($people as $person) {
	$person->printInformation();
}

Σύνθεση

Η σύνθεση είναι μια τεχνική κατά την οποία, αντί να κληρονομούμε ιδιότητες και μεθόδους από μια άλλη κλάση, απλά χρησιμοποιούμε την εμφάνισή της στην κλάση μας. Αυτό μας επιτρέπει να συνδυάζουμε λειτουργίες και ιδιότητες πολλών κλάσεων χωρίς να δημιουργούμε πολύπλοκες δομές κληρονομικότητας.

Για παράδειγμα, έχουμε μια κλάση Engine και μια κλάση Car. Αντί να πούμε “Ένα αυτοκίνητο είναι ένας κινητήρας”, λέμε “Ένα αυτοκίνητο έχει έναν κινητήρα”, που είναι μια τυπική σχέση σύνθεσης.

class Engine
{
	function start()
	{
		echo 'Engine is running.';
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo 'The car is ready to drive!';
	}
}

$car = new Car;
$car->start();

Εδώ, η Car δεν έχει όλες τις ιδιότητες και τις μεθόδους της Engine, αλλά έχει πρόσβαση σε αυτές μέσω της ιδιότητας $engine.

Το πλεονέκτημα της σύνθεσης είναι η μεγαλύτερη ευελιξία σχεδιασμού και η καλύτερη προσαρμοστικότητα για μελλοντικές αλλαγές.

Ορατότητα

Στην PHP, μπορείτε να ορίσετε “ορατότητα” για ιδιότητες, μεθόδους και σταθερές κλάσεων. Η ορατότητα καθορίζει πού μπορείτε να έχετε πρόσβαση σε αυτά τα στοιχεία.

  1. Δημόσιο: Εάν ένα στοιχείο χαρακτηρίζεται ως public, σημαίνει ότι μπορείτε να έχετε πρόσβαση σε αυτό από οπουδήποτε, ακόμη και εκτός της κλάσης.
  2. Προστατευμένο: Ένα στοιχείο που επισημαίνεται ως protected είναι προσβάσιμο μόνο μέσα στην κλάση και σε όλους τους απογόνους της (κλάσεις που κληρονομούν από αυτήν).
  3. Ιδιωτικό: Εάν ένα στοιχείο είναι private, μπορείτε να έχετε πρόσβαση σε αυτό μόνο μέσα από την κλάση στην οποία ορίστηκε.

Αν δεν καθορίσετε ορατότητα, η PHP θα την ορίσει αυτόματα σε public.

Ας δούμε ένα παράδειγμα κώδικα:

class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		echo $this->privateProperty;    // Works
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Works
// echo $object->protectedProperty;   // Throws an error
// echo $object->privateProperty;     // Throws an error

Συνεχίζοντας με την κληρονομικότητα των κλάσεων:

class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}

Σε αυτή την περίπτωση, η μέθοδος printProperties() στην ChildClass μπορεί να έχει πρόσβαση στις δημόσιες και προστατευόμενες ιδιότητες, αλλά δεν μπορεί να έχει πρόσβαση στις ιδιωτικές ιδιότητες της γονικής κλάσης.

Τα δεδομένα και οι μέθοδοι θα πρέπει να είναι όσο το δυνατόν πιο κρυφά και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάζεται ο υπόλοιπος κώδικας.

Λέξη-κλειδί Final

Στην PHP, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί final αν θέλουμε να αποτρέψουμε μια κλάση, μέθοδο ή σταθερά από το να κληρονομηθεί ή να αντικατασταθεί. Όταν μια κλάση χαρακτηρίζεται ως final, δεν μπορεί να επεκταθεί. Όταν μια μέθοδος χαρακτηρίζεται ως final, δεν μπορεί να αντικατασταθεί σε μια υποκλάση.

Γνωρίζοντας ότι μια συγκεκριμένη κλάση ή μέθοδος δεν θα τροποποιείται πλέον, μπορούμε να κάνουμε αλλαγές πιο εύκολα χωρίς να ανησυχούμε για πιθανές συγκρούσεις. Για παράδειγμα, μπορούμε να προσθέσουμε μια νέα μέθοδο χωρίς να φοβόμαστε ότι ένας απόγονος μπορεί να έχει ήδη μια μέθοδο με το ίδιο όνομα, οδηγώντας σε σύγκρουση. Ή μπορούμε να αλλάξουμε τις παραμέτρους μιας μεθόδου, και πάλι χωρίς τον κίνδυνο να προκαλέσουμε ασυνέπεια με μια μέθοδο που έχει παρακαμφθεί σε έναν απόγονο.

final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}

Σε αυτό το παράδειγμα, η απόπειρα κληρονόμησης από την τελική κλάση FinalClass θα οδηγήσει σε σφάλμα.

Στατικές ιδιότητες και μέθοδοι

Όταν μιλάμε για “στατικά” στοιχεία μιας κλάσης στην PHP, εννοούμε μεθόδους και ιδιότητες που ανήκουν στην ίδια την κλάση και όχι σε μια συγκεκριμένη περίπτωση της κλάσης. Αυτό σημαίνει ότι δεν χρειάζεται να δημιουργήσετε μια παρουσία της κλάσης για να αποκτήσετε πρόσβαση σε αυτά. Αντ' αυτού, τις καλείτε ή τις προσπελαύνετε απευθείας μέσω του ονόματος της κλάσης.

Λάβετε υπόψη ότι, εφόσον τα στατικά στοιχεία ανήκουν στην κλάση και όχι στις περιπτώσεις της, δεν μπορείτε να χρησιμοποιήσετε την ψευδομεταβλητή $this μέσα σε στατικές μεθόδους.

Η χρήση στατικών ιδιοτήτων οδηγεί σε συγκεχυμένο κώδικα γεμάτο παγίδες, γι' αυτό δεν πρέπει ποτέ να τις χρησιμοποιείτε και δεν θα δείξουμε ένα παράδειγμα εδώ. Από την άλλη πλευρά, οι στατικές μέθοδοι είναι χρήσιμες. Ακολουθεί ένα παράδειγμα:

class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2

Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση Calculator με δύο στατικές μεθόδους. Μπορούμε να καλέσουμε αυτές τις μεθόδους απευθείας χωρίς να δημιουργήσουμε μια περίπτωση της κλάσης χρησιμοποιώντας τον τελεστή ::. Οι στατικές μέθοδοι είναι ιδιαίτερα χρήσιμες για λειτουργίες που δεν εξαρτώνται από την κατάσταση μιας συγκεκριμένης περίπτωσης της κλάσης.

Σταθερές κλάσης

Μέσα στις κλάσεις, έχουμε τη δυνατότητα να ορίσουμε σταθερές. Οι σταθερές είναι τιμές που δεν αλλάζουν ποτέ κατά τη διάρκεια της εκτέλεσης του προγράμματος. Σε αντίθεση με τις μεταβλητές, η τιμή μιας σταθεράς παραμένει η ίδια.

class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4

Σε αυτό το παράδειγμα, έχουμε μια κλάση Car με τη σταθερά NumberOfWheels. Όταν έχουμε πρόσβαση στη σταθερά μέσα στην κλάση, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί self αντί για το όνομα της κλάσης.

Διεπαφές αντικειμένων

Οι διεπαφές αντικειμένων λειτουργούν ως “συμβόλαια” για τις κλάσεις. Εάν μια κλάση πρόκειται να υλοποιήσει μια διεπαφή αντικειμένου, πρέπει να περιέχει όλες τις μεθόδους που ορίζει η διεπαφή. Είναι ένας πολύ καλός τρόπος για να διασφαλιστεί ότι ορισμένες κλάσεις τηρούν το ίδιο “συμβόλαιο” ή την ίδια δομή.

Στην PHP, οι διεπαφές ορίζονται χρησιμοποιώντας τη λέξη-κλειδί interface. Όλες οι μέθοδοι που ορίζονται σε μια διεπαφή είναι δημόσιες (public). Όταν μια κλάση υλοποιεί μια διεπαφή, χρησιμοποιεί τη λέξη-κλειδί implements.

interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();

Εάν μια κλάση υλοποιεί μια διασύνδεση, αλλά δεν έχουν οριστεί όλες οι αναμενόμενες μέθοδοι, η PHP θα εκπέμψει ένα σφάλμα.

Μια κλάση μπορεί να υλοποιήσει πολλές διεπαφές ταυτόχρονα, κάτι που διαφέρει από την κληρονομικότητα, όπου μια κλάση μπορεί να κληρονομήσει μόνο από μια κλάση:

interface Guardian
{
	function guardHouse();
}

class Dog implements Animal, Guardian
{
	public function makeSound()
	{
		echo 'Bark';
	}

	public function guardHouse()
	{
		echo 'Dog diligently guards the house';
	}
}

Αφηρημένες κλάσεις

Οι αφηρημένες κλάσεις χρησιμεύουν ως πρότυπα βάσης για άλλες κλάσεις, αλλά δεν μπορείτε να δημιουργήσετε άμεσα τις περιπτώσεις τους. Περιέχουν ένα μείγμα από πλήρεις μεθόδους και αφηρημένες μεθόδους που δεν έχουν καθορισμένο περιεχόμενο. Οι κλάσεις που κληρονομούν από αφηρημένες κλάσεις πρέπει να παρέχουν ορισμούς για όλες τις αφηρημένες μεθόδους του γονέα.

Χρησιμοποιούμε τη λέξη-κλειδί abstract για να ορίσουμε μια αφηρημένη κλάση.

abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'This is a regular method';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'This is the implementation of the abstract method';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();

Σε αυτό το παράδειγμα, έχουμε μια αφηρημένη κλάση με μια κανονική και μια αφηρημένη μέθοδο. Στη συνέχεια έχουμε μια κλάση Child που κληρονομεί από την AbstractClass και παρέχει μια υλοποίηση για την αφηρημένη μέθοδο.

Σε τι διαφέρουν οι διεπαφές και οι αφηρημένες κλάσεις; Οι αφηρημένες κλάσεις μπορούν να περιέχουν τόσο αφηρημένες όσο και συγκεκριμένες μεθόδους, ενώ οι διεπαφές ορίζουν μόνο ποιες μεθόδους πρέπει να υλοποιεί η κλάση, αλλά δεν παρέχουν καμία υλοποίηση. Μια κλάση μπορεί να κληρονομήσει από μία μόνο αφηρημένη κλάση, αλλά μπορεί να υλοποιήσει οποιονδήποτε αριθμό διεπαφών.

Έλεγχος τύπου

Στον προγραμματισμό, είναι ζωτικής σημασίας να διασφαλίζουμε ότι τα δεδομένα με τα οποία εργαζόμαστε έχουν τον σωστό τύπο. Στην PHP, διαθέτουμε εργαλεία που παρέχουν αυτή τη διασφάλιση. Η επαλήθευση ότι τα δεδομένα είναι σωστού τύπου ονομάζεται “έλεγχος τύπου”.

Τύποι που μπορεί να συναντήσουμε στην PHP:

  1. Βασικοί τύποι: Αυτοί περιλαμβάνουν int (ακέραιοι αριθμοί), float (αριθμοί κινητής υποδιαστολής), bool (τιμές boolean), string (συμβολοσειρές), array (πίνακες) και null.
  2. Κλάσεις: Όταν θέλουμε μια τιμή να είναι περίπτωση μιας συγκεκριμένης κλάσης.
  3. Διασυνδέσεις: Ορίζει ένα σύνολο μεθόδων που πρέπει να υλοποιεί μια κλάση. Μια τιμή που ανταποκρίνεται σε μια διεπαφή πρέπει να έχει αυτές τις μεθόδους.
  4. Μεικτοί τύποι: Μπορούμε να καθορίσουμε ότι μια μεταβλητή μπορεί να έχει πολλούς επιτρεπόμενους τύπους.
  5. Void: Αυτός ο ειδικός τύπος δηλώνει ότι μια συνάρτηση ή μέθοδος δεν επιστρέφει καμία τιμή.

Ας δούμε πώς μπορούμε να τροποποιήσουμε τον κώδικα για να συμπεριλάβουμε τύπους:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "This person is {$this->age} years old.";
	}
}

/**
 * A function that accepts a Person object and prints the person's age.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}

Με αυτόν τον τρόπο εξασφαλίζουμε ότι ο κώδικάς μας αναμένει και λειτουργεί με δεδομένα του σωστού τύπου, βοηθώντας μας να αποφύγουμε πιθανά σφάλματα.

Ορισμένοι τύποι δεν μπορούν να γραφτούν απευθείας σε PHP. Σε αυτή την περίπτωση, αναφέρονται στο σχόλιο phpDoc, το οποίο είναι η τυπική μορφή για την τεκμηρίωση κώδικα PHP, ξεκινώντας με /** και τελειώνοντας με */. Σας επιτρέπει να προσθέσετε περιγραφές κλάσεων, μεθόδων κ.λπ. Και επίσης να απαριθμήσετε σύνθετους τύπους χρησιμοποιώντας τις λεγόμενες επισημειώσεις @var, @param και @return. Αυτοί οι τύποι χρησιμοποιούνται στη συνέχεια από εργαλεία στατικής ανάλυσης κώδικα, αλλά δεν ελέγχονται από την ίδια την PHP.

class Registry
{
	/** @var array<Person>  indicates that it's an array of Person objects */
	private array $persons = [];

	public function addPerson(Person $person): void
	{
		$this->persons[] = $person;
	}
}

Σύγκριση και ταυτότητα

Στην PHP, μπορείτε να συγκρίνετε αντικείμενα με δύο τρόπους:

  1. Σύγκριση τιμών ==: Ελέγχει αν τα αντικείμενα ανήκουν στην ίδια κλάση και έχουν τις ίδιες τιμές στις ιδιότητές τους.
  2. Σύγκριση ταυτότητας ===: Ελέγχει αν πρόκειται για την ίδια περίπτωση του αντικειμένου.
class Car
{
	public string $brand;

	public function __construct(string $brand)
	{
		$this->brand = $brand;
	}
}

$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;

var_dump($car1 == $car2);   // true, because they have the same value
var_dump($car1 === $car2);  // false, because they are not the same instance
var_dump($car1 === $car3);  // true, because $car3 is the same instance as $car1

Ο χειριστής instanceof

Ο τελεστής instanceof σας επιτρέπει να προσδιορίσετε αν ένα δεδομένο αντικείμενο είναι παράδειγμα μιας συγκεκριμένης κλάσης, απόγονος αυτής της κλάσης ή αν υλοποιεί μια συγκεκριμένη διασύνδεση.

Φανταστείτε ότι έχουμε μια κλάση Person και μια άλλη κλάση Student, η οποία είναι απόγονος της Person:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}
}

class Student extends Person
{
	private string $major;

	public function __construct(int $age, string $major)
	{
		parent::__construct($age);
		$this->major = $major;
	}
}

$student = new Student(20, 'Computer Science');

// Check if $student is an instance of the Student class
var_dump($student instanceof Student);  // Output: bool(true)

// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person);   // Output: bool(true)

Από τις εξόδους, είναι προφανές ότι το αντικείμενο $student θεωρείται παράδειγμα και των δύο κλάσεων Student και Person.

Ρευστές διεπαφές

Μια “ρευστή διεπαφή” είναι μια τεχνική στην OOP που επιτρέπει την αλυσιδωτή σύνδεση μεθόδων με μία μόνο κλήση. Αυτό συχνά απλοποιεί και αποσαφηνίζει τον κώδικα.

Το βασικό στοιχείο μιας ρευστής διεπαφής είναι ότι κάθε μέθοδος στην αλυσίδα επιστρέφει μια αναφορά στο τρέχον αντικείμενο. Αυτό επιτυγχάνεται με τη χρήση του return $this; στο τέλος της μεθόδου. Αυτό το στυλ προγραμματισμού συνδέεται συχνά με μεθόδους που ονομάζονται “setters”, οι οποίες ορίζουν τις τιμές των ιδιοτήτων ενός αντικειμένου.

Ας δούμε πώς θα μπορούσε να μοιάζει μια ρευστή διεπαφή για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

Σε αυτό το παράδειγμα, οι μέθοδοι setFrom(), setRecipient() και setMessage() χρησιμοποιούνται για να ορίσουν τις αντίστοιχες τιμές (αποστολέας, παραλήπτης, περιεχόμενο μηνύματος). Μετά τον ορισμό καθεμιάς από αυτές τις τιμές, οι μέθοδοι επιστρέφουν το τρέχον αντικείμενο ($email), επιτρέποντάς μας να ακολουθήσουμε αλυσιδωτά μια άλλη μέθοδο. Τέλος, καλούμε τη μέθοδο send(), η οποία στην πραγματικότητα στέλνει το μήνυμα ηλεκτρονικού ταχυδρομείου.

Χάρη στις ρευστές διεπαφές, μπορούμε να γράψουμε κώδικα που είναι διαισθητικός και εύκολα αναγνώσιμος.

Αντιγραφή με clone

Στην PHP, μπορούμε να δημιουργήσουμε ένα αντίγραφο ενός αντικειμένου χρησιμοποιώντας τον τελεστή clone. Με αυτόν τον τρόπο, λαμβάνουμε μια νέα περίπτωση με πανομοιότυπο περιεχόμενο.

Αν χρειάζεται να τροποποιήσουμε κάποιες από τις ιδιότητές του κατά την αντιγραφή ενός αντικειμένου, μπορούμε να ορίσουμε μια ειδική μέθοδο __clone() στην κλάση. Αυτή η μέθοδος καλείται αυτόματα όταν το αντικείμενο κλωνοποιείται.

class Sheep
{
	public string $name;

	public function __construct(string $name)
	{
		$this->name = $name;
	}

	public function __clone()
	{
		$this->name = 'Clone of ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Outputs: Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Outputs: Clone of Dolly

Σε αυτό το παράδειγμα, έχουμε μια κλάση Sheep με μια ιδιότητα $name. Όταν κλωνοποιούμε μια περίπτωση αυτής της κλάσης, η μέθοδος __clone() εξασφαλίζει ότι το όνομα του κλωνοποιημένου προβάτου παίρνει το πρόθεμα “Clone of”.

Χαρακτηριστικά

Τα γνωρίσματα στην PHP είναι ένα εργαλείο που επιτρέπει την κοινή χρήση μεθόδων, ιδιοτήτων και σταθερών μεταξύ κλάσεων και αποτρέπει την επανάληψη κώδικα. Μπορείτε να τα φανταστείτε ως ένα μηχανισμό “αντιγραφής και επικόλλησης” (Ctrl-C και Ctrl-V), όπου το περιεχόμενο ενός γνωρίσματος “επικολλάται” σε κλάσεις. Αυτό σας επιτρέπει να επαναχρησιμοποιείτε κώδικα χωρίς να χρειάζεται να δημιουργείτε περίπλοκες ιεραρχίες κλάσεων.

Ας ρίξουμε μια ματιά σε ένα απλό παράδειγμα χρήσης των traits στην PHP:

trait Honking
{
	public function honk()
	{
		echo 'Beep beep!';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Outputs 'Beep beep!'

$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'

Σε αυτό το παράδειγμα, έχουμε ένα trait με όνομα Honking που περιέχει μία μέθοδο honk(). Στη συνέχεια, έχουμε δύο κλάσεις: Car και Truck, οι οποίες χρησιμοποιούν το χαρακτηριστικό Honking. Ως αποτέλεσμα, και οι δύο κλάσεις “έχουν” τη μέθοδο honk() και μπορούμε να την καλέσουμε σε αντικείμενα και των δύο κλάσεων.

Τα γνωρίσματα σας επιτρέπουν να μοιράζεστε εύκολα και αποτελεσματικά κώδικα μεταξύ κλάσεων. Δεν εισέρχονται στην ιεραρχία κληρονομικότητας, δηλαδή το $car instanceof Honking θα επιστρέψει το false.

Εξαιρέσεις

Οι εξαιρέσεις στην OOP μας επιτρέπουν να χειριζόμαστε με αξιοπρέπεια σφάλματα και απροσδόκητες καταστάσεις στον κώδικά μας. Πρόκειται για αντικείμενα που μεταφέρουν πληροφορίες σχετικά με ένα σφάλμα ή μια ασυνήθιστη κατάσταση.

Στην PHP, έχουμε μια ενσωματωμένη κλάση Exception, η οποία χρησιμεύει ως βάση για όλες τις εξαιρέσεις. Αυτή διαθέτει διάφορες μεθόδους που μας επιτρέπουν να λάβουμε περισσότερες πληροφορίες σχετικά με την εξαίρεση, όπως το μήνυμα σφάλματος, το αρχείο και τη γραμμή όπου συνέβη το σφάλμα κ.λπ.

Όταν εμφανίζεται ένα σφάλμα στον κώδικα, μπορούμε να “πετάξουμε” την εξαίρεση χρησιμοποιώντας τη λέξη-κλειδί throw.

function division(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Division by zero!');
	}
	return $a / $b;
}

Όταν η συνάρτηση division() λαμβάνει null ως δεύτερο όρισμά της, εκπέμπει μια εξαίρεση με το μήνυμα σφάλματος 'Division by zero!'. Για να αποτρέψουμε τη συντριβή του προγράμματος όταν εκπέμπεται η εξαίρεση, την παγιδεύουμε στο μπλοκ try/catch:

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}

Ο κώδικας που μπορεί να πετάξει μια εξαίρεση τυλίγεται σε ένα μπλοκ try. Αν η εξαίρεση εκτοξευτεί, η εκτέλεση του κώδικα μεταφέρεται σε ένα μπλοκ catch, όπου μπορούμε να χειριστούμε την εξαίρεση (π.χ. να γράψουμε ένα μήνυμα σφάλματος).

Μετά τα μπλοκ try και catch, μπορούμε να προσθέσουμε ένα προαιρετικό μπλοκ finally, το οποίο εκτελείται πάντα, ανεξάρτητα από το αν η εξαίρεση προκλήθηκε ή όχι (ακόμη και αν χρησιμοποιήσουμε τα μπλοκ return, break ή continue στο μπλοκ try ή catch ):

try {
	echo division(10, 0);
} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
} finally {
	// Code that is always executed whether the exception has been thrown or not
}

Μπορούμε επίσης να δημιουργήσουμε τις δικές μας κλάσεις εξαιρέσεων (ιεραρχία) που κληρονομούν από την κλάση Exception. Ως παράδειγμα, θεωρήστε μια απλή τραπεζική εφαρμογή που επιτρέπει καταθέσεις και αναλήψεις:

class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}

class BankAccount
{
	private int $balance = 0;
	private int $dailyLimit = 1000;

	public function deposit(int $amount): int
	{
		$this->balance += $amount;
		return $this->balance;
	}

	public function withdraw(int $amount): int
	{
		if ($amount > $this->balance) {
			throw new InsufficientFundsException('Not enough funds in the account.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Daily withdrawal limit exceeded.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}

Μπορούν να καθοριστούν πολλαπλά μπλοκ catch για ένα μόνο μπλοκ try, εάν περιμένετε διαφορετικούς τύπους εξαιρέσεων.

$account = new BankAccount;
$account->deposit(500);

try {
	$account->withdraw(1500);
} catch (ExceededLimitException $e) {
	echo $e->getMessage();
} catch (InsufficientFundsException $e) {
	echo $e->getMessage();
} catch (BankingException $e) {
	echo 'An error occurred during the operation.';
}

Σε αυτό το παράδειγμα, είναι σημαντικό να σημειωθεί η σειρά των μπλοκ catch. Δεδομένου ότι όλες οι εξαιρέσεις κληρονομούν από το BankingException, αν είχαμε αυτό το μπλοκ πρώτο, όλες οι εξαιρέσεις θα συλλαμβάνονταν σε αυτό χωρίς ο κώδικας να φτάσει στα επόμενα μπλοκ catch. Επομένως, είναι σημαντικό να έχουμε πιο συγκεκριμένες εξαιρέσεις (δηλαδή αυτές που κληρονομούν από άλλες) πιο ψηλά στη σειρά των μπλοκ catch από τις εξαιρέσεις των γονέων τους.

Επαναλήψεις

Στην PHP, μπορείτε να διατρέχετε αντικείμενα με τη χρήση του βρόχου foreach, όπως ακριβώς κάνετε βρόχο σε έναν πίνακα. Για να λειτουργήσει αυτό, το αντικείμενο πρέπει να υλοποιεί μια ειδική διεπαφή.

Η πρώτη επιλογή είναι να υλοποιήσετε τη διεπαφή Iterator, η οποία έχει τις μεθόδους current() που επιστρέφουν την τρέχουσα τιμή, key() που επιστρέφουν το κλειδί, next() που μεταβαίνουν στην επόμενη τιμή, rewind() που μεταβαίνουν στην αρχή και valid() που ελέγχουν αν έχουμε φτάσει στο τέλος.

Η άλλη επιλογή είναι να υλοποιήσουμε μια διεπαφή IteratorAggregate, η οποία έχει μόνο μια μέθοδο getIterator(). Αυτή είτε επιστρέφει ένα αντικείμενο τοποθέτησης που θα παρέχει τη διάσχιση, είτε μπορεί να είναι μια γεννήτρια, η οποία είναι μια ειδική συνάρτηση που χρησιμοποιεί το yield για να επιστρέφει διαδοχικά τα κλειδιά και τις τιμές:

class Person
{
	public function __construct(
		public int $age,
	) {
	}
}

class Registry implements IteratorAggregate
{
	private array $people = [];

	public function addPerson(Person $person): void
	{
		$this->people[] = $person;
	}

	public function getIterator(): Generator
	{
		foreach ($this->people as $person) {
			yield $person;
		}
	}
}

$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));

foreach ($list as $person) {
	echo "Age: {$person->age} years\n";
}

Βέλτιστες πρακτικές

Μόλις μάθετε τις βασικές αρχές του αντικειμενοστραφούς προγραμματισμού, είναι ζωτικής σημασίας να εστιάσετε στις βέλτιστες πρακτικές του OOP. Αυτές θα σας βοηθήσουν να γράψετε κώδικα που δεν είναι μόνο λειτουργικός αλλά και ευανάγνωστος, κατανοητός και εύκολα συντηρήσιμος.

  1. Διαχωρισμός των ανησυχιών: Κάθε κλάση θα πρέπει να έχει σαφώς καθορισμένη ευθύνη και να ασχολείται μόνο με μία πρωταρχική εργασία. Αν μια κλάση κάνει πάρα πολλά πράγματα, ίσως είναι σκόπιμο να τη χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις.
  2. Ενθυλάκωση: Τα δεδομένα και οι μέθοδοι θα πρέπει να είναι όσο το δυνατόν πιο κρυφά και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση μιας κλάσης χωρίς να επηρεάζεται ο υπόλοιπος κώδικας.
  3. Έγχυση εξάρτησης: Αντί να δημιουργείτε εξαρτήσεις απευθείας μέσα σε μια κλάση, θα πρέπει να τις “εγχέετε” από έξω. Για μια βαθύτερη κατανόηση αυτής της αρχής, σας συνιστούμε τα κεφάλαια για το Dependency Injection.
έκδοση: 4.0