Sök…


Introduktion

Klasser och objekt används för att göra din kod effektivare och mindre repetitiv genom att gruppera liknande uppgifter.

En klass används för att definiera åtgärder och datastruktur som används för att bygga objekt. Objekten byggs sedan med denna fördefinierade struktur.

Syntax

  • class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { } // Klassdeklaration
  • interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { } // Gränssnittsdeklaration
  • use <Trait1> [, <Trait2>, ...] ; // Använd drag
  • [ public | protected | private ] [ static ] $<varName>; // Attributdeklaration
  • const <CONST_NAME>; // Konstant deklaration
  • [ public | protected | private ] [ static ] function <methodName>([args...]) { } // Metoddeklaration

Anmärkningar

Klasser och gränssnittskomponenter

Klasser kan ha egenskaper, konstanter och metoder.

  • Egenskaper har variabler inom objektets räckvidd. De kan initialiseras vid deklarationen, men bara om de innehåller ett primitivt värde.
  • Konstanter måste initialiseras vid deklarationen och kan endast innehålla ett primitivt värde. Konstantvärden är fixerade vid sammanställningstiden och kanske inte tilldelas vid körtid.
  • Metoder måste ha en kropp, till och med en tom, såvida inte metoden förklaras abstrakt.
class Foo {
    private $foo = 'foo'; // OK
    private $baz = array(); // OK
    private $bar = new Bar(); // Error!
}

Gränssnitt kan inte ha egenskaper, men kan ha konstanter och metoder.

  • Gränssnittskonstanter måste initieras på deklaration och kan bara innehålla ett primitivt värde. Konstantvärden är fixerade vid sammanställningstiden och kanske inte tilldelas vid körtid.
  • Gränssnitts metoder har ingen kropp.
interface FooBar {
    const FOO_VALUE = 'bla';
    public function doAnything();
}

gränssnitt

Introduktion

Gränssnitt är definitioner av de offentliga API: er som klasser måste implementera för att tillfredsställa gränssnittet. De fungerar som "kontrakt" och anger vad en uppsättning underklasser gör, men inte hur de gör det.

Interface definition är mycket lika klassdefinitionen, ändra nyckelordet class till interface :

interface Foo {

}

Gränssnitt kan innehålla metoder och / eller konstanter, men inga attribut. Gränssnittskonstanter har samma begränsningar som klasskonstanter. Gränssnittsmetoder är implicit abstrakta:

interface Foo {
    const BAR = 'BAR';

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

Obs: gränssnitt får inte deklarera konstruktörer eller förstörare, eftersom det här är implementeringsdetaljer på klassnivå.

Insikt

Varje klass som måste genomföra ett gränssnitt måste göra det med hjälp av implements sökord. För att göra det måste klassen tillhandahålla en implementering för varje metod som deklareras i gränssnittet, med respekt för samma signatur.

En enda klass kan implementera mer än ett gränssnitt åt gången.

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

När abstrakta klasser implementerar gränssnitt behöver de inte implementera alla metoder. Alla metoder som inte implementeras i basklassen måste sedan implementeras av betongklassen som utvidgar den:

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

Lägg märke till att gränssnittets förverkligande är en ärftlig egenskap. När du utökar en klass som implementerar ett gränssnitt behöver du inte förklara det i betongklassen, eftersom det är implicit.

Obs: Innan PHP 5.3.9 kunde en klass inte implementera två gränssnitt som specificerade en metod med samma namn, eftersom det skulle orsaka oklarhet. Nyare versioner av PHP tillåter detta så länge som duplikatmetoderna har samma signatur [1] .

Arv

Som klasser, är det möjligt att upprätta ett arv relationen mellan gränssnitt, med hjälp av samma sökord extends . Huvudskillnaden är att flera arv är tillåtna för gränssnitt:

interface Foo {

}

interface Bar {

}

interface Baz extends Foo, Bar {

}

exempel

I exemplet bälgen har vi ett enkelt exempelgränssnitt för ett fordon. Fordon kan gå framåt och bakåt.

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

    ...
}

