Szukaj…


Wprowadzenie

Klasy i obiekty służą do zwiększania wydajności i mniej powtarzalności kodu poprzez grupowanie podobnych zadań.

Klasa służy do definiowania akcji i struktury danych używanych do budowania obiektów. Obiekty są następnie budowane przy użyciu tej predefiniowanej struktury.

Składnia

  • class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { } // Deklaracja klasy
  • interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { } // Deklaracja interfejsu
  • use <Trait1> [, <Trait2>, ...] ; // Użyj cech
  • [ public | protected | private ] [ static ] $<varName>; // Deklaracja atrybutu
  • const <CONST_NAME>; // Deklaracja stała
  • [ public | protected | private ] [ static ] function <methodName>([args...]) { } // Deklaracja metody

Uwagi

Klasy i elementy interfejsu

Klasy mogą mieć właściwości, stałe i metody.

  • Właściwości przechowują zmienne w zakresie obiektu. Mogą być inicjowane podczas deklaracji, ale tylko jeśli zawierają prymitywną wartość.
  • Stałe muszą być inicjowane podczas deklaracji i mogą zawierać tylko pierwotną wartość. Stałe wartości są ustalane w czasie kompilacji i nie mogą być przypisywane w czasie wykonywania.
  • Metody muszą mieć ciało, nawet puste, chyba że metoda zostanie uznana za abstrakcyjną.
class Foo {
    private $foo = 'foo'; // OK
    private $baz = array(); // OK
    private $bar = new Bar(); // Error!
}

Interfejsy nie mogą mieć właściwości, ale mogą mieć stałe i metody.

  • Stałe interfejsu muszą być inicjowane podczas deklaracji i mogą zawierać tylko pierwotną wartość. Stałe wartości są ustalane w czasie kompilacji i nie mogą być przypisywane w czasie wykonywania.
  • Metody interfejsu nie mają treści.
interface FooBar {
    const FOO_VALUE = 'bla';
    public function doAnything();
}

Interfejsy

Wprowadzenie

Interfejsy to definicje publicznych interfejsów API, które muszą zostać zaimplementowane w celu spełnienia wymagań interfejsu. Działają jako „kontrakty”, określając, co robi zestaw podklas, ale nie sposób , w jaki to robią.

Definicja interfejsu jest podobna do definicji klasy, zmiana class słowa kluczowego na interface :

interface Foo {

}

Interfejsy mogą zawierać metody i / lub stałe, ale bez atrybutów. Stałe interfejsu mają takie same ograniczenia jak stałe klasy. Metody interfejsu są domyślnie abstrakcyjne:

interface Foo {
    const BAR = 'BAR';

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

Uwaga: interfejsy nie mogą deklarować konstruktorów ani destruktorów, ponieważ są to szczegóły implementacji na poziomie klasy.

Realizacja

Każda klasa, która musi zaimplementować interfejs, musi to zrobić za pomocą słowa kluczowego implements . Aby to zrobić, klasa musi zapewnić implementację dla każdej metody zadeklarowanej w interfejsie, uwzględniając ten sam podpis.

Jedna klasa może implementować więcej niż jeden interfejs na raz.

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

Kiedy klasy abstrakcyjne implementują interfejsy, nie muszą implementować wszystkich metod. Każda metoda niezaimplementowana w klasie bazowej musi zostać zaimplementowana przez konkretną klasę, która ją rozszerza:

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

Zauważ, że realizacja interfejsu jest cechą odziedziczoną. Rozszerzając klasę, która implementuje interfejs, nie trzeba ponownie konfigurować go w konkretnej klasie, ponieważ jest ona niejawna.

Uwaga: przed PHP 5.3.9 klasa nie mogła zaimplementować dwóch interfejsów, które określiły metodę o tej samej nazwie, ponieważ spowodowałoby to niejednoznaczność. Nowsze wersje PHP na to pozwalają, o ile duplikaty metod mają tę samą sygnaturę [1] .

Dziedzictwo

Podobnie jak klasy, możliwe jest ustanowienie relacji dziedziczenia między interfejsami przy użyciu tego samego extends słowa kluczowego. Główną różnicą jest to, że dla interfejsów dozwolone jest wielokrotne dziedziczenie:

interface Foo {

}

interface Bar {

}

interface Baz extends Foo, Bar {

}

Przykłady

W poniższym przykładzie mamy prosty przykładowy interfejs dla pojazdu. Pojazdy mogą jechać do przodu i do tyłu.

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

    ...
}

Następnie tworzymy dwie klasy implementujące interfejs: Rower i Samochód. Rower i samochód są bardzo różne, ale oba są pojazdami i muszą implementować te same metody publiczne, które zapewnia VehicleInterface.

Typehinting pozwala metodom i funkcjom żądać interfejsów. Załóżmy, że mamy klasę garażu, która zawiera wszelkiego rodzaju pojazdy.

class ParkingGarage {
    protected $vehicles = [];

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

Ponieważ addVehicle wymaga $vehicle typu VehicleInterface - a nie konkretnej implementacji - możemy wprowadzić zarówno Rowery, jak i Samochody, którymi może manipulować ParkingGarage i z których można korzystać.

Stałe klasowe

Stałe klasy zapewniają mechanizm utrzymywania stałych wartości w programie. Oznacza to, że umożliwiają nadanie nazwy (i powiązanego sprawdzania czasu kompilacji) wartości takiej jak 3.14 lub "Apple" . Stałe klasy mogą być definiowane wyłącznie z const słów kluczowych - w zdefiniowania funkcji nie mogą być używane w tym kontekście.

Na przykład może być dogodne mieć skróconą reprezentację wartości π w całym programie. Klasa z wartościami const zapewnia prosty sposób przechowywania takich wartości.

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

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

Do stałych klas można uzyskać dostęp za pomocą operatora podwójnego dwukropka (tak zwanego operatora rozdzielczości zakresu) w klasie, podobnie jak zmienne statyczne. Jednak w przeciwieństwie do zmiennych statycznych, stałe klas mają swoje wartości ustalone w czasie kompilacji i nie można ich ponownie przypisać (np. MathValues::PI = 7 spowodowałby błąd krytyczny).

Stałe klasy są również przydatne do definiowania rzeczy wewnętrznych w klasie, które mogą wymagać późniejszej zmiany (ale nie zmieniają się wystarczająco często, aby uzasadnić przechowywanie, powiedzmy, bazy danych). Możemy odwołać to wewnętrznie za pomocą self resolutor Zakres (który działa zarówno w implementacjach Instanced i statycznych)

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

Stałe klasy mogą zawierać wartości skalarne tylko w wersjach <5.6

Od wersji PHP 5.6 możemy używać wyrażeń ze stałymi, co oznacza, że instrukcje matematyczne i ciągi z konkatenacją są stałymi dopuszczalnymi

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

Począwszy od PHP 7.0, stałe zadeklarowane za pomocą define mogą teraz zawierać tablice.

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

Stałe klas są użyteczne nie tylko do przechowywania pojęć matematycznych. Na przykład, przygotowując ciasto, może być dogodne mieć jedną klasę Pie która może przyjmować różne rodzaje owoców.

class Pie {
    protected $fruit;

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

Możemy wtedy użyć klasy Pie

$pie = new Pie("strawberry");

Problem, który się tu pojawia, polega na tym, że podczas tworzenia instancji klasy Pie nie ma wskazówek co do dopuszczalnych wartości. Na przykład, kiedy robisz ciasto „boysenberry”, może być błędnie napisane „boisenberry”. Ponadto możemy nie obsługiwać śliwkowego ciasta. Zamiast tego przydałoby się mieć listę akceptowalnych rodzajów owoców, które zostały już zdefiniowane gdzieś, warto je poszukać. Powiedz klasę o nazwie Fruit :

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

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

Podanie dopuszczalnych wartości jako stałych klasy stanowi cenną wskazówkę co do dopuszczalnych wartości, które akceptuje metoda. Zapewnia również, że błędy ortograficzne nie mogą przejść obok kompilatora. Podczas gdy zarówno new Pie('aple') i new Pie('apple') są akceptowane przez kompilator, new Pie(Fruit::APLE) spowoduje błąd kompilatora.

Wreszcie, użycie stałych klasy oznacza, że rzeczywista wartość stałej może być modyfikowana w jednym miejscu, a każdy kod używający stałej automatycznie ma skutki modyfikacji.

Chociaż najczęstszą metodą dostępu do stałej klasy jest MyClass::CONSTANT_NAME , może być również dostępna przez:

echo MyClass::CONSTANT;

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

Stałe klas w PHP są konwencjonalnie nazywane wielkimi literami, a podkreślniki są separatorami słów, chociaż każda poprawna nazwa etykiety może być używana jako stała nazwa klasy.

Począwszy od PHP 7.1, stałe klas mogą być teraz definiowane z innymi widocznościami niż domyślny zasięg publiczny. Oznacza to, że można teraz zdefiniować zarówno stałe chronione, jak i prywatne, aby zapobiec niepotrzebnemu wyciekowi stałych klasy do zakresu publicznego (patrz Widoczność metody i właściwości ). Na przykład:

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

zdefiniuj stałe vs klasy

Chociaż jest to poprawna konstrukcja:

function bar() { return 2; };

define('BAR', bar());

Jeśli spróbujesz zrobić to samo ze stałymi klas, pojawi się błąd:

function bar() { return 2; };

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

Ale możesz zrobić:

function bar() { return 2; };

define('BAR', bar());

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

Aby uzyskać więcej informacji, zobacz stałe w instrukcji .

Użycie :: class do pobrania nazwy klasy

PHP 5.5 wprowadził składnię ::class celu uzyskania pełnej nazwy klasy, biorąc pod uwagę zakres przestrzeni nazw i use instrukcji.

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"

Powyższe działa, nawet jeśli klasy nie są nawet zdefiniowane (tj. Ten fragment kodu działa sam).

Ta składnia jest przydatna w przypadku funkcji wymagających nazwy klasy. Na przykład można go użyć z class_exists aby sprawdzić, czy klasa istnieje. Błędy nie będą generowane niezależnie od wartości zwracanej w tym fragmencie:

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

Późne wiązanie statyczne

W PHP 5.3+ i nowszych możesz użyć późnego wiązania statycznego, aby kontrolować, z której klasy wywoływana jest właściwość lub metoda statyczna. Został on dodany w celu rozwiązania problemu związanego z rozwiązaniem self:: scope. Weź następujący kod

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

Można oczekiwać, że klasa MrEd zastąpi nadrzędną whatToSay() . Ale kiedy to uruchomimy, dostajemy coś nieoczekiwanego

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

Problem polega na tym, że self::whatToSay(); może odnosić się tylko do klasy Horse , co oznacza, że nie jest posłuszny MrEd . Jeśli przejdziemy do static:: resolwera static:: scope, nie mamy tego problemu. Ta nowsza metoda nakazuje klasie zastosować się do instancji, która ją wywołuje. W ten sposób otrzymujemy dziedzictwo, którego oczekujemy

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

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

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

Klasy abstrakcyjne

Klasa abstrakcyjna to klasa, której nie można utworzyć. Klasy abstrakcyjne mogą definiować metody abstrakcyjne, które są metodami bez treści, tylko definicja:

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

Klasy abstrakcyjne powinny zostać rozszerzone o klasę potomną, która może następnie zapewnić implementację tych metod abstrakcyjnych.

Głównym celem takiej klasy jest dostarczenie pewnego rodzaju szablonu, który pozwala klasom potomnym dziedziczyć, „zmuszając” strukturę do przestrzegania. Rozważmy to na przykładzie:

W tym przykładzie będziemy implementować interfejs Worker . Najpierw definiujemy interfejs:

interface Worker {
    public function run();
}

Aby ułatwić rozwój kolejnych implementacji Workera, utworzymy abstrakcyjną klasę robotniczą, która już udostępnia metodę run() z interfejsu, ale określa pewne abstrakcyjne metody, które muszą zostać wypełnione przez dowolną klasę potomną:

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

Przede wszystkim udostępniliśmy abstrakcyjną metodę getMemoryLimit() . Każda klasa rozszerzająca się z AbstractWorker musi podać tę metodę i zwrócić limit pamięci. Następnie AbstractWorker ustawia limit pamięci i rejestruje go.

Po drugie, AbstractWorker wywołuje metody prepareMain() i main() , po zalogowaniu, że zostały wywołane.

Na koniec wszystkie te wywołania metod zostały zgrupowane w bloku try - catch . Więc jeśli którakolwiek z metod abstrakcyjnych zdefiniowanych przez klasę potomną zgłasza wyjątek, złapiemy ten wyjątek, zarejestrujemy go i ponownie wyrzucimy. Dzięki temu wszystkie klasy potomne nie będą musiały tego samodzielnie wdrażać.

Teraz pozwala zdefiniować klasę potomną, która rozciąga się od 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();
        }
    }
}

Jak widać, TransactionProcessorWorker był raczej łatwy do wdrożenia, ponieważ musieliśmy jedynie określić limit pamięci i martwić się o faktyczne działania, które musiał wykonać. Obsługa TransactionProcessorWorker nie jest wymagana w TransactionProcessorWorker ponieważ jest to obsługiwane w AbsractWorker .

Ważna uwaga

Dziedzicząc po klasie abstrakcyjnej, wszystkie metody oznaczone jako abstrakcyjne w deklaracji klasy rodzica muszą być zdefiniowane przez dziecko (lub samo dziecko musi również zostać oznaczone jako abstrakcyjne); dodatkowo metody te muszą być zdefiniowane z taką samą (lub mniej ograniczoną) widocznością. Na przykład, jeśli metoda abstrakcyjna jest zdefiniowana jako chroniona, implementacja funkcji musi być zdefiniowana jako chroniona lub publiczna, ale nie prywatna.

Zaczerpnięte z dokumentacji PHP dla abstrakcji klas .

Jeśli nie zdefiniujesz metod nadrzędnych klas abstrakcyjnych w klasie podrzędnej, zostanie zgłoszony krytyczny błąd PHP, taki jak poniżej.

Błąd krytyczny: klasa X zawiera 1 metodę abstrakcyjną i dlatego musi zostać zadeklarowana jako abstrakcyjna lub zaimplementować pozostałe metody (X :: x) w

Przestrzeń nazw i automatyczne ładowanie

Technicznie, automatyczne ładowanie działa poprzez wykonanie wywołania zwrotnego, gdy klasa PHP jest wymagana, ale nie została znaleziona. Takie wywołania zwrotne zwykle próbują załadować te klasy.

Ogólnie, automatyczne ładowanie może być rozumiane jako próba załadowania plików PHP (szczególnie plików klasy PHP, gdzie plik źródłowy PHP jest dedykowany dla określonej klasy) z odpowiednich ścieżek zgodnie z w pełni kwalifikowaną nazwą klasy (FQN), gdy klasa jest potrzebna .

Załóżmy, że mamy te klasy:

Plik klasy dla application\controllers\Base :

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

Plik klasy dla application\controllers\Control :

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

Plik klasy dla application\models\Page :

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

W folderze źródłowym klasy te należy umieścić odpowiednio na ścieżkach jako ich nazwy FQN:

  • Folder źródłowy
    • applications
      • controllers
        • Base.php
        • Control.php
      • models
        • Page.php

Takie podejście umożliwia programowe rozwiązanie ścieżki do pliku klasy zgodnie z FQN przy użyciu tej funkcji:

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
}

Funkcja spl_autoload_register pozwala nam załadować klasę w razie potrzeby za pomocą funkcji zdefiniowanej przez użytkownika:

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

Funkcję tę można dodatkowo rozszerzyć, aby korzystać z zastępczych metod ładowania:

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

Zauważ, że PHP nie próbuje ładować klas za każdym razem, gdy ładowany jest plik korzystający z tej klasy. Może być załadowany w środku skryptu, a nawet w funkcjach zamykania. Jest to jeden z powodów, dla których programiści, szczególnie ci, którzy używają automatycznego ładowania, powinni unikać zastępowania wykonywania plików źródłowych w środowisku wykonawczym, szczególnie w plikach phar.

Dynamiczne wiązanie

Wiązanie dynamiczne, nazywane również zastępowaniem metod, jest przykładem polimorfizmu w czasie wykonywania, który występuje, gdy wiele klas zawiera różne implementacje tej samej metody, ale obiekt, do którego zostanie wywołana metoda, jest nieznany do czasu wykonania .

Jest to przydatne, gdy określony warunek określa, która klasa zostanie użyta do wykonania akcji, w przypadku której akcja ma taką samą nazwę w obu klasach.

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

W powyższym przykładzie klasa Animal ( Dog|Cat ), która będzie makeNoise jest nieznana do czasu uruchomienia w zależności od właściwości w klasie User .

Widoczność metody i właściwości

Istnieją trzy typy widoczności, które można zastosować do metod ( funkcje klasy / obiektu ) i właściwości ( zmienne klasy / obiektu ) w klasie, które zapewniają kontrolę dostępu do metody lub właściwości, do której są stosowane.

Możesz przeczytać o nich obszernie w Dokumentacji PHP dla widoczności OOP .

Publiczny

Deklaracja metody lub właściwości jako public pozwala na dostęp do metody lub właściwości przez:

  • Klasa, która to zadeklarowała.
  • Klasy, które rozszerzają zadeklarowaną klasę.
  • Wszelkie obiekty zewnętrzne, klasy lub kod poza hierarchią klas.

Przykładem tego public dostępu jest:

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

Chroniony

Deklaracja metody lub właściwości jako protected umożliwia dostęp do metody lub właściwości przez:

  • Klasa, która to zadeklarowała.
  • Klasy, które rozszerzają zadeklarowaną klasę.

Nie pozwala to na dostęp do tych metod lub właściwości zewnętrznym obiektom, klasom lub kodowi poza hierarchią klas. Jeśli coś korzystającego z tej metody / właściwości nie ma do niej dostępu, nie będzie ono dostępne i zostanie zgłoszony błąd. Dostęp do niego mają tylko przypadki zadeklarowanego siebie (lub jego podklas).

Przykładem tego protected dostępu byłoby:

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

W powyższym przykładzie zauważono, że dostęp do protected elementów można uzyskać tylko we własnym zakresie. Zasadniczo: „Do domu można uzyskać dostęp tylko z jego wnętrza”.


Prywatny

Deklaracja metody lub właściwości jako private umożliwia dostęp do metody lub właściwości przez:

  • Klasa, która zadeklarowała ją Tylko (nie podklasy).

private metoda lub właściwość jest widoczna i dostępna tylko w klasie, która ją utworzyła.

Pamiętaj, że obiekty tego samego typu będą miały dostęp do prywatnych i chronionych członków, nawet jeśli nie są to te same instancje.

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'

Jak wspomniano, dostęp do private metody / właściwości można uzyskać tylko z jej zdefiniowanej klasy.

Wywoływanie konstruktora nadrzędnego podczas tworzenia wystąpienia dziecka

Częstą pułapką klas potomnych jest to, że jeśli rodzic i dziecko zawierają metodę konstruktora ( __construct() ), uruchomi się tylko konstruktor klasy __construct() . Mogą zaistnieć sytuacje, w których trzeba uruchomić __construct() nadrzędną __construct() z jej potomka. Jeśli musisz to zrobić, musisz użyć resolwera parent:: scope:

parent::__construct();

Teraz wykorzystanie tego w rzeczywistej sytuacji wyglądałoby mniej więcej tak:

class Foo {

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

}

class Bar extends Foo {

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

Powyższe spowoduje uruchomienie nadrzędnej __construct() co spowoduje uruchomienie echo .

Ostateczne słowo kluczowe

Def: Końcowe słowo kluczowe zapobiega zastępowaniu metody przez klasy potomne, poprzedzając definicję słowem końcowym. Jeśli sama klasa jest definiowana jako ostateczna, nie można jej rozszerzyć

Ostateczna metoda

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

Klasa końcowa:

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)

Stałe końcowe: W przeciwieństwie do Javy, final słowo kluczowe nie jest używane dla stałych klas w PHP. Zamiast tego użyj słowa kluczowego const .

Dlaczego muszę użyć final ?

  1. Zapobieganie masowemu spadkowi łańcucha losu
  2. Zachęcająca kompozycja
  3. Zmuś programistę do myślenia o publicznym interfejsie API użytkownika
  4. Zmuś programistę do zmniejszenia publicznego interfejsu API obiektu
  5. final klasę można zawsze rozszerzyć
  6. extends enkapsulację przerw
  7. Nie potrzebujesz tej elastyczności
  8. Możesz zmienić kod

Kiedy unikać final : Klasy końcowe działają skutecznie tylko przy następujących założeniach:

  1. Istnieje abstrakcja (interfejs), którą implementuje ostatnia klasa
  2. Cały publiczny interfejs API ostatecznej klasy jest częścią tego interfejsu

$ this, self and static plus singleton

Użyj $this aby odnieść się do bieżącego obiektu. Użyj self aby odnieść się do bieżącej klasy. Innymi słowy, użyj $this->member dla członków niestatycznych, użyj self::$member dla członków statycznych.

W poniższym przykładzie sayGoodbye() sayHello() i sayGoodbye() używają self a $this różnicę można zaobserwować tutaj.

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 odnosi się do dowolnej klasy w hierarchii, w której wywołałeś metodę. Pozwala to na lepsze wykorzystanie właściwości klas statycznych podczas dziedziczenia klas.

Rozważ następujący kod:

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

To nie daje pożądanego rezultatu:

nieznany
nieznany
nieznany

Jest tak, ponieważ self odnosi się do klasy Car za każdym razem, gdy wywoływana jest metoda brand() .

Aby odwołać się do właściwej klasy, należy zamiast tego użyć 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();

To daje pożądaną wydajność:

nieznany
BMW
Mercedes

Zobacz także Późne wiązanie statyczne

Singleton

Jeśli masz obiekt, który jest kosztowny do utworzenia lub reprezentuje połączenie z jakimś zasobem zewnętrznym, którego chcesz ponownie użyć, tj. Połączenie z bazą danych, w którym nie ma puli połączeń lub gniazda z innym systemem, możesz użyć static i self słów kluczowych w klasa, aby uczynić go singletonem. Istnieją mocne opinie na temat tego, czy należy stosować wzorzec singletonu, czy nie, ale ma on swoje zastosowania.

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

Jak widać w przykładowym kodzie, definiujemy prywatną $instance własności statycznej do przechowywania odwołania do obiektu. Ponieważ jest to wartość statyczna, to odwołanie jest wspólne dla WSZYSTKICH obiektów tego typu.

Metoda getInstance() używa metody znanej jako leniwa instancja, aby opóźnić utworzenie obiektu do ostatniej możliwej chwili, ponieważ nie chcesz, aby nieużywane obiekty leżały w pamięci nigdy nie były przeznaczone do użycia. Oszczędza również czas i procesor przy ładowaniu strony bez konieczności ładowania większej liczby obiektów niż to konieczne. Metoda sprawdza, czy obiekt jest ustawiony, tworząc go, jeśli nie, i zwraca go. Dzięki temu powstaje tylko jeden obiekt tego rodzaju.

Ustawiamy też konstruktor na prywatny, aby nikt nie tworzył go przy użyciu new słowa kluczowego z zewnątrz. Jeśli chcesz dziedziczyć po tej klasie, po prostu zmień private słowa kluczowe na protected .

Aby użyć tego obiektu, wystarczy napisać:

$singleton = Singleton::getInstance();

Teraz błagam cię, abyś użył zastrzyku zależności, gdzie możesz i celujesz w luźno sprzężone obiekty, ale czasami jest to po prostu nieuzasadnione i może być użyteczny wzorzec singletonu.

Automatyczne ładowanie

Nikt nie chce require ani include każdym razem, gdy używana jest klasa lub dziedzictwo. Ponieważ może to być bolesne i łatwe do zapomnienia, PHP oferuje tak zwane automatyczne ładowanie. Jeśli korzystasz już z Composer, przeczytaj o automatycznym ładowaniu za pomocą Composer .

Czym dokładnie jest automatyczne ładowanie?

Nazwa w zasadzie mówi wszystko. Nie trzeba pobrać plik, gdzie wymagane jest przechowywany w klasie, ale PHP auto matycznie obciążenie jest to.

Jak mogę to zrobić w podstawowym PHP bez kodu innej firmy?

Istnieje funkcja __autoload , ale uważa się za lepszą praktykę stosowanie spl_autoload_register . Funkcje te będą brane pod uwagę przez PHP za każdym razem, gdy klasa nie zostanie zdefiniowana w danym obszarze. Tak więc dodanie autoloadu do istniejącego projektu nie stanowi problemu, ponieważ zdefiniowane klasy (przez require ie) będą działały jak wcześniej. Ze względu na dokładność, poniższe przykłady wykorzystają funkcje anonimowe, jeśli używasz PHP <5.3, możesz zdefiniować funkcję i przekazać jej nazwę jako argument do spl_autoload_register .

Przykłady

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

Powyższy kod po prostu próbuje dołączyć nazwę pliku z nazwą klasy i dołączonym rozszerzeniem „.php” za pomocą sprintf . Jeśli FooBar wymaga załadowania, sprawdza, czy FooBar.php istnieje, a jeśli tak, to go obejmuje.

Oczywiście można to rozszerzyć, aby dopasować do indywidualnych potrzeb projektu. Jeśli do grupowania używane jest _ wewnątrz nazwy klasy, np. User_Post i User_Image odnoszą się do User , obie klasy mogą być przechowywane w folderze o nazwie „Użytkownik” w następujący sposób:

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

Klasa User_Post zostanie teraz załadowana z „User / Post.php” itp.

spl_autoload_register można dostosować do różnych potrzeb. Wszystkie twoje pliki z klasami noszą nazwę „class.CLASSNAME.php”? Nie ma problemu. Różne zagnieżdżanie ( User_Post_Content => „User / Post / Content.php”)? Nie ma też problemu.

Jeśli chcesz bardziej rozbudowanego mechanizmu automatycznego ładowania - i nadal nie chcesz uwzględniać Composer - możesz pracować bez dodawania bibliotek stron trzecich.

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

Korzystając z autoloaderów takich jak ten, możesz z przyjemnością napisać kod w następujący sposób:

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

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

Korzystanie z klas:

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

Te przykłady będą obejmować klasy z foo/bar.php , foo.php , hello/world.php i demo/classes.php .

Anonimowe klasy

Do PHP 7 wprowadzono anonimowe klasy, aby umożliwić szybkie tworzenie jednorazowych obiektów. Mogą przyjmować argumenty konstruktora, rozszerzać inne klasy, implementować interfejsy i używać cech tak jak normalne klasy.

W najbardziej podstawowej formie anonimowa klasa wygląda następująco:

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

Zagnieżdżenie anonimowej klasy w innej klasie nie daje jej dostępu do prywatnych lub chronionych metod lub właściwości tej klasy zewnętrznej. Dostęp do chronionych metod i właściwości klasy zewnętrznej można uzyskać poprzez rozszerzenie klasy zewnętrznej z klasy anonimowej. Dostęp do prywatnych właściwości klasy zewnętrznej można uzyskać, przekazując je konstruktorowi anonimowej klasy.

Na przykład:

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

Definiowanie klasy podstawowej

Obiekt w PHP zawiera zmienne i funkcje. Obiekty zazwyczaj należą do klasy, która definiuje zmienne i funkcje, które będą zawierać wszystkie obiekty tej klasy.

Składnia do zdefiniowania klasy to:

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

Po zdefiniowaniu klasy możesz utworzyć instancję, używając:

$myShape = new Shape();

Dostęp do zmiennych i funkcji obiektu można uzyskać w następujący sposób:

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

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

Konstruktor

Klasy mogą definiować specjalną __construct() , która jest wykonywana w ramach tworzenia obiektu. Jest to często używane do określenia początkowych wartości obiektu:

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

Rozszerzanie kolejnej klasy

Definicje klas mogą rozszerzać istniejące definicje klas, dodawać nowe zmienne i funkcje, a także modyfikować te zdefiniowane w klasie nadrzędnej.

Oto klasa, która stanowi rozwinięcie poprzedniego przykładu:

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

Klasa Square zawiera zmienne i zachowanie zarówno dla klasy Shape i klasy Square :

$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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow