Chapterبەش 6 · Lectureوانە 6.2
Inheritance, Interfaces and Traitsبۆماوە، نێوبەستەکان و ترەیتەکان
Building classes on other classes in PHP: extends and the "is a" test, parent::__construct(), overriding with parent:: and #[Override], protected, final, abstract classes, interfaces (implements, several at once), polymorphism through an interface, traits, enums (PHP 8.1) in brief, and an overview of namespaces, autoloading with spl_autoload_register() and Composer. A worked example builds a bazaar till that takes cash, card and mobile-wallet payments. Every example runs on PHP 8.2 and works the same on 8.3, 8.4 and 8.5.
بنیاتنانی لەسەر تر لە PHPـدا: extends و تاقیکردنەوەی «جۆرێکە لە»، parent::__construct()، بە parent:: و #[Override]، protected، final، ، (implements، چەندانێک پێکەوە)، لە ڕێگەی ، ، (PHP 8.1) بە کورتی و تێڕوانینێکی گشتی بۆ ، بە spl_autoload_register() و Composer. نموونەیەکی کارپێکراو شوێنی پارەدانێکی بازاڕ دروست دەکات کە پارەی نەختینە، کارت و جزدانی مۆبایل وەردەگرێت. هەموو نموونەیەک لەسەر PHP 8.2 کار دەکات و لەسەر 8.3، 8.4 و 8.5 هەمان شتە.
What you will learnچی فێر دەبیت
- Build a child class with extends, call parent::__construct() and override methods correctly.
- Choose between public, protected, private and final inside a class family.
- Write abstract classes and interfaces, and decide which one a problem needs.
- Use polymorphism through an interface so that new classes plug in without changing old code.
- Use a trait and a backed enum, and explain namespaces, autoloading and Composer.
- بە extends دروست بکەیت، parent::__construct() بانگ بکەیت و بە دروستی دووپاتە پێناسە بکەیتەوە.
- لە ناو خێزانێکی لە نێوان public، protected، private و final هەڵبژێریت.
- و بنووسیت و بڕیار بدەیت کێشەیەک کامیانی پێویستە.
- لە ڕێگەی بەکار بهێنیت بۆ ئەوەی نوێ بێ گۆڕینی کۆدی کۆن بچنە ناوەوە.
- و نرخدار بەکار بهێنیت و ، و Composer ڕوون بکەیتەوە.
Why inheritance?بۆچی بۆماوە؟
The bazaar shop now sells more than jars on a shelf. A gift card has a code; apples are sold by the kilo. Copying the whole Product class into GiftCard and FreshProduct would repeat the same code three times, and every fix would have to be made three times. solves this: a (child class) extends a (parent class) and receives its members, then adds or changes only what is different.
دووکانی بازاڕەکە ئێستا تەنیا قوتووی سەر ڕەفە نافرۆشێت. کارتی دیاری کۆدێکی هەیە؛ سێو بە کیلۆ دەفرۆشرێت. کۆپیکردنی هەموو Product بۆ ناو GiftCard و FreshProduct هەمان کۆد سێ جار دووپاتە دەکاتەوە و هەر چاککردنێک دەبێت سێ جار بکرێت. ئەمە چارەسەر دەکات: بە extends لە دروست دەبێت و بەشەکانی وەردەگرێت، پاشان تەنیا ئەوەی جیاوازە زیاد دەکات یان دەیگۆڕێت.
Use the "is a" test before you inherit: a gift card is a product, so GiftCard extends Product fits. A cart is not a product; it has products, so the cart of the last lesson holds Product objects instead (). The general idea is also taught in Java and Python; this lesson shows PHP's way.
پێش تاقیکردنەوەی «جۆرێکە لە» بەکار بهێنە: کارتی دیاری جۆرێکە لە بەرهەم، بۆیە GiftCard extends Product دەگونجێت. سەبەتە جۆرێک لە بەرهەم نییە؛ بەرهەمی تێدایە، بۆیە سەبەتەکەی وانەی پێشوو لەبری ئەوە Product هەڵدەگرێت (). بیرۆکە گشتییەکە لە Java و Pythonـیشدا دەخوێندرێت؛ ئەم وانەیە ڕێگای PHP پیشان دەدات.
extends: building on a parent classextends: بنیاتنان لەسەر کلاسێکی باوک
Write class GiftCard extends Product. The child receives every public and protected property and method of the parent. If the child has no constructor of its own, PHP uses the parent's, so new GiftCard("Bookshop card", 25000) fills name and price. A class may extend only one parent (single inheritance).
class GiftCard extends Product بنووسە. هەموو و public و protectedـی وەردەگرێت. ئەگەر خۆی نەبێت، PHP هی باوکەکە بەکار دەهێنێت، بۆیە new GiftCard("Bookshop card", 25000) name و price پڕ دەکاتەوە. تەنیا دەتوانێت لە یەک extends بکات ( تاک).
<?php
class Product
{
public function __construct(
public string $name,
protected int $price,
) {}
public function label(): string
{
return $this->name . ": " . number_format($this->price) . " IQD";
}
}
class GiftCard extends Product // a GiftCard is a Product
{
public function code(): string // a new method only gift cards have
{
return strtoupper(substr($this->name, 0, 3)) . "-" . $this->price;
}
}
$card = new GiftCard("Bookshop card", 25000); // uses Product's constructor
echo $card->label(), "\n"; // inherited from Product
echo $card->code(), "\n"; // its own method
var_dump($card instanceof Product);Bookshop card: 25,000 IQD BOO-25000 bool(true)
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6
protected: hidden from outside code, but open to child classes.protected: لە کۆدی دەرەوە شاراوەیە، بەڵام بۆ کراوەیە.- Lineدێڕی 15
extends Product: GiftCard gets name, price and label().extends Product: GiftCard name، price و label() وەردەگرێت.- Lineدێڕی 19
The child may use the protected
$pricein its own method.دەتوانێت
$priceـی protected لە خۆیدا بەکار بهێنێت.- Lineدێڕی 23-26
Product's constructor and label() work for a GiftCard;
instanceof Productis true.و label()ـی Product بۆ GiftCardـێک کار دەکەن؛
instanceof Productڕاستە.
extends: building on a parent classextends: بنیاتنان لەسەر کلاسێکی باوک
parent::__construct(): building the parent's partparent::__construct(): دروستکردنی بەشی کلاسی باوکەکە
FreshProduct needs its own constructor, because it takes a price per kilo and a weight. When a child writes its own constructor, PHP does not call the parent's by itself. Call it with parent::__construct(...), usually as the first line, so the Product part of the object is built. (Java calls super(...) automatically; PHP never does.)
FreshProduct خۆی پێویستە، چونکە نرخی کیلۆیەک و کێشێک وەردەگرێت. کاتێک خۆی دەنووسێت، PHP خۆی باوک بانگ ناکات. بە parent::__construct(...) بانگی بکە، زۆرجار وەک یەکەم دێڕ، بۆ ئەوەی بەشی Productـی دروست بێت. (Java خۆی super(...) بانگ دەکات؛ PHP هەرگیز.)
<?php
class Product
{
public function __construct(
public string $name,
protected int $price,
) {}
public function label(): string
{
return $this->name . ": " . number_format($this->price) . " IQD";
}
}
class FreshProduct extends Product // sold by the kilo
{
public function __construct(
string $name,
int $pricePerKg,
public float $kg,
) {
parent::__construct($name, (int) round($pricePerKg * $kg));
}
}
$apples = new FreshProduct("Apples", 3000, 1.5);
echo $apples->label(), "\n";Apples: 4,500 IQD
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 17-21
The child's own parameters;
$kgis promoted to a property.خودی ؛
$kgبەرز دەکرێتەوە بۆ .- Lineدێڕی 22
Send the name and the price (3000 × 1.5 = 4500) up to Product.
ناوەکە و نرخەکە (3000 × 1.5 = 4500) بۆ Product بنێرە.
Without that line, the Product part stays empty, and the first read of $this->name stops the script:
بەبێ ئەو دێڕە، بەشی Product بەتاڵ دەمێنێتەوە و یەکەم خوێندنەوەی $this->name ڕادەگرێت:
<?php
class Product
{
public function __construct(
public string $name,
protected int $price,
) {}
public function label(): string
{
return $this->name . ": " . number_format($this->price) . " IQD";
}
}
class FreshProduct extends Product
{
public function __construct(string $name, int $pricePerKg, public float $kg)
{
// parent::__construct(...) is missing
}
}
$apples = new FreshProduct("Apples", 3000, 1.5);
echo $apples->label(), "\n";Fatal error: Uncaught Error: Typed property Product::$name must not be accessed before initialization in C:\xampp\htdocs\shop\parent_construct_missing.php:11
Stack trace:
#0 C:\xampp\htdocs\shop\parent_construct_missing.php(24): Product->label()
#1 {main}
thrown in C:\xampp\htdocs\shop\parent_construct_missing.php on line 11Overriding: a new version of a methodپێناسەکردنەوەی میتۆد: وەشانێکی نوێی میتۆدێک
A child may write a method with the same name as one it inherits. This is : for a FreshProduct, its own label() runs instead of Product's. Inside it, parent::label() still reaches the parent's version, so the child can add to it instead of rewriting it. The new method must be compatible: the same name, parameters that still accept what the parent accepted, and a visibility that is the same or wider.
دەتوانێت بە هەمان ناوی ئەوەی بۆماوەی کردووە بنووسێت. ئەمە : بۆ FreshProductـێک، label()ـی خۆی لەبری هی Product جێبەجێ دەبێت. لە ناویدا parent::label() هێشتا دەگاتە وەشانی باوکەکە، بۆیە دەتوانێت شتی بۆ زیاد بکات لەبری ئەوەی دووپاتە بینووسێتەوە. نوێیەکە دەبێت گونجاو بێت: هەمان ناو، کە هێشتا ئەوە وەردەگرن کە باوکەکە وەریدەگرت و وەک خۆی یان فراوانتر.
label() دووپاتە پێناسە دەکاتەوەoverride.phpPHPRuns on the serverلەسەر ڕاژەکاری وێب کار دەکات<?php
class Product
{
public function __construct(
public string $name,
protected int $price,
) {}
public function label(): string
{
return $this->name . ": " . number_format($this->price) . " IQD";
}
}
class FreshProduct extends Product
{
public function __construct(string $name, int $pricePerKg, public float $kg)
{
parent::__construct($name, (int) round($pricePerKg * $kg));
}
#[\Override] // PHP 8.3+ checks that Product has label()
public function label(): string // a new version of label()
{
return parent::label() . " (" . $this->kg . " kg)";
}
}
$items = [new Product("Tea", 5000), new FreshProduct("Apples", 3000, 1.5)];
foreach ($items as $item) {
echo $item->label(), "\n";
}Tea: 5,000 IQD Apples: 4,500 IQD (1.5 kg)
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 22
#[\Override](PHP 8.3) asks PHP to check that the parent really haslabel(). PHP 8.2 simply ignores it, so the file runs on both.#[\Override](PHP 8.3) داوا لە PHP دەکات بپشکنێت کە باوکەکە بەڕاستیlabel()ـی هەیە. PHP 8.2 تەنیا پشتگوێی دەخات، بۆیە لەسەر هەردووکیان کار دەکات.- Lineدێڕی 25
parent::label()gives "Apples: 4,500 IQD"; the child adds the weight.parent::label()"Apples: 4,500 IQD" دەدات؛ کێشەکەی بۆ زیاد دەکات.- Lineدێڕی 29-32
One loop, two classes: each object runs its own label().
یەک ، دوو : هەر label()ـی خۆی جێبەجێ دەکات.
Making an inherited method less visible is refused, because code that may call label() on any Product must still be able to call it on a FreshProduct:
کەمکردنەوەی بۆماوەیی ڕەت دەکرێتەوە، چونکە ئەو کۆدەی دەتوانێت label() لەسەر هەر Productـێک بانگ بکات دەبێت هێشتا بتوانێت لەسەر FreshProductـێکیش بانگی بکات:
<?php
class Product
{
public function label(): string
{
return "a product";
}
}
class FreshProduct extends Product
{
protected function label(): string // public -> protected: not allowed
{
return "fresh";
}
}Fatal error: Access level to FreshProduct::label() must be public (as in class Product) in C:\xampp\htdocs\shop\narrow_visibility.php on line 12
Overriding: a new version of a methodپێناسەکردنەوەی میتۆد: وەشانێکی نوێی میتۆدێک
protected and private in a familyprotected و private لە خێزانێکدا
protected sits between the two others: the class itself and its children may use the member, outside code may not. A private member of the parent still exists inside a child object, but the child's code cannot see it. PHP then looks for a property of GiftCard with that name, finds none and gives a warning:
protected لە نێوان ئەو دووانەی تردایە: خودی و دەتوانن بەشەکە بەکار بهێنن، کۆدی دەرەوە ناتوانێت. بەشێکی privateـی باوک هێشتا لە ناو هەیە، بەڵام کۆدی نایبینێت. ئەوکات PHP بەدوای GiftCard بەو ناوە دەگەڕێت، هیچ نادۆزێتەوە و دەدات:
<?php
class Product
{
protected int $price = 5000; // children may use it
private int $cost = 4000; // only Product may use it
}
class GiftCard extends Product
{
public function show(): void
{
echo "price: ", $this->price, "\n";
echo "cost: ", $this->cost, "\n";
}
}
(new GiftCard())->show();price: 5000 cost: Warning: Undefined property: GiftCard::$cost in C:\xampp\htdocs\shop\protected_private.php on line 13
final: no more changesfinal: گۆڕانکاریی تر نییە
Sometimes a class or a method must stay exactly as it is, for example the code that takes money. final class CashPayment cannot be extended, and a final method cannot be overridden. PHP checks this as soon as it reads the class:
هەندێک جار یان دەبێت ڕێک وەک خۆی بمێنێتەوە، بۆ نموونە ئەو کۆدەی پارە وەردەگرێت. final class CashPayment ناتوانرێت extends بکرێت و final ناتوانرێت دووپاتە پێناسە بکرێتەوە. PHP هەر کە دەخوێنێتەوە ئەمە دەپشکنێت:
<?php
final class CashPayment
{
public function pay(int $amount): string
{
return "Paid " . number_format($amount) . " IQD in cash";
}
}
class CheapCash extends CashPayment {}Fatal error: Class CheapCash cannot extend final class CashPayment in C:\xampp\htdocs\shop\final.php on line 10
<?php
class Checkout
{
final public function total(int $sum): int
{
return $sum;
}
}
class FreeCheckout extends Checkout
{
public function total(int $sum): int
{
return 0;
}
}Fatal error: Cannot override final method Checkout::total() in C:\xampp\htdocs\shop\final_method.php on line 12
Since PHP 8.1 a class constant can be final too, and since 8.4 a property. The Java version of the same idea is in final classes and methods.
لە PHP 8.1ـەوە دەتوانێت final بێت و لە 8.4ـەوە . وەشانی Javaـی هەمان بیرۆکە لە کلاسەکان و میتۆدەکانی finalدایە.
Abstract classes: half-finished plansکلاسە ڕووتەکان: نەخشەی نیوەتەواو
Every payment prints a receipt the same way, but each kind pays differently. An holds the shared code and leaves the different part as an : a signature without a body. You cannot make an object of an abstract class; a child must write every abstract method (or be abstract itself). A class with even one abstract method must be declared abstract.
هەموو پارەدانێک بە هەمان شێوە پسوولە چاپ دەکات، بەڵام هەر جۆرێک بە شێوەیەکی جیاواز پارە دەدات. کۆدە هاوبەشەکە هەڵدەگرێت و بەشە جیاوازەکە وەک بەجێ دەهێڵێت: سەرەدێڕێک بێ جەستە. ناتوانیت لە دروست بکەیت؛ دەبێت هەموو بنووسێت (یان خۆی ڕووت بێت). تەنانەت بە یەک دەبێت بە abstract بناسێنرێت.
<?php
abstract class Payment
{
public function __construct(protected int $amount) {}
abstract public function pay(): string; // no body: each child writes it
public function receipt(): string // ready code, shared by all children
{
return "Receipt: " . $this->pay();
}
}
class CashPayment extends Payment
{
public function pay(): string
{
return number_format($this->amount) . " IQD paid in cash";
}
}
$p = new CashPayment(37000);
echo $p->receipt(), "\n";
$q = new Payment(37000); // not allowedReceipt: 37,000 IQD paid in cash
Fatal error: Uncaught Error: Cannot instantiate abstract class Payment in C:\xampp\htdocs\shop\abstract.php:24
Stack trace:
#0 {main}
thrown in C:\xampp\htdocs\shop\abstract.php on line 24Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
abstract class: a plan, not a finished thing.abstract class: نەخشەیەک، نەک شتێکی تەواو.- Lineدێڕی 6
No body: each child must write
pay().بێ جەستە: هەر دەبێت
pay()بنووسێت.- Lineدێڕی 8-11
Ready code that every child shares; it calls the child's
pay().کۆدی ئامادە کە هەموو بەکاری دەهێنێت؛
pay()ـی بانگ دەکات.- Lineدێڕی 24
Making an object of the abstract class itself is refused.
دروستکردنی لە خودی ڕەت دەکرێتەوە.
Interfaces: a contractنێوبەستەکان: گرێبەستێک
An lists public methods without bodies: it says what a class can do, never how. A class signs it with implements and must then write every one of those methods. An interface has no properties and no ready code (PHP 8.4 adds property declarations), and you cannot make an object of it. Java's version is in Interfaces. Interfaces are also how a class receives its helpers from outside: see Dependency Injection.
لیستێکی publicـە بێ جەستە: دەڵێت چی دەتوانێت بکات، هەرگیز نالێت چۆن. بە implements واژووی دەکات و پاشان دەبێت هەموو ئەو بنووسێت. نە هەیە نە کۆدی ئامادە (PHP 8.4 ناساندنی زیاد دەکات) و ناتوانیت لێ دروست بکەیت. وەشانی Java لە نێوبەستەکاندایە. هەروەها ئەو ڕێگایەن کە یاریدەدەرەکانی لە دەرەوە وەردەگرێت: بڕوانە تێخستنی وابەستەیی.
At the till of a bazaar shop the cashier asks only one thing: "Pay 37,000 dinars." Cash, a bank card or a phone wallet all answer that question in their own way, and the cashier does not need to know how. The interface is that question; each payment class is one way of answering it.
لە شوێنی پارەدانی دووکانێکی بازاڕ، کاشێرەکە تەنیا یەک شت دەپرسێت: «37,000 دینار بدە.» پارەی نەختینە، کارتی بانکی یان جزدانی مۆبایل هەموویان بە شێوەی خۆیان وەڵامی ئەو پرسیارە دەدەنەوە و کاشێرەکە پێویست ناکات بزانێت چۆن. ئەو پرسیارەیە؛ هەر پارەدان ڕێگایەکی وەڵامدانەوەیەتی.
Where the analogy stops workingسنووری ئەم نموونەیە
PHP checks only that the method exists with the right parameters and return type; it cannot check that the method really takes the money.
PHP تەنیا دەپشکنێت کە بە و جۆری دروست هەیە؛ ناتوانێت بپشکنێت کە بەڕاستی پارەکە وەردەگرێت.
<?php
interface PaymentMethod
{
public function pay(int $amount): string; // a promise, no body
}
class CashPayment implements PaymentMethod
{
public function pay(int $amount): string
{
return "Cash: " . number_format($amount) . " IQD";
}
}
class CardPayment implements PaymentMethod
{
public function __construct(private string $last4) {}
public function pay(int $amount): string
{
return "Card ****" . $this->last4 . ": " . number_format($amount) . " IQD";
}
}
$cash = new CashPayment();
$card = new CardPayment("4821");
echo $cash->pay(37000), "\n";
echo $card->pay(37000), "\n";
var_dump($card instanceof PaymentMethod);Cash: 37,000 IQD Card ****4821: 37,000 IQD bool(true)
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-5
The contract: one public method, no body.
گرێبەستەکە: یەک public، بێ جەستە.
- Lineدێڕی 7
implements PaymentMethod: CashPayment promises to havepay().implements PaymentMethod: CashPayment بەڵێن دەداتpay()ـی هەبێت.- Lineدێڕی 29
An object of a class that implements the interface is also an instance of the interface.
ئەو جێبەجێ دەکات، هەروەها ئەو .
Forget one method of the contract, or try to make an object of the interface itself, and PHP refuses:
ئەگەر یەکێک لە گرێبەستەکە لەبیر بکەیت، یان هەوڵ بدەیت لە خودی دروست بکەیت، PHP ڕەتی دەکاتەوە:
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
class WalletPayment implements PaymentMethod
{
public function send(int $amount): string // wrong name: pay() is missing
{
return "Sent " . $amount;
}
}Fatal error: Class WalletPayment contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PaymentMethod::pay) in C:\xampp\htdocs\shop\interface_missing.php on line 7
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
$m = new PaymentMethod();Fatal error: Uncaught Error: Cannot instantiate interface PaymentMethod in C:\xampp\htdocs\shop\new_interface.php:7
Stack trace:
#0 {main}
thrown in C:\xampp\htdocs\shop\new_interface.php on line 7Interfaces: a contractنێوبەستەکان: گرێبەستێک
Several interfaces at onceچەند نێوبەستەیەک پێکەوە
A class may extend only one parent, but it may implement as many interfaces as it needs, separated by commas. A card payment can be refunded; cash at this shop cannot. instanceof asks whether an object keeps a contract. When a class does both, extends comes first: class CardPayment extends Payment implements PaymentMethod, Refundable.
تەنیا دەتوانێت لە یەک extends بکات، بەڵام دەتوانێت هەر چەند پێویست بێت جێبەجێ بکات و بە فاریزە لێکیان جیا دەکاتەوە. پارەدان بە کارت دەتوانرێت بگەڕێنرێتەوە؛ پارەی نەختینە لەم دووکانەدا ناگەڕێنرێتەوە. instanceof دەپرسێت ئایا گرێبەستێک دەباتە سەر. کاتێک هەردووکیان دەکات، extends یەکەم دێت: class CardPayment extends Payment implements PaymentMethod, Refundable.
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
interface Refundable
{
public function refund(int $amount): string;
}
class CardPayment implements PaymentMethod, Refundable
{
public function pay(int $amount): string
{
return "Card paid " . number_format($amount) . " IQD";
}
public function refund(int $amount): string
{
return "Card refunded " . number_format($amount) . " IQD";
}
}
class CashPayment implements PaymentMethod
{
public function pay(int $amount): string
{
return "Cash paid " . number_format($amount) . " IQD";
}
}
foreach ([new CardPayment(), new CashPayment()] as $m) {
echo $m->pay(12000), "\n";
if ($m instanceof Refundable) {
echo $m->refund(12000), "\n";
} else {
echo get_class($m), " cannot refund\n";
}
}Card paid 12,000 IQD Card refunded 12,000 IQD Cash paid 12,000 IQD CashPayment cannot refund
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 12
CardPayment keeps two contracts.
CardPayment دوو گرێبەست دەباتە سەر.
- Lineدێڕی 35
instanceof Refundableis true only for the card.instanceof Refundableتەنیا بۆ کارتەکە ڕاستە.
Polymorphism: one call, many formsفرەشێوەیی: یەک بانگکردن، چەندین شێوە
checkout() below accepts any PaymentMethod. It calls $method->pay($total) without asking which class it got, and each object runs its own pay(). This is : one call, many forms. Next month the shop can add an instalment plan as a new class that implements the interface, and checkout() will not change at all.
checkout()ـی خوارەوە هەر PaymentMethodـێک وەردەگرێت. $method->pay($total) بانگ دەکات بێ ئەوەی بپرسێت کام وەرگرتووە و هەر pay()ـی خۆی جێبەجێ دەکات. ئەمە : یەک بانگکردن، چەندین شێوە. مانگی داهاتوو دووکانەکە دەتوانێت پلانی قیست وەک نوێ زیاد بکات کە جێبەجێ دەکات و checkout() هیچ ناگۆڕێت.
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
class CashPayment implements PaymentMethod
{
public function pay(int $amount): string
{
return "Cash: " . number_format($amount) . " IQD, change counted at the till";
}
}
class CardPayment implements PaymentMethod
{
public function __construct(private string $last4) {}
public function pay(int $amount): string
{
return "Card ****{$this->last4}: " . number_format($amount) . " IQD sent to the bank";
}
}
class WalletPayment implements PaymentMethod
{
public function __construct(private string $phone) {}
public function pay(int $amount): string
{
$hidden = substr($this->phone, 0, 4) . "*****" . substr($this->phone, -2);
return "Wallet $hidden: " . number_format($amount) . " IQD approved in the app";
}
}
function checkout(int $total, PaymentMethod $method): void // any PaymentMethod is welcome
{
echo $method->pay($total), "\n";
}
$methods = [
new CashPayment(),
new CardPayment("4821"),
new WalletPayment("07000000123"),
];
foreach ($methods as $method) {
checkout(37000, $method); // one call, three behaviours
}Cash: 37,000 IQD, change counted at the till Card ****4821: 37,000 IQD sent to the bank Wallet 0700*****23: 37,000 IQD approved in the app
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 31
The wallet shows only the start and the end of the phone number.
جزدانەکە تەنیا سەرەتا و کۆتایی ژمارەی مۆبایلەکە پیشان دەدات.
- Lineدێڕی 36
The parameter type is the interface, not one class.
جۆری ، نەک یەک .
- Lineدێڕی 46-48
The same line runs three different
pay()methods.هەمان دێڕ سێ
pay()ـی جیاواز جێبەجێ دەکات.


The type declaration also protects checkout(). A Coupon class that happens to have a pay() method, but never promised the contract with implements, is refused:
پاسەوانی checkout()ـیش دەکات. Coupon کە بە ڕێکەوت pay()ـی هەیە، بەڵام هەرگیز بە implements بەڵێنی گرێبەستەکەی نەداوە، ڕەت دەکرێتەوە:
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
class Coupon // has pay(), but does not implement PaymentMethod
{
public function pay(int $amount): string
{
return "Coupon used";
}
}
function checkout(int $total, PaymentMethod $method): void
{
echo $method->pay($total), "\n";
}
checkout(37000, new Coupon());Fatal error: Uncaught TypeError: checkout(): Argument #2 ($method) must be of type PaymentMethod, Coupon given, called in C:\xampp\htdocs\shop\wrong_type.php on line 20 and defined in C:\xampp\htdocs\shop\wrong_type.php:15
Stack trace:
#0 C:\xampp\htdocs\shop\wrong_type.php(20): checkout(37000, Object(Coupon))
#1 {main}
thrown in C:\xampp\htdocs\shop\wrong_type.php on line 15Polymorphism: one call, many formsفرەشێوەیی: یەک بانگکردن، چەندین شێوە
Abstract class or interface?کلاسی ڕووت یان نێوبەستە؟
Both describe methods that others must write, and neither can be created with new. The differences decide which one fits, and many programs use both: an interface for the contract and an abstract class for code that several of its classes share, as the worked example does.
هەردووکیان ئەو وەسف دەکەن کە ئەوانی تر دەبێت بیاننووسن و هیچیان بە new دروست ناکرێن. جیاوازییەکان دیاری دەکەن کامیان دەگونجێت و زۆر بەرنامە هەردووکیان بەکار دەهێنن: بۆ گرێبەستەکە و بۆ ئەو کۆدەی چەند هاوبەشی دەکەن، وەک نموونە کارپێکراوەکە.
| Compareبەراورد | Abstract class | Interface |
|---|---|---|
| How a class uses it چۆن بەکاری دەهێنێت | extends: only oneextends: تەنیا یەک | implements: as many as neededimplements: هەر چەندێک پێویست بێت |
| Properties | Yes, anyبەڵێ، هەر جۆرێک | No (PHP 8.4 adds property declarations); constants onlyنەخێر (PHP 8.4 ناساندنی زیاد دەکات)؛ تەنیا |
| Method bodiesجەستەی | Yes, finished and abstract methodsبەڵێ، تەواو و ڕووت | No: only public method signaturesنەخێر: تەنیا سەرەدێڕی public |
| Constructor | Yesبەڵێ | Allowed but not recommendedڕێگەپێدراوە، بەڵام پێشنیار ناکرێت |
| Use it whenکەی بەکاری بهێنیت | Closely related classes share code and data نزیک لە یەک کۆد و هاوبەشیان هەیە | Unrelated classes share an ability بێپەیوەندی توانایەکی هاوبەشیان هەیە |
Traits: shared code for unrelated classesترەیتەکان: کۆدی هاوبەش بۆ کلاسەکانی بێپەیوەندی
PHP has single inheritance: a class has at most one parent. But Cart and Checkout are unrelated, and both want the same history log. A is a named group of methods and properties that PHP copies into a class with use; the manual calls it "language-assisted copy and paste". A trait cannot make objects itself.
PHP تاکی هەیە: زۆرترین یەک هەیە. بەڵام Cart و Checkout پەیوەندییان بە یەکەوە نییە و هەردووکیان هەمان تۆماری مێژوویان دەوێت. کۆمەڵەیەکی ناونراوی و کە PHP بە use کۆپییان دەکاتە ناو ؛ ڕێبەری PHP پێی دەڵێت «کۆپی و لکاندن بە یارمەتیی زمانەکە». خۆی ناتوانێت دروست بکات.
<?php
trait KeepsHistory // a box of reusable members
{
private array $history = [];
protected function remember(string $event): void
{
$this->history[] = $event;
}
public function history(): string
{
return implode(" | ", $this->history);
}
}
class Cart
{
use KeepsHistory; // copy the trait's members in here
public function add(string $item): void
{
$this->remember("added $item");
}
}
class Checkout
{
use KeepsHistory; // the same members, in an unrelated class
public function pay(int $amount): void
{
$this->remember("paid " . number_format($amount));
}
}
$cart = new Cart();
$cart->add("Tea");
$cart->add("Honey");
$till = new Checkout();
$till->pay(20000);
echo $cart->history(), "\n";
echo $till->history(), "\n";added Tea | added Honey paid 20,000
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-15
The trait: a private property, a protected helper and a public method.
: private، یارمەتیدەرێکی protected و public.
- Lineدێڕی 19
use KeepsHistory;copies the trait's members into Cart.use KeepsHistory;بەشەکانی کۆپی دەکاتە ناو Cartـەوە.- Lineدێڕی 29
The same members, in a class with a different job.
هەمان بەشەکان، لە کە کارێکی جیاوازی هەیە.
- Lineدێڕی 43-44
Each object keeps its own history.
هەر مێژووی خۆی هەڵدەگرێت.
<?php
trait KeepsHistory
{
public function history(): string { return ""; }
}
$h = new KeepsHistory();Fatal error: Uncaught Error: Cannot instantiate trait KeepsHistory in C:\xampp\htdocs\shop\trait_new.php:7
Stack trace:
#0 {main}
thrown in C:\xampp\htdocs\shop\trait_new.php on line 7Enums: a fixed list of values (PHP 8.1)ئینەمەکان: لیستێکی جێگیری نرخەکان (PHP 8.1)
A payment can only be paid, failed or refunded. Storing that as free text invites mistakes such as "payed". An (PHP 8.1) is a type with a fixed list of cases; nothing else can sneak in. A backed enum gives each case a string or int value, which is what you store in a database; from() turns the stored text back into a case and tryFrom() returns null for unknown text. Enums may have methods. Java's enums are in Enums.
پارەدانێک تەنیا دەتوانێت دراو، سەرنەکەوتوو یان گەڕێنراوە بێت. هەڵگرتنی ئەمە وەک دەقی ئازاد دەرگا بۆ هەڵەی وەک "payed" دەکاتەوە. (PHP 8.1) جۆرێکە بە لیستێکی جێگیری caseـەکان؛ هیچ شتێکی تر ناتوانێت بچێتە ناوی. نرخدار string یان int دەداتە هەر caseـێک کە ئەوەیە لە هەڵیدەگریت؛ from() دەقە هەڵگیراوەکە دەکاتەوە بە caseـێک و tryFrom() بۆ دەقی نەناسراو null دەگەڕێنێتەوە. دەتوانن هەبێت. Java لە ئینەمەکاندان.
<?php
enum PaymentStatus: string
{
case Paid = "paid";
case Failed = "failed";
case Refunded = "refunded";
public function label(): string
{
return match ($this) {
PaymentStatus::Paid => "Payment received",
PaymentStatus::Failed => "Payment failed, try again",
PaymentStatus::Refunded => "Money returned",
};
}
}
$status = PaymentStatus::Paid;
echo $status->name, " / ", $status->value, " / ", $status->label(), "\n";
$fromDb = PaymentStatus::from("refunded"); // text from a database -> a case
echo $fromDb->label(), "\n";
var_dump(PaymentStatus::tryFrom("lost")); // unknown text -> null
echo count(PaymentStatus::cases()), " cases\n";
var_dump($status === PaymentStatus::Paid);
PaymentStatus::from("lost"); // unknown text -> ValueErrorPaid / paid / Payment received
Money returned
NULL
3 cases
bool(true)
Fatal error: Uncaught ValueError: "lost" is not a valid backing value for enum PaymentStatus in C:\xampp\htdocs\shop\enums.php:28
Stack trace:
#0 C:\xampp\htdocs\shop\enums.php(28): PaymentStatus::from('lost')
#1 {main}
thrown in C:\xampp\htdocs\shop\enums.php on line 28Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
: string: every case has a string value.: string: هەموو caseـێک stringـی هەیە.- Lineدێڕی 8-15
An enum may have methods;
match ($this)picks the text for each case.دەتوانێت هەبێت؛
match ($this)دەقی هەر caseـێک هەڵدەبژێرێت.- Lineدێڕی 21
from()for values you trust, such as your own database.from()بۆ ئەو متمانەت پێیانە، وەک خۆت.- Lineدێڕی 24
tryFrom()for values you do not trust: unknown text givesnull.tryFrom()بۆ ئەو متمانەت پێیان نییە: دەقی نەناسراوnullدەدات.- Lineدێڕی 28
from()with unknown text throws aValueError.from()بە دەقی نەناسراوValueErrorدەدات.
Namespaces and autoloading: an overviewبۆشایی ناوەکان و بارکردنی خۆکار: تێڕوانینێکی گشتی
Real projects have many classes, often from other people's libraries. Two libraries may both have a class called Payment. A works like a folder for names: Bazaar\Payments\CashPayment and OtherShop\Payment can live side by side. A file declares its namespace on the first line (namespace Bazaar\Payments;), and another file imports a name once with use and then writes it short.
پرۆژە ڕاستەقینەکان زۆریان هەیە، زۆرجار لە خەڵکی ترەوە. لەوانەیە دوو هەردووکیان بە ناوی Payment هەبێت. وەک بۆ ناوەکان کار دەکات: Bazaar\Payments\CashPayment و OtherShop\Payment دەتوانن لە تەنیشت یەکەوە بژین. لە یەکەم دێڕدا خۆی ڕادەگەیەنێت (namespace Bazaar\Payments;) و تر بە use یەک جار ناوێک هاوردە دەکات و پاشان بە کورتی دەینووسێت.
With one class per file, a big project would need a require for every class. removes that list: you register a function with spl_autoload_register(), and PHP calls it with the class name the first time a class is used. The usual rule (PSR-4) makes the namespace follow the folders, so the function can turn a name into a path. The example below runs on XAMPP as it is; [autoload] lines show the moment each file is loaded. (include and require explains require.)
بە یەک بۆ هەر ، پرۆژەیەکی گەورە بۆ هەموو requireـێکی پێویست دەبوو. ئەو لیستە لادەبات: بە spl_autoload_register() تۆمار دەکەیت و PHP یەکەم جار کە بەکار دێت بە ناوی ئەو بانگی دەکات. یاسا باوەکە (PSR-4) وا دەکات شوێنی بکەوێت، بۆیە دەتوانێت ناوێک بکات بە . نموونەی خوارەوە وەک خۆی لەسەر XAMPP کار دەکات؛ دێڕەکانی [autoload] ئەو ساتە پیشان دەدەن کە هەر بار دەکرێت. (include و require require ڕوون دەکاتەوە.)
bazaar/
├── index.php
└── src/
├── Payments/
│ ├── PaymentMethod.php
│ └── CashPayment.php
└── Shop/
└── Checkout.php<?php
namespace Bazaar\Payments;
class CashPayment implements PaymentMethod
{
public function pay(int $amount): string
{
return "Cash: " . number_format($amount) . " IQD";
}
}<?php
namespace Bazaar\Shop;
use Bazaar\Payments\PaymentMethod; // import the name once
class Checkout
{
public function finish(int $total, PaymentMethod $method): string
{
return $method->pay($total);
}
}<?php
// Autoloader: turn a class name into a file name, then load that file
spl_autoload_register(function (string $class): void {
$prefix = "Bazaar\\";
if (!str_starts_with($class, $prefix)) {
return; // not one of our classes
}
$relative = substr($class, strlen($prefix)); // "Payments\CashPayment"
$file = __DIR__ . "/src/" . str_replace("\\", "/", $relative) . ".php";
echo "[autoload] $class\n";
require $file;
});
use Bazaar\Payments\CashPayment;
use Bazaar\Shop\Checkout;
$till = new Checkout();
echo $till->finish(37000, new CashPayment()), "\n";
echo Checkout::class, "\n";[autoload] Bazaar\Shop\Checkout [autoload] Bazaar\Payments\CashPayment [autoload] Bazaar\Payments\PaymentMethod Cash: 37,000 IQD Bazaar\Shop\Checkout
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3-12
The autoloader: "BazaarShopCheckout" becomes src/Shop/Checkout.php.
بارکەرەکە: "BazaarShopCheckout" دەبێتە src/Shop/Checkout.php.
- Lineدێڕی 14-15
useimports the full names once.useناوە تەواوەکان یەک جار هاوردە دەکات.- Lineدێڕی 17-18
Checkout is loaded at
new Checkout(); CashPayment and the interface it implements follow.Checkout لە کاتی
new Checkout()ـدا بار دەکرێت؛ CashPayment و ئەو جێبەجێی دەکات بە دوایدا دێن.- Lineدێڕی 19
::classgives the full name of a class.::classناوی تەواوی دەدات.
Namespaces and autoloading: an overviewبۆشایی ناوەکان و بارکردنی خۆکار: تێڕوانینێکی گشتی
Composer in one screenComposer لە یەک شاشەدا
Composer is PHP's , like pip for Python. A composer.json file lists the a project needs (from the Packagist site) and the autoload rule. composer install downloads them into vendor/ and writes vendor/autoload.php, so one require line loads every class: the libraries' and yours. composer.lock records the exact versions, so everyone installs the same ones. Frameworks such as Laravel and Symfony, and the ORMs of Object-Relational Mapping, are installed this way. This course does not install Composer; the example below is for reading only.
Composer PHPـیە، وەک pip بۆ Python. composer.json ئەو ڕیز دەکات کە پرۆژەیەک پێویستیەتی (لە ماڵپەڕی Packagistـەوە) لەگەڵ یاسای . composer install داباریان دەکات بۆ ناو vendor/ و vendor/autoload.php دەنووسێت، بۆیە یەک دێڕی require هەموو بار دەکات: هی و هی خۆت. composer.lock وەشانە وردەکان تۆمار دەکات، بۆیە هەموو کەس هەمان وەشان دادەمەزرێنێت. وەک Laravel و Symfony و ORMـەکانی Object-Relational Mapping بەم شێوەیە دادەمەزرێنرێن. ئەم خولە Composer دانامەزرێنێت؛ نموونەی خوارەوە تەنیا بۆ خوێندنەوەیە.
{
"require": {
"vendor/package": "^2.0"
},
"autoload": {
"psr-4": { "Bazaar\\": "src/" }
}
}Worked example: paying at the bazaar tillنموونەی کارپێکراو: پارەدان لە شوێنی پارەدانی بازاڕ
The task. The till must take 37,000 IQD by cash, by bank card or by mobile wallet, report "paid" or "failed", and keep a history. The design. An interface PaymentMethod is the contract. CashPayment implements it directly. Card and wallet both take money from a balance, so they share an abstract class ElectronicPayment; only the fee differs (in this example the wallet adds 1 %). The enum PaymentStatus allows only Paid or Failed, and the trait KeepsHistory gives Checkout its log. Classes that must not be changed are final.
ئەرکەکە. شوێنی پارەدان دەبێت 37,000 دینار بە پارەی نەختینە، بە کارتی بانکی یان بە جزدانی مۆبایل وەربگرێت، «دراو» یان «سەرنەکەوتوو» ڕابگەیەنێت و مێژوویەک هەڵبگرێت. دیزاینەکە. PaymentMethod گرێبەستەکەیە. CashPayment ڕاستەوخۆ جێبەجێی دەکات. کارت و جزدان هەردووکیان پارە لە باڵانسێک دەبەن، بۆیە هاوبەشیان هەیە، ElectronicPayment؛ تەنیا کرێکە جیاوازە (لەم نموونەیەدا جزدانەکە 1% زیاد دەکات). PaymentStatus تەنیا Paid یان Failed ڕێگە دەدات و KeepsHistory تۆمارەکەی دەداتە Checkout. ئەو نابێت بگۆڕدرێن finalـن.
<?php
enum PaymentStatus: string
{
case Paid = "paid";
case Failed = "failed";
}
interface PaymentMethod
{
public function pay(int $amount): PaymentStatus;
public function describe(): string;
}
trait KeepsHistory
{
private array $history = [];
protected function remember(string $event): void
{
$this->history[] = $event;
}
public function history(): array
{
return $this->history;
}
}
final class CashPayment implements PaymentMethod
{
public function __construct(private int $given) {}
public function pay(int $amount): PaymentStatus
{
return $this->given >= $amount ? PaymentStatus::Paid : PaymentStatus::Failed;
}
public function describe(): string
{
return "cash";
}
}
abstract class ElectronicPayment implements PaymentMethod
{
public function __construct(protected int $balance) {}
abstract protected function feePercent(): int;
public function pay(int $amount): PaymentStatus
{
$cost = $amount + intdiv($amount * $this->feePercent(), 100);
if ($cost > $this->balance) {
return PaymentStatus::Failed;
}
$this->balance -= $cost;
return PaymentStatus::Paid;
}
}
final class CardPayment extends ElectronicPayment
{
protected function feePercent(): int { return 0; }
public function describe(): string { return "bank card"; }
}
final class WalletPayment extends ElectronicPayment
{
protected function feePercent(): int { return 1; }
public function describe(): string { return "mobile wallet"; }
}
class Checkout
{
use KeepsHistory;
public function finish(int $total, PaymentMethod $method): PaymentStatus
{
$status = $method->pay($total);
$this->remember(number_format($total) . " IQD by " . $method->describe() . ": " . $status->value);
return $status;
}
}
$till = new Checkout();
$till->finish(37000, new CashPayment(40000));
$till->finish(37000, new CardPayment(100000));
$till->finish(37000, new WalletPayment(30000));
foreach ($till->history() as $i => $line) {
echo $i + 1, ". ", $line, "\n";
}1. 37,000 IQD by cash: paid 2. 37,000 IQD by bank card: paid 3. 37,000 IQD by mobile wallet: failed
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-6
The only two results a payment can have.
تەنیا دوو ئەنجام کە پارەدانێک دەتوانێت هەیبێت.
- Lineدێڕی 8-12
The contract every payment method signs.
ئەو گرێبەستەی هەموو ڕێگایەکی پارەدان واژووی دەکات.
- Lineدێڕی 29-35
Cash: paid if the customer gave enough.
پارەی نەختینە: دراو ئەگەر کڕیار بەشی پێویستی دابێت.
- Lineدێڕی 44-59
Shared code for card and wallet; each child gives only its fee.
کۆدی هاوبەش بۆ کارت و جزدان؛ هەر تەنیا کرێی خۆی دەدات.
- Lineدێڕی 52
37,000 + 1 % = 37,370 for the wallet; its balance of 30,000 is not enough.
37,000 + 1% = 37,370 بۆ جزدانەکە؛ باڵانسی 30,000ـی بەس نییە.
- Lineدێڕی 73-83
Checkout depends only on the interface and records each payment with the trait.
Checkout تەنیا پشت بە دەبەستێت و هەر پارەدانێک بە تۆمار دەکات.
Common mistakesهەڵە باوەکان
All messages below come from PHP 8.2. Most of them appear as soon as PHP reads the class, before any line of the program runs.
هەموو پەیامەکانی خوارەوە لە PHP 8.2ـەوە هاتوون. زۆربەیان هەر کە PHP دەخوێنێتەوە دەردەکەون، پێش جێبەجێبوونی هیچ دێڕێکی بەرنامەکە.
| Mistakeهەڵە | What PHP saysPHP چی دەڵێت | Fixچاککردن |
|---|---|---|
A child constructor without parent::__construct() بەبێ parent::__construct() | Error: Typed property Product::$name must not be accessed before initialization | Call parent::__construct(...) in the child's constructor.لە parent::__construct(...) بانگ بکە. |
class Delivery extends Product, Service {} | Parse error: syntax error, unexpected token ",", expecting "{" | One parent only; share the rest with interfaces or traits.تەنیا یەک باوک؛ باقییەکە بە یان هاوبەش بکە. |
class CardPayment implements PaymentMethod extends Payment {} | Parse error: syntax error, unexpected token "extends", expecting "{" | extends first, then implements.یەکەم extends، پاشان implements. |
| A method of the interface is missing نییە | Class WalletPayment contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PaymentMethod::pay) | Write the missing method with the same signature. نەبووەکە بە هەمان سەرەدێڕ بنووسە. |
protected function label() over a public oneprotected function label() لەسەر یەکێکی public | Access level to FreshProduct::label() must be public (as in class Product) | Keep the same visibility or make it wider.هەمان بهێڵەوە یان فراوانتری بکە. |
new Payment(37000) // abstract | Error: Cannot instantiate abstract class Payment | Make an object of a finished child class. لە تەواو دروست بکە. |
new PaymentMethod() // interface | Error: Cannot instantiate interface PaymentMethod | Make an object of a class that implements it. لە دروست بکە کە جێبەجێی دەکات. |
A class with pay() but no implements بە pay() بەڵام بەبێ implements | TypeError: checkout(): Argument #2 ($method) must be of type PaymentMethod, Coupon given | Add implements PaymentMethod to the class.implements PaymentMethod بۆ زیاد بکە. |
class CheapCash extends CashPayment // final | Class CheapCash cannot extend final class CashPayment | Do not extend it; use it, or implement the interface yourself.extendsـی مەکە؛ بەکاری بهێنە یان خۆت جێبەجێ بکە. |
<?php
interface PaymentMethod {}
class Payment {}
class CardPayment implements PaymentMethod extends Payment {}Parse error: syntax error, unexpected token "extends", expecting "{" in C:\xampp\htdocs\shop\implements_first.php on line 4Exercisesڕاهێنانەکان
Write an abstract class Delivery with an abstract method cost(float $km): int and a finished method quote(float $km): string that prints the class name and the cost (static::class gives the class name of the object). Write BikeDelivery (1,000 IQD + 250 per km) and CarDelivery (2,000 IQD + 400 per km), and print a quote for 5 km from each.
Delivery بنووسە بە cost(float $km): int و تەواوی quote(float $km): string کە ناوی و تێچووەکە چاپ دەکات (static::class ناوی دەدات). BikeDelivery (1,000 دینار + 250 بۆ هەر کیلۆمەترێک) و CarDelivery (2,000 دینار + 400 بۆ هەر کیلۆمەترێک) بنووسە و بۆ 5 کیلۆمەتر نرخی هەریەکەیان چاپ بکە.
Hintئاماژە
abstract public function cost(float $km): int; has no body.
abstract public function cost(float $km): int; جەستەی نییە.
Show answerوەڵامەکە پیشان بدە
quote() is written once in the parent; each child gives only its own cost().
quote() تەنیا یەک جار لە باوکدا نووسراوە؛ هەر تەنیا cost()ـی خۆی دەدات.
<?php
abstract class Delivery
{
abstract public function cost(float $km): int;
public function quote(float $km): string
{
return static::class . ": " . number_format($this->cost($km)) . " IQD for $km km";
}
}
class BikeDelivery extends Delivery
{
public function cost(float $km): int
{
return 1000 + (int) round(250 * $km);
}
}
class CarDelivery extends Delivery
{
public function cost(float $km): int
{
return 2000 + (int) round(400 * $km);
}
}
foreach ([new BikeDelivery(), new CarDelivery()] as $d) {
echo $d->quote(5), "\n";
}BikeDelivery: 2,250 IQD for 5 km CarDelivery: 4,000 IQD for 5 km
Write an interface Discount with apply(int $price): int, and two classes: PercentDiscount (for example 10 %) and FixedDiscount (for example 2,000 IQD off, but never below 0). Write a function finalPrice(int $price, Discount $discount): int and test it with 15,000 IQD and with 1,500 IQD.
Discount بنووسە بە apply(int $price): int و دوو : PercentDiscount (بۆ نموونە 10%) و FixedDiscount (بۆ نموونە 2,000 دینار کەمکردنەوە، بەڵام هەرگیز لە 0 کەمتر نا). finalPrice(int $price, Discount $discount): int بنووسە و بە 15,000 دینار و 1,500 دینار تاقیی بکەرەوە.
Hintئاماژە
Each class keeps its own number (percent or amount) as a promoted private property.
هەر ژمارەی خۆی (ڕێژە یان بڕ) وەک privateـی بەرزکراوە هەڵدەگرێت.
Show answerوەڵامەکە پیشان بدە
finalPrice() does not know which discount it gets; max(0, …) keeps the price from going below zero.
finalPrice() نازانێت کام داشکاندن وەردەگرێت؛ max(0, …) ناهێڵێت نرخەکە لە سفر کەمتر بێت.
<?php
interface Discount
{
public function apply(int $price): int;
}
class PercentDiscount implements Discount
{
public function __construct(private int $percent) {}
public function apply(int $price): int
{
return $price - intdiv($price * $this->percent, 100);
}
}
class FixedDiscount implements Discount
{
public function __construct(private int $amount) {}
public function apply(int $price): int
{
return max(0, $price - $this->amount);
}
}
function finalPrice(int $price, Discount $discount): int
{
return $discount->apply($price);
}
echo finalPrice(15000, new PercentDiscount(10)), "\n";
echo finalPrice(15000, new FixedDiscount(2000)), "\n";
echo finalPrice(1500, new FixedDiscount(2000)), "\n";13500 13000 0
A tea house sells small, medium and large glasses for 500, 750 and 1,000 IQD. Write a backed enum TeaSize with the values "S", "M" and "L" and a method price(). The waiter wrote the order ["S", "L", "L", "M"]: print the total, then show what tryFrom("XL") gives.
چایخانەیەک پەرداخی بچووک، مامناوەند و گەورە بە 500، 750 و 1,000 دینار دەفرۆشێت. نرخداری TeaSize بنووسە بە "S"، "M" و "L" و price(). گارسۆنەکە داواکاریی ["S", "L", "L", "M"]ـی نووسیوە: کۆی گشتی چاپ بکە، پاشان پیشانی بدە tryFrom("XL") چی دەدات.
Hintئاماژە
match ($this) { TeaSize::Small => 500, … } inside price().
match ($this) { TeaSize::Small => 500, … } لە ناو price()ـدا.
Show answerوەڵامەکە پیشان بدە
TeaSize::from($code) turns each letter into a case; an unknown size gives null with tryFrom().
TeaSize::from($code) هەر پیتێک دەکات بە caseـێک؛ قەبارەیەکی نەناسراو بە tryFrom() null دەدات.
<?php
enum TeaSize: string
{
case Small = "S";
case Medium = "M";
case Large = "L";
public function price(): int
{
return match ($this) {
TeaSize::Small => 500,
TeaSize::Medium => 750,
TeaSize::Large => 1000,
};
}
}
$order = ["S", "L", "L", "M"]; // what the waiter wrote down
$total = 0;
foreach ($order as $code) {
$total += TeaSize::from($code)->price();
}
echo "Total: ", number_format($total), " IQD\n";
var_dump(TeaSize::tryFrom("XL"));Total: 3,250 IQD NULL
Both Product and a Receipt class need to show amounts as "15,000 IQD". Put a method money(int $amount): string in a trait FormatsMoney, use it in both classes, and print a product label and a receipt line.
هەردوو Product و Receipt پێویستیان بەوەیە بڕی پارە وەک "15,000 IQD" پیشان بدەن. money(int $amount): string بخە ناو FormatsMoney، لە هەردوو بەکاری بهێنە و لەیبڵی بەرهەمێک و دێڕێکی پسوولە چاپ بکە.
Hintئاماژە
use FormatsMoney; goes inside each class body.
use FormatsMoney; دەچێتە ناو جەستەی هەر .
Show answerوەڵامەکە پیشان بدە
The formatting code exists once, although Product and Receipt have no common parent.
کۆدی ڕێکخستنەکە تەنیا یەک جار هەیە، هەرچەندە Product و Receipt هیچ باوکێکی هاوبەشیان نییە.
<?php
trait FormatsMoney
{
public function money(int $amount): string
{
return number_format($amount) . " IQD";
}
}
class Product
{
use FormatsMoney;
public function __construct(public string $name, public int $price) {}
public function label(): string
{
return $this->name . ": " . $this->money($this->price);
}
}
class Receipt
{
use FormatsMoney;
public function line(string $what, int $amount): string
{
return str_pad($what, 10) . $this->money($amount);
}
}
echo (new Product("Honey", 15000))->label(), "\n";
echo (new Receipt())->line("Total", 37000), "\n";Honey: 15,000 IQD Total 37,000 IQD
A student writes class WalletPayment implements PaymentMethod with a method send(int $amount) instead of pay(int $amount). Before running it, say which message PHP will print and on which line, then fix the class.
قوتابییەک class WalletPayment implements PaymentMethod دەنووسێت بە send(int $amount) لەبری pay(int $amount). پێش جێبەجێکردنی، بڵێ PHP چ پەیامێک و لە کام دێڕدا چاپ دەکات، پاشان چاک بکە.
Show answerوەڵامەکە پیشان بدە
PHP stops at the class line itself (line 7), because the contract is broken as soon as the class is read. The fix is to rename send() to pay() with the same signature as in the interface.
PHP لە دێڕی خودی (دێڕی 7) ڕادەوەستێت، چونکە هەر کە دەخوێندرێتەوە گرێبەستەکە شکاوە. چاککردنەکە گۆڕینی ناوی send()ـە بۆ pay() بە هەمان سەرەدێڕی ناو .
<?php
interface PaymentMethod
{
public function pay(int $amount): string;
}
class WalletPayment implements PaymentMethod
{
public function send(int $amount): string // wrong name: pay() is missing
{
return "Sent " . $amount;
}
}Fatal error: Class WalletPayment contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PaymentMethod::pay) in C:\xampp\htdocs\shop\interface_missing.php on line 7
Summaryکورتە
class Child extends Parent: the child receives the parent's public and protected members. One parent only; use the "is a" test.- A child with its own constructor must call
parent::__construct(...); PHP never does it for you. - Overriding writes a new version of a method;
parent::method()reaches the old one. Visibility may only stay the same or widen.#[\Override](8.3) checks the name. protected: the class and its family.final: no child class, or no override.- An abstract class is half finished: no
new, children write its abstract methods. An interface is a pure contract of public methods; a class may implement many. - Polymorphism: code that asks for an interface works with every class that implements it, even future ones.
- A trait copies methods into unrelated classes with
use. An enum (8.1) is a fixed list of cases; usetryFrom()for untrusted input. - Namespaces group names like folders; an autoloader (
spl_autoload_register(), or Composer'svendor/autoload.php) loads each class file when it is first needed. - Everything here ran on PHP 8.2 and works the same on 8.3, 8.4 and 8.5. Next, classes meet the database in Object-Relational Mapping.
class Child extends Parent: بەشە public و protectedـەکانی وەردەگرێت. تەنیا یەک باوک؛ تاقیکردنەوەی «جۆرێکە لە» بەکار بهێنە.- کە خۆی هەیە دەبێت
parent::__construct(...)بانگ بکات؛ PHP هەرگیز بۆت ناکات. - وەشانێکی نوێی دەنووسێت؛
parent::method()دەگاتە کۆنەکە. تەنیا دەتوانێت وەک خۆی بمێنێتەوە یان فراوانتر بێت.#[\Override](8.3) ناوەکە دەپشکنێت. protected: و خێزانەکەی.final: هیچ ، یان هیچ دووپاتە پێناسەکردنەوەیەک.- نیوەتەواوە:
newـی نییە و دەنووسن. گرێبەستێکی پاکی publicـە؛ دەتوانێت چەندینیان جێبەجێ بکات. - : ئەو کۆدەی داوای دەکات لەگەڵ هەموو ئەو جێبەجێی دەکەن کار دەکات، تەنانەت ئەوانەی داهاتووش.
- بە
useکۆپی دەکاتە ناو بێپەیوەندی. (8.1) لیستێکی جێگیری caseـەکانە؛ بۆ نامتمانەtryFrom()بەکار بهێنە. - ناوەکان وەک گرووپ دەکات؛ بارکەرێک (
spl_autoload_register()، یانvendor/autoload.phpـی Composer) هەر ئەو کاتە بار دەکات کە بۆ یەکەم جار پێویست بێت. - هەموو شتێکی ئێرە لەسەر PHP 8.2 جێبەجێ کرا و لەسەر 8.3، 8.4 و 8.5 هەمان شێوە کار دەکات. دواتر، لە Object-Relational Mappingـدا بە دەبەسترێن.
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
PHP Superglobalsسووپەرگلۆباڵەکانی PHP
Related lecturesوانە پەیوەندیدارەکان
- ORM: Working with Tables as ObjectsORM: کارکردن لەگەڵ خشتەکان وەک ئۆبجێکتەکان
- Including Files: include and requireهاوردەکردنی فایل: include و require
- Inheritance and Polymorphismبۆماوە و فرەشێوەیی
- Abstract Classes, Interfaces, Inner Classes and Enumsکلاسە ڕووتەکان، نێوبەستەکان، کلاسە ناوەکییەکان و ئینەمەکان
- Inheritance and Polymorphismبۆماوە و فرەشێوەیی
- Connecting PHP to MySQLپەیوەندیکردنی PHP بە MySQL
- Functionsکردارەکان
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- PHP Manual: Object Inheritance
- PHP Manual: The Basics (extends, signature compatibility rules)
- PHP Manual: Constructors (parent constructors are not called implicitly)
- PHP Manual: Class Abstraction
- PHP Manual: Object Interfaces
- PHP Manual: Traits
- PHP Manual: Final Keyword
- PHP Manual: The Override attribute (PHP 8.3)
- PHP Manual: Enumerations basics
- PHP Manual: Backed enumerations (from, tryFrom)
- PHP Manual: Namespaces overview
- PHP Manual: Autoloading Classes (spl_autoload_register)
- Composer documentation: Basic usage (composer.json, install, autoload)
- PHP-FIG: PSR-4 Autoloader
- PHP The Right Way: namespaces and dependency management
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.