Sedan skapar vi två klasser som implementerar gränssnittet: Bike and Car. Cykel och bil internt är mycket olika, men båda är fordon och måste implementera samma offentliga metoder som VehicleInterface tillhandahåller.

Typhinting tillåter metoder och funktioner att begära gränssnitt. Låt oss anta att vi har en parkeringsgarantiklass som innehåller fordon av alla slag.

class ParkingGarage {
    protected $vehicles = [];

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

Eftersom addVehicle kräver ett $vehicle av typen VehicleInterface - inte en konkret implementering - kan vi mata in både Bikes och Cars, som ParkingGarage kan manipulera och använda.

Klasskonstanter

Klasskonstanter tillhandahåller en mekanism för att hålla fasta värden i ett program. Det vill säga de tillhandahåller ett sätt att ge ett namn (och tillhörande kompileringstidskontroll) till ett värde som 3.14 eller "Apple" . Klasskonstanter kan bara definieras med nyckelordet const - definieringsfunktionen kan inte användas i detta sammanhang.

Som ett exempel kan det vara bekvämt att ha en kortfattad representation för värdet på π i ett program. En klass med const värden ger ett enkelt sätt att hålla sådana värden.

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

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

Klasskonstanter kan nås genom att använda den dubbla kolonoperatören (så kallad operatören för omfattningsupplösning) på en klass, ungefär som statiska variabler. Till skillnad från statiska variabler har klasskonstanter emellertid sina värden fixerade vid sammanställningstiden och kan inte tilldelas till (t.ex. MathValues::PI = 7 skulle ge ett dödligt fel).

Klasskonstanter är också användbara för att definiera saker som är interna i en klass som kan behöva ändras senare (men ändras inte tillräckligt ofta för att garantera lagring i, till exempel, en databas). Vi kan referera till detta internt med hjälp av self scope resolutor (som fungerar i både instanserade och statiska implementationer)

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

Klasskonstanter kan endast innehålla skalvärden i versioner <5.6

Från PHP 5.6 kan vi använda uttryck med konstanter, vilket innebär att matematiska uttalanden och strängar med sammanlänkning är acceptabla konstanter

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

Från PHP 7.0 kan konstanter som deklarerats med define nu innehålla matriser.

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

Klasskonstanter är användbara för mer än bara att lagra matematiska begrepp. Om du till exempel förbereder en paj kan det vara bekvämt att ha en enda Pie kan ta olika typer av frukt.

class Pie {
    protected $fruit;

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

Vi kan sedan använda Pie

$pie = new Pie("strawberry");

Problemet som uppstår här är, när man instarterar Pie klassen, ges ingen vägledning om de acceptabla värdena. Till exempel, när du gör en "pojke för björnbär", kan det vara felstavad "boisenberry". Dessutom kanske vi inte stöder en plommonpaj. I stället skulle det vara användbart att ha en lista över acceptabla frukttyper som redan definierats någonstans, det skulle vara vettigt att leta efter dem. Säg en klass som heter Fruit :

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

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

Att lista de acceptabla värdena som klasskonstanter ger ett värdefullt tips om de acceptabla värdena som en metod accepterar. Det säkerställer också att felstavningar inte kan komma förbi kompilatorn. Medan new Pie('aple') och new Pie('apple') båda är acceptabla för kompilatorn, kommer new Pie(Fruit::APLE) att producera ett kompileringsfel.

Slutligen betyder användning av klasskonstanter att det verkliga värdet på konstanten kan modifieras på en enda plats, och varje kod som använder konstanten har automatiskt effekterna av modifieringen.

Även om den vanligaste metoden för att komma åt en MyClass::CONSTANT_NAME är MyClass::CONSTANT_NAME , kan den också nås av:

echo MyClass::CONSTANT;

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

Klasskonstanter i PHP benämns konventionellt alla i stora bokstäver med understreck som ordavskiljare, även om något giltigt etikettnamn kan användas som klasskonstantnamn.

Från och med PHP 7.1 kan klasskonstanter nu definieras med olika synbarheter än det offentliga standardområdet. Detta innebär att både skyddade och privata konstanter nu kan definieras för att förhindra klasskonstanter att onödigt läcka ut i det allmänna omfånget (se Metod och egendomssynlighet ). Till exempel:

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

definiera vs klasskonstanter

Även om detta är en giltig konstruktion:

function bar() { return 2; };

define('BAR', bar());

Om du försöker göra samma sak med klasskonstanter får du ett fel:

function bar() { return 2; };

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

Men du kan göra:

function bar() { return 2; };

define('BAR', bar());

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

För mer information, se konstanter i handboken .

Använd :: klass för att hämta klassens namn

PHP 5.5 introducerade ::class syntax för att hämta det fullständiga klassnamnet med hänsyn till namnområdet och use uttalanden.

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"

Ovanstående fungerar även om klasserna inte ens definieras (dvs. det här kodavsnittet fungerar ensamt).

Denna syntax är användbar för funktioner som kräver ett klassnamn. Det kan till exempel användas med class_exists att kontrollera att det finns en klass. Inga fel kommer att genereras oavsett returvärde i det här utdraget:

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

Sen statisk bindning

I PHP 5.3+ och över kan du använda sen statisk bindning för att kontrollera vilken klass en statisk egenskap eller metod kallas från. Det lades till för att övervinna problemet som följer med self:: scope-resolutorn. Ta följande 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!';
    }
}

Du kan förvänta dig att MrEd klassen kommer att åsidosätta whatToSay() förälder whatToSay() . Men när vi kör detta får vi något oväntat

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

Problemet är att self::whatToSay(); kan bara hänvisa till Horse , vilket betyder att den inte följer MrEd . Om vi byter till den static:: scope-resolutorn har vi inte det här problemet. Denna nyare metod säger att klassen ska lyda den instans som kallar den. Således får vi den arv vi förväntar oss

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

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

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

Abstrakta klasser

En abstrakt klass är en klass som inte kan instanseras. Abstrakta klasser kan definiera abstrakta metoder, som är metoder utan någon kropp, bara en definition:

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

Abstrakta klasser bör utvidgas med en barnklass som sedan kan tillhandahålla implementeringen av dessa abstrakta metoder.

Huvudsyftet med en klass som denna är att tillhandahålla en typ av mall som gör det möjligt för barnklasser att ärva från, "tvinga" en struktur att följa. Låt oss utarbeta detta med ett exempel:

I det här exemplet kommer vi att implementera ett Worker gränssnitt. Först definierar vi gränssnittet:

interface Worker {
    public function run();
}

För att underlätta utvecklingen av ytterligare arbetarimplementeringar kommer vi att skapa en abstrakt arbetarklass som redan tillhandahåller run() -metoden från gränssnittet, men specificerar några abstrakta metoder som måste fyllas i av alla barnklass:

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

Först och främst har vi tillhandahållit en abstrakt metod getMemoryLimit() . Varje klass som sträcker sig från AbstractWorker måste tillhandahålla den här metoden och returnera dess minnesgräns. AbstractWorker ställer sedan in minnesgränsen och loggar den.

För det andra AbstractWorker kallar prepareMain() och main() metoder, efter avverkning att de har kallats.

Slutligen har alla dessa metodsamtal grupperats i ett try catch block. Så om någon av de abstrakta metoderna som definieras av barnklassen kastar ett undantag, kommer vi att fånga det undantaget, logga in det och återkasta det. Detta förhindrar att alla barnklasser måste implementera detta själva.

Låter oss nu definiera en barnklass som sträcker sig från 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();
        }
    }
}

Som ni ser var TransactionProcessorWorker ganska lätt att implementera, eftersom vi bara var tvungna att ange minnesgränsen och oroa dig för de faktiska åtgärder som den behövde utföra. Ingen felhantering behövs i TransactionProcessorWorker eftersom det hanteras i AbsractWorker .

Viktig notering

När man ärver från en abstrakt klass måste alla metoder som är markerade abstrakta i förälderns klassdeklaration definieras av barnet (eller barnet måste också vara markerat abstrakt); dessutom måste dessa metoder definieras med samma (eller mindre begränsade) synlighet. Om till exempel den abstrakta metoden definieras som skyddad måste funktionsimplementeringen definieras som antingen skyddad eller offentlig, men inte privat.

Hämtad från PHP-dokumentationen för klassabstraktion .

Om du inte definierar föräldrarnas abstraktklassmetoder inom barnklassen kastas ett Fatal PHP-fel på följande sätt.

Dödligt fel: Klass X innehåller 1 abstrakt metod och måste därför förklaras abstrakt eller implementera de återstående metoderna (X :: x) i

Namnavstånd och autoladning

Tekniskt fungerar autoloading genom att köra ett återuppringning när en PHP-klass krävs men inte hittas. Sådana återuppringningar försöker vanligtvis ladda dessa klasser.

Generellt kan autoloading förstås som försöket att ladda PHP-filer (särskilt PHP-klassfiler, där en PHP-källfil är avsedd för en specifik klass) från lämpliga sökvägar enligt klassens fullt kvalificerade namn (FQN) när en klass behövs .

Anta att vi har dessa klasser:

Klassfil för application\controllers\Base :

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

Klassfil för application\controllers\Control :

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

Klassfil för application\models\Page :

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

Under källmappen bör dessa klasser placeras på banorna som deras FQN: er respektive:

  • Källmapp
    • applications
      • controllers
        • Base.php
        • Control.php
      • models
        • Page.php

Detta tillvägagångssätt gör det möjligt att programmatiskt lösa klassfilens sökväg enligt FQN med denna funktion:

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
}

Funktionen spl_autoload_register tillåter oss att ladda en klass vid behov med en användardefinierad funktion:

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

Denna funktion kan utvidgas ytterligare för att använda fallback-metoder för lastning:

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

Observera att PHP inte försöker ladda klasserna när en fil som använder denna klass laddas. Det kan laddas i mitten av ett skript eller till och med i avstängningsfunktioner. Detta är ett av anledningarna till att utvecklare, särskilt de som använder autoloading, bör undvika att byta ut källfiler under körtiden, särskilt i phar-filer.

Dynamisk bindning

Dynamisk bindning, även kallad metodöverskridande, är ett exempel på körtidspolymorfism som inträffar när flera klasser innehåller olika implementationer av samma metod, men objektet som metoden kommer att anropas är okänt fram till körtid .

Detta är användbart om ett visst villkor dikterar vilken klass som ska användas för att utföra en åtgärd, där åtgärden heter samma i båda klasserna.

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

I ovanstående exempel Animal klassen ( Dog|Cat ), vilket kommer att makeNoise är okänd tills körning beroende på egenskapen i User

Metod och egendomens synlighet

Det finns tre synlighetstyper som du kan använda på metoder ( klass / objektfunktioner ) och egenskaper ( klass / objektvariabler ) i en klass som ger åtkomstkontroll för metoden eller egenskapen som de tillämpas på.

Du kan läsa mycket om dessa i PHP-dokumentationen för OOP-synlighet .

offentlig

Om du förklarar en metod eller en egendom som public gör det möjligt att komma åt metoden eller egenskapen av:

  • Klassen som förklarade det.
  • Klasserna som förlänger den deklarerade klassen.
  • Eventuella externa objekt, klasser eller kod utanför klasshierarkin.

Ett exempel på denna public skulle vara:

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

Skyddade

Att förklara en metod eller en egenskap som protected gör det möjligt att komma åt metoden eller egenskapen av:

  • Klassen som förklarade det.
  • Klasserna som förlänger den deklarerade klassen.

Detta tillåter inte att externa objekt, klasser eller kod utanför klasshierarkin får åtkomst till dessa metoder eller egenskaper. Om något med den här metoden / egenskapen inte har tillgång till den kommer den inte att finnas tillgänglig och ett fel kastas. Endast fall av det deklarerade jaget (eller underklasser därav) har tillgång till det.

Ett exempel på denna protected åtkomst skulle vara:

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

Exemplet ovan noterar att du bara kan få åtkomst till de protected elementen inom det egna området. I huvudsak: "Det som finns i huset kan bara komma inifrån huset."


Privat

Att förklara en metod eller en egenskap som private gör det möjligt att komma åt metoden eller egenskapen av:

  • Klassen som förklarade den endast (inte underklasser).

En private metod eller egenskap är bara synlig och tillgänglig inom klassen som skapade den.

Observera att objekt av samma typ har tillgång till varandras privata och skyddade medlemmar även om de inte är samma fall.

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'

Som nämnts kan du bara komma åt den private metoden / egenskapen från den definierade klassen.

Att ringa en förälderkonstruktör när han instintrar ett barn

En vanlig fallgrop för barnklasser är att om din förälder och ditt barn båda innehåller en konstruktörmetod ( __construct() ), är det bara __construct() körs . Det kan finnas tillfällen där du behöver köra föräldrar __construct() -metoden från sitt barn. Om du behöver göra det måste du använda parent:: scope resolutor:

parent::__construct();

Nu utnyttjar man att det i en verklig situation skulle se ut som:

class Foo {

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

}

class Bar extends Foo {

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

Ovanstående kommer att köra överordnade __construct() vilket resulterar i att echo körs.

Sista nyckelord

Def: Final Key förhindrar barnklasser från att åsidosätta en metod genom att prefixera definitionen med final. Om klassen definieras som slutlig kan den inte förlängas

Slutlig metod

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

Slutklass:

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)

Slutkonstanter: Till skillnad från Java används inte det final nyckelordet för klasskonstanter i PHP. Använd istället nyckelordet const .

Varför måste jag använda final ?

  1. Förhindra massiv arvskedja av undergång
  2. Uppmuntrande sammansättning
  3. Tvinga utvecklaren att tänka på användarens offentliga API
  4. Tvinga utvecklaren att krympa ett objekts offentliga API
  5. En final klass kan alltid göras töjbar
  6. extends brytningens inkapsling
  7. Du behöver inte den flexibiliteten
  8. Du är fri att ändra koden

När ska man undvika final : Slutklasser fungerar bara effektivt under följande antaganden:

  1. Det finns en abstraktion (gränssnitt) som den slutliga klassen implementerar
  2. Allt det offentliga API: et för den slutliga klassen är en del av det gränssnittet

$ detta, själv och statisk plus singleton

Använd $this att referera till det aktuella objektet. Använd self att hänvisa till den aktuella klassen. Med andra ord, använd $this->member för icke-statiska medlemmar, använd self::$member för statiska medlemmar.

I exemplet nedan sayHello() och sayGoodbye() self och $this skillnad kan observeras här.

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 hänvisar till vilken klass i hierarkin du kallade metoden på. Det möjliggör bättre återanvändning av statiska klassegenskaper när klasser ärvs.

Tänk på följande 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();

Detta ger inte det resultat du vill:

okänd
okänd
okänd

Det beror på att self hänvisar till Car när brand() kallas.

För att hänvisa till rätt klass måste du använda static istället:

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

Detta ger den önskade utgången:

okänd
BMW
Mercedes

Se även Sen statisk bindning

Singleton

Om du har ett objekt som är dyrt att skapa eller representerar en anslutning till någon extern resurs som du vill återanvända, dvs en databasanslutning där det inte finns någon anslutningssamling eller ett uttag till något annat system, kan du använda de static och self nyckelorden i ett klass för att göra det till en singleton. Det finns starka åsikter om huruvida singletonmönstret ska eller inte ska användas, men det har dess användningsområden.

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

Som du ser i exemplskoden definierar vi en privat statisk egenskap $instance att hålla objektreferensen. Eftersom detta är statiskt delas denna referens över ALLA objekt av denna typ.

getInstance() använder en metod som är känd som lat inställning för att försena att skapa objektet till sista möjliga ögonblick eftersom du inte vill ha oanvända objekt som ligger i minnet aldrig avsett att användas. Det sparar också tid och CPU på sidbelastning att inte behöva ladda fler objekt än nödvändigt. Metoden kontrollerar om objektet är inställt, skapar det om inte, och returnerar det. Detta säkerställer att endast ett objekt av detta slag någonsin skapas.

Vi ställer också in konstruktören som privat för att se till att ingen skapar den med det new nyckelordet från utsidan. Om du behöver ärva från den här klassen, ändra bara de private nyckelorden till protected .

För att använda detta objekt skriver du bara följande:

$singleton = Singleton::getInstance();

Nu uppmanar jag er att använda beroendeinjektion där man kan och sträva efter löst kopplade objekt, men ibland är det bara inte rimligt och singletonmönstret kan vara användbart.

självladdande

Ingen vill require eller include varje gång en klass eller arv används. Eftersom det kan vara smärtsamt och lätt att glömma, erbjuder PHP så kallad autoloading. Om du redan använder Composer kan du läsa om autoloading med Composer .

Vad är autoladning exakt?

Namnet säger i princip allt. Du behöver inte få filen där den begärda klassen lagras i, men PHP auto matiskt belastning är det.

Hur kan jag göra detta i grundläggande PHP utan tredje parts kod?

Det finns funktionen __autoload , men det anses vara bättre praxis att använda spl_autoload_register . Dessa funktioner kommer att beaktas av PHP varje gång en klass inte definieras inom det givna utrymmet. Så att lägga till autoload till ett befintligt projekt är inget problem, eftersom definierade klasser (via require dvs) kommer att fungera som tidigare. För precisens skull kommer följande exempel att använda anonyma funktioner, om du använder PHP <5.3 kan du definiera funktionen och skicka dess namn som argument till spl_autoload_register .

exempel

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

Koden ovan försöker helt enkelt att inkludera ett filnamn med klassnamnet och det bifogade tillägget ".php" med sprintf . Om FooBar måste laddas ser det ut om FooBar.php finns och om så är inkluderat.

Naturligtvis kan detta utvidgas för att passa projektets individuella behov. Om _ inuti en klassnamn används för att gruppera, t.ex. User_Post och User_Image båda hänvisar till User kan båda klasserna hållas i en mapp som heter "User" så här:

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

Klassen User_Post kommer nu att laddas från "User / Post.php", etc.

spl_autoload_register kan anpassas efter olika behov. Alla dina filer med klasser heter "class.CLASSNAME.php"? Inga problem. Olika häckningar ( User_Post_Content => "User / Post / Content.php")? Inga problem heller.

Om du vill ha en mer detaljerad autoladningsmekanism - och fortfarande inte vill inkludera Composer - kan du arbeta utan att lägga till tredjepartsbibliotek.

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

Om du använder autoloaders som detta kan du gärna skriva kod som så här:

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

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

Använda klasser:

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

Dessa exempel kommer att inkludera klasser från foo/bar.php , foo.php , hello/world.php och demo/classes.php .

Anonyma klasser

Anonyma klasser introducerades i PHP 7 för att möjliggöra att snabba enstaka objekt enkelt kunde skapas. De kan ta konstruktörargument, utöka andra klasser, implementera gränssnitt och använda drag precis som vanliga klasser kan.

I sin mest grundläggande form ser en anonym klass ut enligt följande:

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

Att häcka en anonym klass i en annan klass ger den inte tillgång till privata eller skyddade metoder eller egenskaper hos den yttre klassen. Tillgång till skyddade metoder och egenskaper hos den yttre klassen kan fås genom att utvidga den yttre klassen från den anonyma klassen. Tillgång till privata fastigheter i den yttre klassen kan fås genom att överföra dem till den anonyma klassens konstruktör.

Till exempel:

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

Definiera en grundklass

Ett objekt i PHP innehåller variabler och funktioner. Objekt tillhör vanligtvis en klass, som definierar variabler och funktioner som alla objekt i denna klass kommer att innehålla.

Syntaxen för att definiera en klass är:

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

När en klass har definierats kan du skapa en instans med:

$myShape = new Shape();

Variabler och funktioner på objektet öppnas på följande sätt:

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

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

Konstruktör

Klasser kan definiera en speciell __construct() -metod, som körs som en del av att skapa objekt. Det här används ofta för att ange de initiala värdena för ett objekt:

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

Utöka en annan klass

Klassdefinitioner kan utöka befintliga klassdefinitioner, lägga till nya variabler och funktioner samt ändra de som definieras i moderklassen.

Här är en klass som utvidgar föregående exempel:

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

Square klassen innehåller variabler och beteende för både Shape klassen och Square klassen:

$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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow