Suche…


Einführung

Klassen und Objekte werden verwendet, um den Code effizienter und weniger wiederholend zu gestalten, indem ähnliche Aufgaben gruppiert werden.

Mit einer Klasse werden die Aktionen und Datenstrukturen definiert, die zum Erstellen von Objekten verwendet werden. Die Objekte werden dann anhand dieser vordefinierten Struktur erstellt.

Syntax

  • class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { } // Klassendeklaration
  • interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { } // Schnittstellendeklaration
  • use <Trait1> [, <Trait2>, ...] ; // Verwenden Sie Merkmale
  • [ public | protected | private ] [ static ] $<varName>; // Attributdeklaration
  • const <CONST_NAME>; // Konstante Deklaration
  • [ public | protected | private ] [ static ] function <methodName>([args...]) { } // Methodendeklaration

Bemerkungen

Klassen und Schnittstellenkomponenten

Klassen können Eigenschaften, Konstanten und Methoden haben.

  • Eigenschaften enthalten Variablen im Bereich des Objekts. Sie können bei der Deklaration initialisiert werden, jedoch nur, wenn sie einen primitiven Wert enthalten.
  • Konstanten müssen bei der Deklaration initialisiert werden und dürfen nur einen primitiven Wert enthalten. Konstantenwerte werden zur Kompilierzeit festgelegt und können zur Laufzeit nicht zugewiesen werden.
  • Methoden müssen einen Körper haben, auch einen leeren, wenn die Methode nicht als abstrakt deklariert ist.
class Foo {
    private $foo = 'foo'; // OK
    private $baz = array(); // OK
    private $bar = new Bar(); // Error!
}

Schnittstellen können keine Eigenschaften haben, können jedoch Konstanten und Methoden haben.

  • Interface Konstanten müssen nach der Deklaration initialisiert werden und kann nur einen primitiven Wert enthalten. Konstantenwerte werden zur Kompilierzeit festgelegt und können zur Laufzeit nicht zugewiesen werden.
  • Interface - Methoden haben keinen Körper.
interface FooBar {
    const FOO_VALUE = 'bla';
    public function doAnything();
}

Schnittstellen

Einführung

Schnittstellen sind Definitionen der öffentlichen APIs, die Klassen implementieren müssen, um die Schnittstelle zu erfüllen. Sie arbeiten als "Verträge" und geben an, was eine Reihe von Unterklassen bewirkt, aber nicht, wie sie es tun.

Interface - Definition ist sehr ähnlich Klassendefinition, die Änderung der Keyword - class interface :

interface Foo {

}

Schnittstellen können Methoden und / oder Konstanten enthalten, jedoch keine Attribute. Schnittstellenkonstanten haben die gleichen Einschränkungen wie Klassenkonstanten. Schnittstellenmethoden sind implizit abstrakt:

interface Foo {
    const BAR = 'BAR';

    public function doSomething($param1, $param2);
}

Anmerkung: Schnittstellen dürfen keine Konstruktoren oder Destruktoren deklarieren, da dies Implementierungsdetails auf Klassenebene sind.

Realisierung

Jede Klasse, die eine Schnittstelle implementieren muss, muss das Schlüsselwort implements . Dazu muss die Klasse für jede in der Schnittstelle deklarierte Methode eine Implementierung bereitstellen, die dieselbe Signatur beachtet.

Eine einzelne Klasse kann mehrere Schnittstellen gleichzeitig implementieren.

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) {
        // ...
    }
}

Wenn abstrakte Klassen Schnittstellen implementieren, müssen sie nicht alle Methoden implementieren. Jede Methode, die nicht in der Basisklasse implementiert ist, muss dann von der konkreten Klasse implementiert werden, die sie erweitert:

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) {
        // ...
    }
}

Beachten Sie, dass die Schnittstellenrealisierung ein vererbtes Merkmal ist. Wenn Sie eine Klasse erweitern, die eine Schnittstelle implementiert, müssen Sie sie nicht erneut in der konkreten Klasse deklarieren, da sie implizit ist.

Hinweis: Vor PHP 5.3.9 konnte eine Klasse nicht zwei Schnittstellen implementieren, die eine Methode mit demselben Namen angegeben haben, da dies zu Mehrdeutigkeiten führen würde. Neuere Versionen von PHP erlauben dies, solange die doppelten Methoden die gleiche Signatur haben [1] .

Erbe

Wie Klassen ist es möglich, eine Vererbungsbeziehung zwischen Schnittstellen herzustellen, wobei das gleiche Schlüsselwort extends . Der Hauptunterschied besteht darin, dass Mehrfachvererbung für Schnittstellen zulässig ist:

interface Foo {

}

interface Bar {

}

interface Baz extends Foo, Bar {

}

Beispiele

Im folgenden Beispiel haben wir eine einfache Beispielschnittstelle für ein Fahrzeug. Fahrzeuge können vorwärts und rückwärts fahren.

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() {
        ...
    }

    ...
}

Dann erstellen wir zwei Klassen, die die Schnittstelle implementieren: Bike und Car. Fahrrad und Auto sind intern sehr unterschiedlich, aber beide sind Fahrzeuge und müssen die gleichen öffentlichen Methoden implementieren, die VehicleInterface bietet.

Bei der Typisierung können Methoden und Funktionen Schnittstellen anfordern. Nehmen wir an, wir haben eine Parkhausklasse, die Fahrzeuge aller Art enthält.

class ParkingGarage {
    protected $vehicles = [];

    public function addVehicle(VehicleInterface $vehicle) {
        $this->vehicles[] = $vehicle;
    }
}

Da addVehicle erfordert ein $vehicle vom Typ VehicleInterface -nicht eine konkrete Implementierung können wir Eingang sowohl Fahrräder und Autos, die die Parkgarage manipulieren und verwenden.

Klassenkonstanten

Klassenkonstanten bieten einen Mechanismus zum Halten fester Werte in einem Programm. Das heißt, sie bieten eine Möglichkeit, einem Wert wie 3.14 oder "Apple" einen Namen (und damit verbundene Überprüfungen der Kompilierzeit) zu geben. Klassenkonstanten können nur mit dem Schlüsselwort const definiert werden - die Funktion define kann in diesem Kontext nicht verwendet werden.

Zum Beispiel kann es zweckmäßig sein, eine Kurzdarstellung für den Wert von π in einem Programm zu haben. Eine Klasse mit const Werten bietet eine einfache Möglichkeit zum Speichern solcher Werte.

class MathValues {
    const PI = M_PI;
    const PHI = 1.61803;
}

$area = MathValues::PI * $radius * $radius;

Auf Klassenkonstanten kann zugegriffen werden, indem der Doppelpunktoperator (der sogenannte Bereichsauflösungsoperator) für eine Klasse verwendet wird, ähnlich wie bei statischen Variablen. Im Gegensatz zu statischen Variablen werden die Werte der Klassenkonstanten jedoch zur Kompilierzeit festgelegt und können nicht erneut zugewiesen werden (z. B. MathValues::PI = 7 würde einen schwerwiegenden Fehler verursachen).

Klassenkonstanten sind auch nützlich, um interne Elemente einer Klasse zu definieren, die möglicherweise später geändert werden müssen (aber nicht häufig genug geändert werden, um das Speichern in einer Datenbank zu rechtfertigen). Wir können dies intern mit dem self Scope-Resolutor referenzieren (der sowohl in instanziierten als auch in statischen Implementierungen funktioniert).

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;
    }
}

Klassenkonstanten können nur in Versionen <5.6 skalare Werte enthalten

Ab PHP 5.6 können Ausdrücke mit Konstanten verwendet werden. Dies bedeutet, dass mathematische Anweisungen und Zeichenfolgen mit Verkettung akzeptable Konstanten sind

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;
    }
}

Ab PHP 7.0 mit deklarierten Konstanten define nun Arrays enthalten.

define("BAZ", array('baz'));

Klassenkonstanten eignen sich nicht nur zum Speichern mathematischer Konzepte. Wenn Sie zum Beispiel eine Torte zubereiten, kann es zweckmäßig sein, eine einzige Pie zu haben, die verschiedene Obstsorten aufnehmen kann.

class Pie {
    protected $fruit;

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

Wir können die Pie Klasse dann so verwenden

$pie = new Pie("strawberry");

Das Problem, das sich hier ergibt, ist, dass beim Instanziieren der Pie Klasse keine Anleitung bezüglich der akzeptablen Werte bereitgestellt wird. Wenn Sie zum Beispiel eine "Boysenberry" -Pastete herstellen, kann es sein, dass "Boisenberry" falsch geschrieben ist. Außerdem unterstützen wir möglicherweise keinen Pflaumenkuchen. Stattdessen wäre es sinnvoll, bereits eine Liste akzeptabler Fruchtsorten definiert zu haben, an denen es sinnvoll wäre, nach ihnen zu suchen. Sagen Sie eine Klasse namens Fruit :

class Fruit {
    const APPLE = "apple";
    const STRAWBERRY = "strawberry";
    const BOYSENBERRY = "boysenberry";
}

$pie = new Pie(Fruit::STRAWBERRY);

Das Auflisten der akzeptablen Werte als Klassenkonstanten liefert einen wertvollen Hinweis auf die akzeptablen Werte, die eine Methode akzeptiert. Es stellt auch sicher, dass Rechtschreibfehler den Compiler nicht passieren können. Während new Pie('aple') und new Pie('apple') beide für den Compiler akzeptabel sind, führt die new Pie(Fruit::APLE) einem Compiler-Fehler.

Die Verwendung von Klassenkonstanten bedeutet schließlich, dass der tatsächliche Wert der Konstante an einer einzigen Stelle geändert werden kann, und jeder Code, der die Konstante verwendet, hat automatisch die Auswirkungen der Änderung.

Während MyClass::CONSTANT_NAME die häufigste Methode für den Zugriff auf eine Klassenkonstante ist, kann auf sie auch folgendermaßen zugegriffen werden:

echo MyClass::CONSTANT;

$classname = "MyClass";
echo $classname::CONSTANT; // As of PHP 5.3.0

Klassenkonstanten in PHP werden üblicherweise in Großbuchstaben mit Unterstrichen als Worttrennzeichen benannt, obwohl jeder gültige Markenname als Klassenname verwendet werden kann.

Seit PHP 7.1 können Klassenkonstanten jetzt mit unterschiedlichen Sichtbarkeiten vom öffentlichen Standardbereich definiert werden. Dies bedeutet, dass jetzt sowohl geschützte als auch private Konstanten definiert werden können, um zu verhindern, dass Klassenkonstanten unnötig in den öffentlichen Bereich gelangen (siehe Methoden- und Eigenschaftensichtbarkeit ). Zum Beispiel:

class Something {
    const PUBLIC_CONST_A = 1;
    public const PUBLIC_CONST_B = 2;
    protected const PROTECTED_CONST = 3;
    private const PRIVATE_CONST = 4;
}

Definiere vs Klassenkonstanten

Obwohl dies eine gültige Konstruktion ist:

function bar() { return 2; };

define('BAR', bar());

Wenn Sie versuchen, dasselbe mit Klassenkonstanten zu tun, erhalten Sie eine Fehlermeldung:

function bar() { return 2; };

class Foo {
    const BAR = bar(); // Error: Constant expression contains invalid operations
}

Aber du kannst tun:

function bar() { return 2; };

define('BAR', bar());

class Foo {
    const BAR = BAR; // OK
}

Weitere Informationen finden Sie unter Konstanten im Handbuch .

Verwenden von :: class zum Abrufen des Klassennamens

PHP 5.5 führte die ::class Syntax ein, um den vollständigen Klassennamen abzurufen, unter Berücksichtigung des Namespace-Bereichs und der 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"

Das Obige funktioniert auch, wenn die Klassen nicht einmal definiert sind (dh dieser Code-Snippet funktioniert alleine).

Diese Syntax ist nützlich für Funktionen, die einen Klassennamen erfordern. Beispielsweise kann es mit class_exists , um zu überprüfen, ob eine Klasse vorhanden ist. In diesem Snippet werden unabhängig vom Rückgabewert keine Fehler generiert:

class_exists(ThisClass\Will\NeverBe\Loaded::class, false);

Spätes statisches Binden

In PHP 5.3 und höher können Sie die spätere statische Bindung verwenden, um zu steuern, von welcher Klasse eine statische Eigenschaft oder Methode aufgerufen wird. Es wurde hinzugefügt, um das mit dem self:: Scope Resolutor inhärente Problem zu überwinden. Nimm den folgenden Code

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!';
    }
}

Sie würden erwarten, dass die MrEd Klasse die übergeordnete whatToSay() Funktion überschreibt. Aber wenn wir das ausführen, bekommen wir etwas Unerwartetes

Horse::speak(); // Neigh!
MrEd::speak(); // Neigh!

Das Problem ist, dass self::whatToSay(); kann sich nur auf die Horse Klasse beziehen, was bedeutet, dass sie MrEd nicht gehorcht. Wenn wir auf den static:: scope resolutor wechseln, haben wir dieses Problem nicht. Diese neuere Methode weist die Klasse an, der Instanz zu folgen, die sie aufruft. So bekommen wir die Erbschaft, die wir erwarten

class Horse {
    public static function whatToSay() {
         echo 'Neigh!';
    }

    public static function speak() {
         static::whatToSay(); // Late Static Binding
    }
}

Horse::speak(); // Neigh!
MrEd::speak(); // Hello Wilbur!

Abstrakte Klassen

Eine abstrakte Klasse ist eine Klasse, die nicht instanziiert werden kann. Abstrakte Klassen können abstrakte Methoden definieren, dh Methoden ohne Körper, nur eine Definition:

abstract class MyAbstractClass {
    abstract public function doSomething($a, $b);
}

Abstrakte Klassen sollten um eine untergeordnete Klasse erweitert werden, die dann die Implementierung dieser abstrakten Methoden bereitstellen kann.

Der Hauptzweck einer Klasse wie dieser besteht darin, eine Art Vorlage bereitzustellen, mit der untergeordnete Klassen erben können, wodurch eine Struktur "erzwungen" wird. Lassen Sie uns dies an einem Beispiel erläutern:

In diesem Beispiel implementieren wir eine Worker Schnittstelle. Zuerst definieren wir die Schnittstelle:

interface Worker {
    public function run();
}

Um die Entwicklung weiterer Worker-Implementierungen zu erleichtern, erstellen wir eine abstrakte Workerklasse, die bereits die run() -Methode über die Schnittstelle bereitstellt, jedoch einige abstrakte Methoden angibt, die von einer untergeordneten Klasse ausgefüllt werden müssen:

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();
}

Zunächst haben wir eine abstrakte Methode getMemoryLimit() bereitgestellt. Jede Klasse, die sich von AbstractWorker muss diese Methode bereitstellen und das Speicherlimit zurückgeben. Der AbstractWorker legt dann das Speicherlimit fest und protokolliert es.

Zweitens ruft der AbstractWorker nach der Protokollierung, dass sie aufgerufen wurden, die prepareMain() und main() .

Schließlich wurden alle diese Methodenaufrufe in einem try catch Block zusammengefasst. Wenn also eine der abstrakten Methoden, die von der untergeordneten Klasse definiert werden, eine Ausnahme auslöst, werden wir diese Ausnahme abfangen, protokollieren und erneut auslösen. Dies verhindert, dass alle untergeordneten Klassen dies selbst implementieren müssen.

Nun definieren wir eine Kindklasse, die vom 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();
        }
    }
}

Wie Sie sehen, war der TransactionProcessorWorker relativ einfach zu implementieren, da wir nur die Speicherbegrenzung angeben mussten und uns um die tatsächlichen Aktionen kümmern mussten, die er ausführen musste. Im TransactionProcessorWorker ist keine Fehlerbehandlung erforderlich, da dies im AbsractWorker .

Wichtige Notiz

Bei der Vererbung von einer abstrakten Klasse müssen alle Methoden, die in der Klassendeklaration des übergeordneten Elements als abstrakt markiert sind, vom Kind definiert werden (oder das Kind selbst muss auch als abstrakt gekennzeichnet sein). Darüber hinaus müssen diese Methoden mit der gleichen (oder weniger eingeschränkten) Sichtbarkeit definiert werden. Wenn die abstrakte Methode beispielsweise als geschützt definiert ist, muss die Funktionsimplementierung entweder als geschützt oder als öffentlich, jedoch nicht als privat definiert werden.

Aus der PHP-Dokumentation zur Klassenabstraktion entnommen.

Wenn Sie die übergeordneten abstrakten Klassenmethoden nicht innerhalb der untergeordneten Klasse definieren, wird ein schwerwiegender PHP-Fehler wie der folgende ausgegeben .

Schwerwiegender Fehler: Klasse X enthält 1 abstrakte Methode und muss daher als abstrakt deklariert werden oder die restlichen Methoden (X :: x) in implementieren

Namensraum und Autoloading

Technisch funktioniert das automatische Laden durch Ausführen eines Rückrufs, wenn eine PHP-Klasse erforderlich ist, aber nicht gefunden wird. Solche Rückrufe versuchen normalerweise, diese Klassen zu laden.

Unter Autoloading kann im Allgemeinen der Versuch verstanden werden, PHP-Dateien (insbesondere PHP-Klassendateien, in denen eine PHP-Quelldatei für eine bestimmte Klasse vorgesehen ist) aus geeigneten Pfaden gemäß dem vollständig qualifizierten Namen der Klasse (FQN) zu laden, wenn eine Klasse benötigt wird .

Angenommen, wir haben diese Klassen:

Klassendatei für application\controllers\Base :

<?php
namespace application\controllers { class Base {...} }

Klassendatei für application\controllers\Control :

<?php
namespace application\controllers { class Control {...} }

Klassendatei für application\models\Page :

<?php
namespace application\models { class Page {...} }

Unter dem Quellordner sollten diese Klassen als FQNs an den Pfaden platziert werden:

  • Quellverzeichnis
    • applications
      • controllers
        • Base.php
        • Control.php
      • models
        • Page.php

Dieser Ansatz ermöglicht es, den Klassendateipfad mithilfe der Funktion programmgesteuert gemäß dem FQN aufzulösen:

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
}

Mit der Funktion spl_autoload_register können wir bei Bedarf eine Klasse mit einer benutzerdefinierten Funktion laden:

const SOURCE_FOLDER = __DIR__ . "/src";
spl_autoload_register(function (string $className) {
    $file = getClassPath(SOURCE_FOLDER, $className);
    if (is_readable($file)) require_once $file;
});

Diese Funktion kann weiter erweitert werden, um Fallback-Lademethoden zu verwenden:

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;
        }
    }
});

Beachten Sie, dass PHP nicht versucht, die Klassen zu laden, wenn eine Datei geladen wird, die diese Klasse verwendet. Es kann in der Mitte eines Skripts geladen werden oder sogar in Shutdown-Funktionen. Dies ist einer der Gründe, warum Entwickler, insbesondere diejenigen, die das automatische Laden verwenden, es vermeiden sollten, die Ausführung von Quelldateien zur Laufzeit zu ersetzen, insbesondere in Phar-Dateien.

Dynamische Bindung

Dynamische Bindung, auch bezeichnet als Verfahren überwiegend ein Beispiel für Laufzeit - Polymorphismus, der auftritt , wenn mehrere Klassen verschiedene Implementierungen des gleichen Verfahrens enthalten, aber das Objekt , dass das Verfahren auf ist bis zur Laufzeit unbekannt genannt wird.

Dies ist nützlich, wenn eine bestimmte Bedingung vorschreibt, welche Klasse zum Ausführen einer Aktion verwendet wird, wobei die Aktion in beiden Klassen gleich benannt wird.

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();

Im obigen Beispiel ist die Animal Klasse ( Dog|Cat ), die makeNoise wird, bis zur Laufzeit unbekannt. makeNoise hängt von der Eigenschaft in der User Klasse ab.

Sichtbarkeit von Methoden und Eigenschaften

Es gibt drei Sichtbarkeitstypen, die Sie auf Methoden ( Klassen- / Objektfunktionen ) und Eigenschaften ( Klassen- / Objektvariablen ) innerhalb einer Klasse anwenden können, die eine Zugriffssteuerung für die Methode oder Eigenschaft bieten, auf die sie angewendet werden.

Sie können dies ausführlich in der PHP-Dokumentation für OOP Visibility nachlesen.

Öffentlichkeit

Durch das Deklarieren einer Methode oder einer Eigenschaft als public kann auf die Methode oder Eigenschaft auf folgende Weise zugegriffen werden:

  • Die Klasse, die es deklariert hat.
  • Die Klassen, die die deklarierte Klasse erweitern.
  • Alle externen Objekte, Klassen oder Code außerhalb der Klassenhierarchie.

Ein Beispiel für diesen public Zugang wäre:

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

Geschützt

Wenn eine Methode oder Eigenschaft als protected deklariert wird, kann auf die Methode oder Eigenschaft durch Folgendes zugegriffen werden:

  • Die Klasse, die es deklariert hat.
  • Die Klassen, die die deklarierte Klasse erweitern.

Dadurch können externe Objekte, Klassen oder Code außerhalb der Klassenhierarchie nicht auf diese Methoden oder Eigenschaften zugreifen. Wenn etwas, das diese Methode / Eigenschaft verwendet, keinen Zugriff darauf hat, ist es nicht verfügbar und es wird ein Fehler ausgegeben. Nur Instanzen des deklarierten Selbst (oder seiner Unterklassen) haben Zugriff darauf.

Ein Beispiel für diesen protected Zugriff wäre:

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 ''

Das obige Beispiel weist darauf hin, dass Sie nur innerhalb seines eigenen Bereichs auf die protected Elemente zugreifen können. Im Wesentlichen: "Was im Haus ist, kann nur von innen heraus erreicht werden."


Privatgelände

Wenn eine Methode oder eine Eigenschaft als private deklariert wird, kann auf die Methode oder Eigenschaft durch Folgendes zugegriffen werden:

  • Die Klasse, die es deklariert hat Only (keine Unterklassen).

Eine private Methode oder Eigenschaft ist nur innerhalb der Klasse, in der sie erstellt wurde, sichtbar und zugänglich.

Beachten Sie, dass Objekte desselben Typs Zugriff auf die privaten und geschützten Mitglieder der jeweils anderen Person haben, auch wenn es sich nicht um dieselben Instanzen handelt.

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'

Wie bereits erwähnt, können Sie auf die private Methode / Eigenschaft nur innerhalb ihrer definierten Klasse zugreifen.

Aufrufen eines übergeordneten Konstruktors beim Instanziieren eines untergeordneten Objekts

Eine häufige Gefahr von __construct() Klassen besteht darin, dass nur der __construct() Klassenkonstruktor ausgeführt wird, wenn sowohl Ihr Elternteil als auch Ihr Kind eine Konstruktormethode ( __construct() ) enthalten. Es kann vorkommen, dass Sie die übergeordnete __construct() -Methode von ihrem __construct() aus __construct() . Wenn Sie dies tun müssen, müssen Sie den parent:: scope resolutor verwenden:

parent::__construct();

Wenn Sie sich nun vorstellen, dass in einer realen Situation so etwas aussehen würde:

class Foo {

    function __construct($args) { 
        echo 'parent'; 
    }

}

class Bar extends Foo {

    function __construct($args) {
        parent::__construct($args);
    }
}

Oben wird das übergeordnete __construct() wodurch das echo ausgeführt wird.

Letztes Schlüsselwort

Def: Final Keyword verhindert, dass untergeordnete Klassen eine Methode überschreiben, indem der Definition final mit einem vorangestellten Schlüssel vorangestellt wird. Wenn die Klasse selbst als final definiert wird, kann sie nicht erweitert werden

Endmethode

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()

Abschlussklasse:

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)

Endgültige Konstanten: Im Gegensatz zu Java wird das final Schlüsselwort nicht für Klassenkonstanten in PHP verwendet. Verwenden Sie stattdessen das Schlüsselwort const .

Warum muss ich final ?

  1. Verhindern massiver Erbschaftskette des Schicksals
  2. Zusammenstellung fördern
  3. Erzwingen Sie den Entwickler, über die öffentliche API des Benutzers nachzudenken
  4. Erzwingen Sie den Entwickler, die öffentliche API eines Objekts zu verkleinern
  5. Eine final kann jederzeit erweiterbar gemacht werden
  6. Pausen extends die Kapselung
  7. Sie brauchen diese Flexibilität nicht
  8. Sie können den Code frei ändern

Wann Sie final vermeiden: Final-Klassen funktionieren nur unter folgenden Annahmen:

  1. Es gibt eine Abstraktion (Schnittstelle), die die letzte Klasse implementiert
  2. Die gesamte öffentliche API der finalen Klasse ist Teil dieser Schnittstelle

$ this, selbst und statisch plus das Singleton

Verwenden Sie $this , um auf das aktuelle Objekt zu verweisen. Verwenden Sie self , um auf die aktuelle Klasse zu verweisen. Verwenden Sie also $this->member für nicht statische Member und self::$member für statische Member.

In dem folgenden Beispiel verwenden sayHello() und sayGoodbye() self und $this Unterschied kann hier beobachtet werden.

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 bezieht sich auf die Klasse in der Hierarchie, in der Sie die Methode aufgerufen haben. Es ermöglicht eine bessere Wiederverwendung statischer Klasseneigenschaften, wenn Klassen vererbt werden.

Betrachten Sie den folgenden Code:

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();

Dies führt nicht zu dem gewünschten Ergebnis:

unbekannte
unbekannte
unbekannte

Dies liegt daran, dass self sich auf die Klasse Car bezieht, wenn die Methode brand() aufgerufen wird.

Um auf die richtige Klasse zu verweisen, müssen Sie stattdessen static verwenden:

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();

Dies erzeugt die gewünschte Ausgabe:

unbekannte
BMW
Mercedes

Siehe auch Late static Bindung

Der singleton

Wenn Sie über ein Objekt verfügen, das teuer zu erstellen ist oder eine Verbindung zu einer externen Ressource darstellt, die Sie wiederverwenden möchten, z. B. eine Datenbankverbindung, bei der kein Verbindungspooling besteht, oder ein Socket zu einem anderen System, können Sie die Schlüsselwörter static und self in verwenden Klasse, um es zum Einzelgänger zu machen. Es gibt starke Meinungen darüber, ob das Singleton-Pattern verwendet werden sollte oder nicht, aber es hat seine Verwendung.

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
    }
}

Wie Sie im Beispielcode sehen können, definieren wir eine private statische Eigenschaft $instance , die die Objektreferenz enthalten soll. Da dies statisch ist, wird diese Referenz für ALLE Objekte dieses Typs verwendet.

Die getInstance() -Methode verwendet eine als Lazy Instantiation bekannte Methode, um die Erstellung des Objekts bis zum letztmöglichen Zeitpunkt zu verzögern, da nicht verwendete ungenutzte Objekte im Speicher niemals verwendet werden sollen. Außerdem wird Zeit gespart und die CPU beim Laden der Seite muss nicht mehr Objekte laden als nötig. Die Methode prüft, ob das Objekt gesetzt ist, erstellt es, falls nicht, und gibt es zurück. Dadurch wird sichergestellt, dass immer nur ein Objekt dieser Art erstellt wird.

Wir setzen den Konstruktor auch als privat, um sicherzustellen, dass ihn niemand mit dem new Schlüsselwort von außen erstellt. Wenn Sie von dieser Klasse erben müssen, ändern Sie die private Schlüsselwörter in protected .

Um dieses Objekt zu verwenden, schreiben Sie einfach Folgendes:

$singleton = Singleton::getInstance();

Nun möchte ich Sie dazu auffordern, Abhängigkeitsinjektion zu verwenden, bei der Sie locker gekoppelte Objekte anvisieren können, aber manchmal ist das einfach nicht sinnvoll und das Singleton-Muster kann nützlich sein.

Autoloading

Niemand möchte jedes Mal require oder include wenn eine Klasse oder Vererbung verwendet wird. Da es schmerzhaft sein kann und leicht zu vergessen ist, bietet PHP das sogenannte Autoloading. Wenn Sie Composer bereits verwenden, lesen Sie das automatische Laden mit Composer .

Was genau ist das Autoloading?

Der Name sagt im Grunde alles aus. Sie müssen nicht auf die Datei erhalten , wenn die angeforderten Klasse gespeichert ist , aber PHP auto matisch Last s es.

Wie kann ich dies in PHP-Grundlagen ohne Code von Drittanbietern tun?

Es ist die Funktion __autoload , aber es gilt als bessere Praxis zu verwenden spl_autoload_register . Diese Funktionen werden von PHP jedes Mal berücksichtigt, wenn eine Klasse nicht innerhalb des angegebenen Bereichs definiert ist. Das Hinzufügen von Autoload zu einem vorhandenen Projekt ist daher kein Problem, da definierte Klassen (über require dh) wie zuvor funktionieren. Der Einfachheit halber werden in den folgenden Beispielen anonyme Funktionen verwendet. Wenn Sie PHP <5.3 verwenden, können Sie die Funktion definieren und ihren Namen als Argument an spl_autoload_register .

Beispiele

spl_autoload_register(function ($className) {
    $path = sprintf('%s.php', $className);
    if (file_exists($path)) {
        include $path;
    } else {
        // file not found
    }
});

Der obige Code versucht einfach, mit sprintf einen Dateinamen mit dem Klassennamen und der angefügten Erweiterung ".php" sprintf . Wenn FooBar geladen werden muss, wird FooBar.php ob FooBar.php vorhanden ist.

Natürlich kann dies auf die individuellen Bedürfnisse des Projekts erweitert werden. Wenn _ innerhalb eines Klassennamens zur Gruppierung verwendet wird, z. B. beziehen sich User_Post und User_Image auf User , beide Klassen können wie User_Image in einem Ordner mit dem Namen "User" User_Image werden:

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
    }
});

Die Klasse User_Post wird jetzt aus "User / Post.php" usw. geladen.

spl_autoload_register kann an verschiedene Bedürfnisse angepasst werden. Alle Ihre Dateien mit Klassen heißen "class.CLASSNAME.php". Kein Problem. Verschiedene Verschachtelung ( User_Post_Content => "User / Post / Content.php")? Kein Problem.

Wenn Sie einen umfassenderen Autoloading-Mechanismus wünschen - und Composer trotzdem nicht einbinden möchten - können Sie arbeiten, ohne Bibliotheken von Drittanbietern hinzuzufügen.

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
            )
        );
    }
});

Mit Autoloadern wie diesem können Sie Code wie folgt schreiben:

require_once './autoload.php'; // where spl_autoload_register is defined

$foo = new Foo_Bar(new Hello_World());

Klassen verwenden:

class Foo_Bar extends Foo {}
class Hello_World implements Demo_Classes {}

Diese Beispiele umfassen Klassen aus foo/bar.php , foo.php , hello/world.php und demo/classes.php .

Anonyme Klassen

In PHP 7 wurden anonyme Klassen eingeführt, um das schnelle Erstellen von einmaligen Objekten zu ermöglichen. Sie können Konstruktorargumente annehmen, andere Klassen erweitern, Schnittstellen implementieren und Traits genauso verwenden wie normale Klassen.

In ihrer grundlegendsten Form sieht eine anonyme Klasse wie folgt aus:

new class("constructor argument") {
    public function __construct($param) {
        var_dump($param);
    }
}; // string(20) "constructor argument"

Durch das Verschachteln einer anonymen Klasse innerhalb einer anderen Klasse erhält sie keinen Zugriff auf private oder geschützte Methoden oder Eigenschaften dieser äußeren Klasse. Zugriff auf geschützte Methoden und Eigenschaften der äußeren Klasse erhält man durch Erweiterung der äußeren Klasse von der anonymen Klasse. Der Zugriff auf private Eigenschaften der äußeren Klasse kann durch Weitergabe an den Konstruktor der anonymen Klasse erfolgen.

Zum Beispiel:

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

Grundklasse definieren

Ein Objekt in PHP enthält Variablen und Funktionen. Objekte gehören normalerweise zu einer Klasse, die die Variablen und Funktionen definiert, die alle Objekte dieser Klasse enthalten werden.

Die Syntax zum Definieren einer Klasse lautet:

class Shape {
    public $sides = 0;
    
    public function description() {
        return "A shape with $this->sides sides.";
    }
}

Sobald eine Klasse definiert ist, können Sie eine Instanz erstellen mit:

$myShape = new Shape();

Auf Variablen und Funktionen des Objekts wird wie folgt zugegriffen:

$myShape = new Shape();
$myShape->sides = 6;

print $myShape->description(); // "A shape with 6 sides"

Konstrukteur

Klassen können eine spezielle __construct() -Methode definieren, die als Teil der Objekterstellung ausgeführt wird. Dies wird häufig verwendet, um die Anfangswerte eines Objekts anzugeben:

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

Eine andere Klasse erweitern

Klassendefinitionen können vorhandene Klassendefinitionen erweitern, neue Variablen und Funktionen hinzufügen sowie die in der übergeordneten Klasse definierten ändern.

Hier ist eine Klasse, die das vorherige Beispiel erweitert:

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;
    }
}

Die Square Klasse enthält Variablen und Verhalten für die Shape Klasse und die Square Klasse:

$mySquare = new Square(10);

print $mySquare->description()/ // A shape with 4 sides

print $mySquare->perimeter() // 40

print $mySquare->area() // 100


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow