PHP
Klassen en objecten
Zoeken…
Invoering
Klassen en objecten worden gebruikt om uw code efficiënter en minder repetitief te maken door vergelijkbare taken te groeperen.
Een klasse wordt gebruikt om de acties en gegevensstructuur te definiëren die wordt gebruikt om objecten te bouwen. De objecten worden vervolgens gebouwd met behulp van deze vooraf gedefinieerde structuur.
Syntaxis
-
class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { }
// Class verklaring -
interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { }
// Interface verklaring uit -
use <Trait1> [, <Trait2>, ...]
; // Gebruik eigenschappen -
[ public | protected | private ] [ static ] $<varName>;
// Attribuutverklaring -
const <CONST_NAME>;
// Constante verklaring -
[ public | protected | private ] [ static ] function <methodName>([args...]) { }
// Methodeverklaring
Opmerkingen
Klassen en interfacecomponenten
Klassen kunnen eigenschappen, constanten en methoden hebben.
- Eigenschappen bevatten variabelen binnen het bereik van het object. Ze kunnen worden geïnitialiseerd bij declaratie, maar alleen als ze een primitieve waarde bevatten.
- Constanten moeten bij declaratie worden geïnitialiseerd en kunnen alleen een primitieve waarde bevatten. Constante waarden worden vastgesteld tijdens het compileren en kunnen niet worden toegewezen tijdens runtime.
- Methoden moeten een lichaam hebben, zelfs een lege, tenzij de methode abstract wordt verklaard.
class Foo {
private $foo = 'foo'; // OK
private $baz = array(); // OK
private $bar = new Bar(); // Error!
}
Interfaces kunnen geen eigenschappen hebben, maar wel constanten en methoden.
- Interface constanten worden geïnitialiseerd voor verklaring en slechts een primitieve waarde bevatten. Constante waarden worden vastgesteld tijdens het compileren en kunnen niet worden toegewezen tijdens runtime.
- Interface methoden hebben geen lichaam.
interface FooBar {
const FOO_VALUE = 'bla';
public function doAnything();
}
interfaces
Invoering
Interfaces zijn definities van de openbare API-klassen die moeten worden geïmplementeerd om aan de interface te voldoen. Ze werken als "contracten" en specificeren wat een set subklassen doet, maar niet hoe ze het doen.
Interface definitie is veel op elkaar lijken definitie van de klasse, het veranderen van het trefwoord class
tot interface
:
interface Foo {
}
Interfaces kunnen methoden en / of constanten bevatten, maar geen attributen. Interfaceconstanten hebben dezelfde beperkingen als klasseconstanten. Interfacemethoden zijn impliciet abstract:
interface Foo {
const BAR = 'BAR';
public function doSomething($param1, $param2);
}
Opmerking: interfaces mogen geen constructors of destructors declareren, omdat dit implementatiedetails op klassenniveau zijn.
Realisatie
Elke klasse die een interface moet implementeren, moet dit doen met behulp van het sleutelwoord implements
. Om dit te doen, moet de klasse een implementatie bieden voor elke methode die in de interface wordt aangegeven, met dezelfde handtekening.
Een enkele klasse kan meer dan één interface tegelijk implementeren.
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) {
// ...
}
}
Wanneer abstracte klassen interfaces implementeren, hoeven ze niet alle methoden te implementeren. Elke methode die niet in de basisklasse is geïmplementeerd, moet vervolgens worden geïmplementeerd door de concrete klasse die deze uitbreidt:
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) {
// ...
}
}
Merk op dat interface-realisatie een erfelijke eigenschap is. Bij het uitbreiden van een klasse die een interface implementeert, hoeft u deze niet opnieuw in de concrete klasse te herdefiniëren, omdat deze impliciet is.
Opmerking: Vóór PHP 5.3.9 kon een klasse geen twee interfaces implementeren die een methode met dezelfde naam specificeerden, omdat dit dubbelzinnigheid zou veroorzaken. Meer recente versies van PHP staan dit toe zolang de dubbele methoden dezelfde handtekening hebben [1] .
Erfenis
Zoals klassen, is het mogelijk om een overervingsrelatie tussen interfaces bestaan, met dezelfde zoekwoord extends
. Het belangrijkste verschil is dat meervoudige overerving is toegestaan voor interfaces:
interface Foo {
}
interface Bar {
}
interface Baz extends Foo, Bar {
}
Voorbeelden
In het onderstaande voorbeeld hebben we een eenvoudige voorbeeldinterface voor een voertuig. Voertuigen kunnen vooruit en achteruit rijden.
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() {
...
}
...
}
Vervolgens maken we twee klassen die de interface implementeren: Bike en Car. Fiets en auto zijn intern heel verschillend, maar beide zijn voertuigen en moeten dezelfde openbare methoden implementeren die VehicleInterface biedt.
Met Typehinting kunnen methoden en functies Interfaces aanvragen. Laten we aannemen dat we een klasse parkeergarages hebben, die allerlei soorten voertuigen bevat.
class ParkingGarage {
protected $vehicles = [];
public function addVehicle(VehicleInterface $vehicle) {
$this->vehicles[] = $vehicle;
}
}
Omdat addVehicle
een $vehicle
van het type VehicleInterface
vereist - geen concrete implementatie - kunnen we zowel fietsen als auto's invoeren, die de ParkingGarage kan manipuleren en gebruiken.
Klasseconstanten
Klasseconstanten bieden een mechanisme voor het vasthouden van vaste waarden in een programma. Dat wil zeggen, ze bieden een manier om een naam (en bijbehorende compilatiecontrole) te geven aan een waarde zoals 3.14
of "Apple"
. Klasseconstanten kunnen alleen worden gedefinieerd met het sleutelwoord const
- de definieerfunctie kan in deze context niet worden gebruikt.
Het kan bijvoorbeeld handig zijn om een steno-weergave te hebben voor de waarde van π in een programma. Een klasse met const
waarden biedt een eenvoudige manier om dergelijke waarden vast te houden.
class MathValues {
const PI = M_PI;
const PHI = 1.61803;
}
$area = MathValues::PI * $radius * $radius;
Klasseconstanten kunnen worden geopend met behulp van de dubbele-puntoperator (de zogenaamde scope-resolutieoperator) op een klasse, net als statische variabelen. In tegenstelling tot statische variabelen, hebben MathValues::PI = 7
echter hun waarden vast tijdens het compileren en kunnen ze niet opnieuw worden toegewezen (bijv. MathValues::PI = 7
zou een fatale fout veroorzaken).
Klasseconstanten zijn ook handig voor het definiëren van interne dingen in een klasse die mogelijk later moeten worden gewijzigd (maar niet vaak genoeg veranderen om opslag in bijvoorbeeld een database te rechtvaardigen). We kunnen hier intern naar verwijzen met behulp van de self
scope resolutor (die in zowel instanced als statische implementaties werkt)
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;
}
}
Klasseconstanten kunnen alleen scalaire waarden bevatten in versies <5.6
Vanaf PHP 5.6 kunnen we uitdrukkingen met constanten gebruiken, wat betekent dat wiskundige verklaringen en tekenreeksen met aaneenschakeling acceptabele constanten zijn
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;
}
}
Vanaf PHP 7.0 kunnen constanten die met define
worden gedeclareerd nu arrays bevatten.
define("BAZ", array('baz'));
Klasseconstanten zijn nuttig voor meer dan alleen het opslaan van wiskundige concepten. Als u bijvoorbeeld een taart bereidt, kan het handig zijn om een enkele Pie
te hebben die verschillende soorten fruit kan nemen.
class Pie {
protected $fruit;
public function __construct($fruit) {
$this->fruit = $fruit;
}
}
We kunnen de Pie
klasse dan zo gebruiken
$pie = new Pie("strawberry");
Het probleem dat zich hier voordoet, is dat bij het instantiëren van de klasse Pie
geen richtlijnen worden gegeven voor de aanvaardbare waarden. Bij het maken van een "boysenberry" -taart kan deze bijvoorbeeld "boisenberry" verkeerd worden gespeld. Bovendien ondersteunen we een pruimentaart misschien niet. In plaats daarvan zou het nuttig zijn om een lijst met acceptabele fruitsoorten te hebben die al ergens zijn gedefinieerd, het zou logisch zijn om ernaar te zoeken. Zeg een klas met de naam Fruit
:
class Fruit {
const APPLE = "apple";
const STRAWBERRY = "strawberry";
const BOYSENBERRY = "boysenberry";
}
$pie = new Pie(Fruit::STRAWBERRY);
Het vermelden van de acceptabele waarden als klasseconstanten geeft een waardevolle hint over de acceptabele waarden die een methode accepteert. Het zorgt er ook voor dat spelfouten niet voorbij de compiler kunnen komen. Terwijl new Pie('aple')
en new Pie('apple')
beide aanvaardbaar zijn voor de compiler, zal new Pie(Fruit::APLE)
een compilerfout veroorzaken.
Ten slotte betekent het gebruik van klasseconstanten dat de werkelijke waarde van de constante op één plaats kan worden gewijzigd, en elke code die de constante gebruikt, heeft automatisch het effect van de wijziging.
Hoewel de meest gebruikelijke methode om toegang te krijgen tot een klassenconstante MyClass::CONSTANT_NAME
, kan deze ook worden gebruikt door:
echo MyClass::CONSTANT;
$classname = "MyClass";
echo $classname::CONSTANT; // As of PHP 5.3.0
Klasseconstanten in PHP worden conventioneel allemaal in hoofdletters genoemd met onderstrepingstekens als woordscheidingstekens, hoewel elke geldige labelnaam kan worden gebruikt als een klasseconstante naam.
Vanaf PHP 7.1 kunnen klasseconstanten nu worden gedefinieerd met andere zichtbaarheden dan het standaard openbare bereik. Dit betekent dat zowel beveiligde als private constanten nu kunnen worden gedefinieerd om te voorkomen dat klasseconstanten onnodig in het publieke bereik lekken (zie Methode en eigenschapzichtbaarheid ). Bijvoorbeeld:
class Something {
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
definieer versus klasse constanten
Hoewel dit een geldige constructie is:
function bar() { return 2; };
define('BAR', bar());
Als u hetzelfde probeert te doen met klasseconstanten, krijgt u een foutmelding:
function bar() { return 2; };
class Foo {
const BAR = bar(); // Error: Constant expression contains invalid operations
}
Maar u kunt doen:
function bar() { return 2; };
define('BAR', bar());
class Foo {
const BAR = BAR; // OK
}
Zie constanten in de handleiding voor meer informatie.
Klasse gebruiken om de naam van de klasse op te halen
PHP 5.5 introduceerde de ::class
syntax om de volledige klassenaam op te halen, rekening houdend met het bereik van de naamruimte en het use
statements.
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"
Het bovenstaande werkt zelfs als de klassen niet eens zijn gedefinieerd (dit codefragment werkt alleen).
Deze syntaxis is handig voor functies die een klassenaam vereisen. Het kan bijvoorbeeld worden gebruikt met class_exists
om te controleren of er een klasse bestaat. Er worden geen fouten gegenereerd, ongeacht de retourwaarde in dit fragment:
class_exists(ThisClass\Will\NeverBe\Loaded::class, false);
Late statische binding
In PHP 5.3+ en hoger kunt u late statische binding gebruiken om te bepalen vanuit welke klasse een statische eigenschap of methode wordt aangeroepen. Het is toegevoegd om het probleem te verhelpen dat inherent is aan de self::
scope resolutor. Neem de volgende 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!';
}
}
Je zou verwachten dat de MrEd
klasse de bovenliggende functie whatToSay()
overschrijft. Maar als we dit uitvoeren, krijgen we iets onverwachts
Horse::speak(); // Neigh!
MrEd::speak(); // Neigh!
Het probleem is dat self::whatToSay();
kan alleen verwijzen naar de klasse Horse
, wat betekent dat hij MrEd
niet gehoorzaamt. Als we overschakelen naar de static::
scope resolutor, hebben we dit probleem niet. Deze nieuwere methode vertelt de klas om de instantie te gehoorzamen die het noemt. Zo krijgen we de erfenis die we verwachten
class Horse {
public static function whatToSay() {
echo 'Neigh!';
}
public static function speak() {
static::whatToSay(); // Late Static Binding
}
}
Horse::speak(); // Neigh!
MrEd::speak(); // Hello Wilbur!
Abstracte klassen
Een abstracte klasse is een klasse die niet kan worden geïnstantieerd. Abstracte klassen kunnen abstracte methoden definiëren, dit zijn methoden zonder enige body, alleen een definitie:
abstract class MyAbstractClass {
abstract public function doSomething($a, $b);
}
Abstracte klassen moeten worden uitgebreid met een kindklasse die vervolgens de implementatie van deze abstracte methoden kan bieden.
Het hoofddoel van een klas als deze is om een soort sjabloon te bieden waarmee kinderen klassen kunnen erven, "dwingen" van een structuur om zich aan te houden. Laten we dit toelichten met een voorbeeld:
In dit voorbeeld zullen we een Worker
interface implementeren. Eerst definiëren we de interface:
interface Worker {
public function run();
}
Om de ontwikkeling van verdere Worker-implementaties te vergemakkelijken, zullen we een abstracte werkerklasse maken die de methode run()
vanuit de interface biedt, maar enkele abstracte methoden specificeert die door elke onderliggende klasse moeten worden ingevuld:
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();
}
Allereerst hebben we een abstracte methode getMemoryLimit()
. Elke klasse die zich uitstrekt van AbstractWorker
moet deze methode bieden en de geheugenlimiet retourneren. De AbstractWorker
stelt vervolgens de geheugenlimiet in en registreert deze.
Ten tweede roept de AbstractWorker
de prepareMain()
en main()
aan, nadat ze hebben prepareMain()
dat ze zijn aangeroepen.
Ten slotte zijn al deze methodeaanroepen gegroepeerd in een try
- catch
blok. Dus als een van de abstracte methoden die door de child class zijn gedefinieerd een uitzondering genereert, zullen we die uitzondering opvangen, vastleggen en opnieuw indelen. Dit voorkomt dat alle kindklassen dit zelf moeten implementeren.
Laten we nu een onderliggende klasse definiëren die zich uitstrekt van de 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();
}
}
}
Zoals u kunt zien, was de TransactionProcessorWorker
vrij eenvoudig te implementeren, omdat we alleen de geheugenlimiet moesten opgeven en ons zorgen moesten maken over de daadwerkelijke acties die het moest uitvoeren. Geen foutafhandeling nodig in de TransactionProcessorWorker
omdat dat wordt afgehandeld in de AbsractWorker
.
Belangrijke notitie
Bij het erven van een abstracte klasse, moeten alle methoden die in de klasseverklaring van de ouder als abstract zijn gemarkeerd, door het kind worden gedefinieerd (of het kind zelf moet ook als abstract worden gemarkeerd); bovendien moeten deze methoden worden gedefinieerd met dezelfde (of een minder beperkte) zichtbaarheid. Als de abstracte methode bijvoorbeeld is gedefinieerd als beveiligd, moet de functie-implementatie worden gedefinieerd als beveiligd of openbaar, maar niet als privé.
Genomen uit de PHP-documentatie voor Klasse-abstractie .
Als u de methoden voor de bovenliggende abstracte klassen niet definieert binnen de onderliggende klasse, wordt er een fatale PHP-fout gegenereerd zoals hieronder.
Fatale fout: Klasse X bevat 1 abstracte methode en moet daarom abstract worden verklaard of de resterende methoden (X :: x) in
Naamruimten en automatisch laden
Technisch gezien werkt autoloading door een callback uit te voeren wanneer een PHP-klasse vereist is maar niet wordt gevonden. Dergelijke callbacks proberen meestal deze klassen te laden.
Over het algemeen kan autoloading worden opgevat als de poging om PHP-bestanden (met name PHP-klassebestanden, waarbij een PHP-bronbestand is toegewezen voor een specifieke klasse) te laden vanuit geschikte paden volgens de volledig gekwalificeerde naam (FQN) van de klasse wanneer een klasse nodig is .
Stel dat we deze klassen hebben:
Klassebestand voor application\controllers\Base
:
<?php
namespace application\controllers { class Base {...} }
Klassebestand voor application\controllers\Control
:
<?php
namespace application\controllers { class Control {...} }
Klassebestand voor application\models\Page
:
<?php
namespace application\models { class Page {...} }
Onder de bronmap moeten deze klassen respectievelijk als FQN's bij de paden worden geplaatst:
- Bronmap
-
applications
-
controllers
-
Base.php
-
Control.php
-
-
models
-
Page.php
-
-
-
Deze benadering maakt het mogelijk om het klassebestandspad programmatisch op te lossen volgens de FQN, met behulp van deze functie:
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
}
Met de functie spl_autoload_register
kunnen we een klasse laden wanneer dat nodig is met behulp van een door de gebruiker gedefinieerde functie:
const SOURCE_FOLDER = __DIR__ . "/src";
spl_autoload_register(function (string $className) {
$file = getClassPath(SOURCE_FOLDER, $className);
if (is_readable($file)) require_once $file;
});
Deze functie kan verder worden uitgebreid om fallback-methoden voor het laden te gebruiken:
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;
}
}
});
Merk op dat PHP niet probeert om de klassen te laden wanneer een bestand dat deze klasse gebruikt wordt geladen. Het kan worden geladen in het midden van een script, of zelfs in afsluitfuncties. Dit is een van de redenen waarom ontwikkelaars, met name degenen die autoloading gebruiken, moeten voorkomen dat ze tijdens de uitvoering de bronbestanden moeten vervangen, vooral in phar-bestanden.
Dynamische binding
Dynamische binding, ook wel methode-overriding genoemd, is een voorbeeld van runtime-polymorfisme dat optreedt wanneer meerdere klassen verschillende implementaties van dezelfde methode bevatten, maar het object dat de methode zal worden aangeroepen is onbekend tot runtime .
Dit is handig als een bepaalde voorwaarde voorschrijft welke klasse wordt gebruikt om een actie uit te voeren, waarbij de actie in beide klassen hetzelfde wordt genoemd.
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();
In het bovenstaande voorbeeld is de klasse Animal
( Dog|Cat
) die makeNoise
maakt onbekend tot uitvoeringstijd, afhankelijk van de eigenschap binnen de klasse User
.
Zichtbaarheid van methode en eigenschap
Er zijn drie zichtbaarheidstypen die u kunt toepassen op methoden ( klasse / objectfuncties ) en eigenschappen ( klasse / objectvariabelen ) binnen een klasse, die toegangscontrole bieden voor de methode of eigenschap waarop ze worden toegepast.
U kunt hier uitgebreid over lezen in de PHP-documentatie voor OOP Visibility .
Openbaar
Als een methode of eigenschap public
kan de methode of eigenschap worden geopend door:
- De klas die het heeft verklaard.
- De klassen die de aangegeven klasse uitbreiden.
- Externe objecten, klassen of code buiten de klassenhiërarchie.
Een voorbeeld van deze public
toegang zou zijn:
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
beschermde
Door een methode of eigenschap als protected
kan de methode of eigenschap worden benaderd door:
- De klas die het heeft verklaard.
- De klassen die de aangegeven klasse uitbreiden.
Dit heeft geen externe objecten, klassen, of code buiten de klasse hiërarchie toegang te geven tot deze methoden of eigenschappen. Als iets dat deze methode / eigenschap gebruikt er geen toegang toe heeft, is het niet beschikbaar en wordt een fout gegenereerd. Alleen instanties van de aangegeven persoon (of subklassen daarvan) hebben hier toegang toe.
Een voorbeeld van deze protected
toegang zou zijn:
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 ''
In het bovenstaande voorbeeld wordt opgemerkt dat u alleen toegang hebt tot de protected
elementen binnen het eigen bereik. In wezen: "Wat er in het huis is, kan alleen vanuit het huis toegankelijk zijn."
Privaat
Door een methode of eigenschap private
verklaren, kan de methode of eigenschap worden benaderd door:
- De klasse die het alleen heeft verklaard (geen subklassen).
Een private
methode of eigenschap is alleen zichtbaar en toegankelijk binnen de klasse die het heeft aangemaakt.
Merk op dat objecten van hetzelfde type toegang hebben tot elkaars privé- en beschermde leden, ook al zijn dit niet dezelfde instanties.
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'
Zoals vermeld, kunt u alleen toegang tot de private
methode / eigendom van binnenuit het is gedefinieerde klasse.
Een ouderconstructeur bellen bij het instantiëren van een kind
Een veel voorkomende valkuil van onderliggende klassen is dat, als uw ouder en kind beide een constructor ( __construct()
) -methode bevatten, alleen de constructor van de onderliggende klasse wordt uitgevoerd . Er kunnen zich situaties __construct()
waarin u de methode ouder __construct()
moet uitvoeren vanaf het onderliggende __construct()
. Als u dat moet doen, moet u de parent::
scope resolutor gebruiken:
parent::__construct();
Nu het benutten van dat binnen een echte situatie er ongeveer zo uit zou zien:
class Foo {
function __construct($args) {
echo 'parent';
}
}
class Bar extends Foo {
function __construct($args) {
parent::__construct($args);
}
}
Bovenstaande zal de bovenliggende __construct()
waardoor de echo
wordt uitgevoerd.
Laatste trefwoord
Def: Definitief sleutelwoord voorkomt dat onderliggende klassen een methode overschrijven door de definitie te prefixen met final. Als de klasse zelf definitief wordt gedefinieerd, kan deze niet worden verlengd
Laatste methode
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()
Laatste Klasse:
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)
Laatste constanten: in tegenstelling tot Java wordt het final
trefwoord niet gebruikt voor klasseconstanten in PHP. Gebruik in plaats daarvan het trefwoord const
.
Waarom moet ik final
?
- Het voorkomen van een enorme erfenis van doom
- Samenstelling aanmoedigen
- Dwing de ontwikkelaar om na te denken over de openbare API van de gebruiker
- Dwing de ontwikkelaar om de openbare API van een object te verkleinen
- Een
final
les kan altijd uitbreidbaar worden gemaakt -
extends
inkapseling breekt uit - Die flexibiliteit heb je niet nodig
- U bent vrij om de code te wijzigen
Wanneer final
te vermijden: eindklassen werken alleen effectief onder de volgende veronderstellingen:
- Er is een abstractie (interface) die de laatste klasse implementeert
- Alle openbare API's van de laatste klasse maken deel uit van die interface
$ this, self and static plus de singleton
Gebruik
$this
om naar het huidige object te verwijzen. Gebruikself
om te verwijzen naar de huidige klasse. Met andere woorden, gebruik$this->member
voor niet-statische leden, gebruikself::$member
voor statische leden.
In het onderstaande voorbeeld gebruiken sayHello()
en sayGoodbye()
self
en $this
verschil hier worden waargenomen.
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
verwijst naar elke klasse in de hiërarchie waarop u de methode hebt aangeroepen. Het zorgt voor een beter hergebruik van statische klasse-eigenschappen wanneer klassen worden geërfd.
Overweeg de volgende 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();
Dit levert niet het gewenste resultaat op:
onbekend
onbekend
onbekend
Dat komt omdat self
verwijst naar de Car
wanneer brand()
wordt aangeroepen.
Om naar de juiste klasse te verwijzen, moet u in plaats daarvan static
gebruiken:
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();
Dit levert wel de gewenste uitvoer op:
onbekend
BMW
Mercedes
Zie ook Late statische binding
De singleton
Als u een object hebt dat duur is om te maken of een verbinding vertegenwoordigt met een externe bron die u opnieuw wilt gebruiken, dat wil zeggen een databaseverbinding waarbij er geen pooling van verbindingen of een socket met een ander systeem is, kunt u de static
en self
in een klasse om er een singleton van te maken. Er zijn sterke meningen over de vraag of het singleton-patroon wel of niet moet worden gebruikt, maar het heeft wel zijn nut.
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
}
}
Zoals u in de voorbeeldcode kunt zien, definiëren we een privaat statisch eigenschap $instance
om de objectreferentie te bevatten. Omdat dit statisch is, wordt deze referentie gedeeld over ALLE objecten van dit type.
De methode getInstance()
gebruikt een methode die bekend staat als luie instantiëring om het maken van het object tot het laatst mogelijke moment uit te stellen, omdat u niet wilt dat ongebruikte objecten in het geheugen rondslingeren die nooit bedoeld zijn om te worden gebruikt. Het bespaart ook tijd en CPU bij het laden van pagina's waarbij niet meer objecten hoeven te worden geladen dan nodig. De methode controleert of het object is ingesteld, maakt het zo niet en retourneert het. Dit zorgt ervoor dat slechts één van dit soort objecten ooit wordt gemaakt.
We stellen de constructor ook privé in om ervoor te zorgen dat niemand het van buitenaf met het new
trefwoord maakt. Als u erven van deze klasse verander gewoon de private
trefwoorden om protected
.
Om dit object te gebruiken schrijf je gewoon het volgende:
$singleton = Singleton::getInstance();
Nu smeek ik je om afhankelijkheidsinjectie te gebruiken waar je kunt en te streven naar losjes gekoppelde objecten, maar soms is dat gewoon niet redelijk en kan het singleton-patroon nuttig zijn.
autoloading
Niemand wil require
of include
elke keer dat een klasse of erfenis wordt gebruikt. Omdat het pijnlijk kan zijn en gemakkelijk te vergeten is, biedt PHP zogenaamde autoloading aan. Als u Composer al gebruikt, lees dan over automatisch laden met Composer .
Wat is automatisch laden?
De naam zegt eigenlijk alles. Je hoeft niet naar het bestand waarin de gevraagde klasse wordt opgeslagen in te krijgen, maar PHP auto tisch belasting is het.
Hoe kan ik dit doen in standaard PHP zonder code van derden?
Er is de functie __autoload
, maar het wordt als een betere praktijk beschouwd om spl_autoload_register
te gebruiken. Deze functies zullen door PHP worden overwogen telkens wanneer een klasse niet binnen de gegeven ruimte wordt gedefinieerd. Dus het toevoegen van autoload aan een bestaand project is geen probleem, omdat gedefinieerde klassen (via require
ie) zullen werken als voorheen. Omwille van de nauwkeurigheid zullen de volgende voorbeelden anonieme functies gebruiken, als u PHP <5.3 gebruikt, kunt u de functie definiëren en de naam ervan als argument spl_autoload_register
aan spl_autoload_register
.
Voorbeelden
spl_autoload_register(function ($className) {
$path = sprintf('%s.php', $className);
if (file_exists($path)) {
include $path;
} else {
// file not found
}
});
De bovenstaande code probeert eenvoudig een bestandsnaam op te nemen met de klassenaam en de toegevoegde extensie ".php" met behulp van sprintf
. Als FooBar
moet worden geladen, kijkt het of FooBar.php
bestaat en zo ja.
Natuurlijk kan dit worden uitgebreid om aan de individuele behoefte van het project te voldoen. Als _
binnen een klassenaam wordt gebruikt om te groeperen, bijv. User_Post
en User_Image
verwijzen beide naar User
, beide klassen kunnen zo worden bewaard in een map met de naam "Gebruiker":
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
}
});
De klasse User_Post
wordt nu geladen uit "User / Post.php", enz.
spl_autoload_register
kan worden aangepast aan verschillende behoeften. Al uw bestanden met klassen heten "class.CLASSNAME.php"? Geen probleem. Verschillende nesten ( User_Post_Content
=> "User / Post / Content.php")? Ook geen probleem.
Als u een uitgebreider autoloading-mechanisme wilt - en nog steeds geen Composer wilt opnemen - kunt u werken zonder externe bibliotheken toe te voegen.
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
)
);
}
});
Met behulp van autoloaders zoals deze, kun je zo code schrijven zoals deze:
require_once './autoload.php'; // where spl_autoload_register is defined
$foo = new Foo_Bar(new Hello_World());
Klassen gebruiken:
class Foo_Bar extends Foo {}
class Hello_World implements Demo_Classes {}
Deze voorbeelden zijn klassen van foo/bar.php
, foo.php
, hello/world.php
en demo/classes.php
.
Anonieme lessen
Anonieme klassen werden geïntroduceerd in PHP 7 om snel eenmalige objecten eenvoudig te maken. Ze kunnen constructorargumenten aannemen, andere klassen uitbreiden, interfaces implementeren en eigenschappen gebruiken, net als normale klassen.
In de meest basale vorm ziet een anonieme klasse er als volgt uit:
new class("constructor argument") {
public function __construct($param) {
var_dump($param);
}
}; // string(20) "constructor argument"
Het nestelen van een anonieme klasse in een andere klasse geeft deze geen toegang tot privé- of beschermde methoden of eigenschappen van die buitenklasse. Toegang tot beschermde methoden en eigenschappen van de buitenklasse kan worden verkregen door de buitenklasse uit te breiden van de anonieme klasse. Toegang tot privé-eigendommen van de buitenklasse kan worden verkregen door deze door te geven aan de constructeur van de anonieme klasse.
Bijvoorbeeld:
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
Een basisklasse definiëren
Een object in PHP bevat variabelen en functies. Objecten behoren meestal tot een klasse, die de variabelen en functies definieert die alle objecten van deze klasse zullen bevatten.
De syntaxis om een klasse te definiëren is:
class Shape {
public $sides = 0;
public function description() {
return "A shape with $this->sides sides.";
}
}
Nadat een klasse is gedefinieerd, kunt u een instantie maken met:
$myShape = new Shape();
Variabelen en functies op het object zijn als volgt toegankelijk:
$myShape = new Shape();
$myShape->sides = 6;
print $myShape->description(); // "A shape with 6 sides"
bouwer
Klassen kunnen een speciale __construct()
-methode definiëren, die wordt uitgevoerd als onderdeel van het maken van objecten. Dit wordt vaak gebruikt om de beginwaarden van een object op te geven:
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
Een andere klasse uitbreiden
Klasdefinities kunnen bestaande klassedefinities uitbreiden, nieuwe variabelen en functies toevoegen en de in de bovenliggende klasse gedefinieerde wijzigen.
Hier is een klasse die het vorige voorbeeld uitbreidt:
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;
}
}
De klasse Square
bevat variabelen en gedrag voor zowel de klasse Shape
als de klasse Square
:
$mySquare = new Square(10);
print $mySquare->description()/ // A shape with 4 sides
print $mySquare->perimeter() // 40
print $mySquare->area() // 100