Trait үндэс
PHP нь зөвхөн нэг эх классаас өвлөж болдог — энэ нь заримдаа хязгаарлалт мэт санагддаг. Жишээлбэл, Log бичих, Timestamp хадгалах зэрэг нийтлэг код олон адил бус классд хэрэгтэй байдаг — гэтэл нэг нийтлэг эх классаас өвлөх боломжгүй. Trait нь яг энэ хэрэгцээг хангадаг: нэгэнт бичсэн аргуудын багцыг хэдэн ч классд "хуулж оруулах" боломж олгодог. Trait нь interface биш, класс ч биш — харин кодын дахин ашиглалтын тусгай механизм юм.
Trait тодорхойлох ба ашиглах
trait түлхүүр үгээр тодорхойлж, use гэж классд оруулна:
<?php
trait Цагийн_тэмдэг {
private ?string $үүсгэсэн_огноо = null;
private ?string $өөрчилсөн_огноо = null;
public function үүсгэх_цаг_тэмдэглэх(): void {
$this->үүсгэсэн_огноо = date('Y-m-d H:i:s');
$this->өөрчилсөн_огноо = date('Y-m-d H:i:s');
}
public function өөрчлөх_цаг_шинэчлэх(): void {
$this->өөрчилсөн_огноо = date('Y-m-d H:i:s');
}
public function үүсгэсэн_огноо(): ?string {
return $this->үүсгэсэн_огноо;
}
public function өөрчилсөн_огноо(): ?string {
return $this->өөрчилсөн_огноо;
}
}
class Нийтлэл {
use Цагийн_тэмдэг;
public function __construct(public string $гарчиг) {
$this->үүсгэх_цаг_тэмдэглэх();
}
}
class Хэрэглэгч {
use Цагийн_тэмдэг;
public function __construct(public string $нэр) {
$this->үүсгэх_цаг_тэмдэглэх();
}
}
$нийтлэл = new Нийтлэл("PHP trait гэж юу вэ?");
echo $нийтлэл->үүсгэсэн_огноо(); // 2025-01-15 10:30:00
$хэрэглэгч = new Хэрэглэгч("Болд");
echo $хэрэглэгч->үүсгэсэн_огноо(); // 2025-01-15 10:30:00
?>
Цагийн_тэмдэг trait-ийн код нь Нийтлэл болон Хэрэглэгч хоёр классд яг адилхан байдлаар хуулагдан ажилладаг — гэтэл нэг газар л бичигдсэн.
Олон trait нэгэн зэрэг ашиглах
Нэг класс хэдэн ч trait ашиглаж болно — тэдгээрийг таслалаар жагсаана:
<?php
trait Лог_бичигч {
private array $лог = [];
public function лог_нэмэх(string $мэдэгдэл): void {
$this->лог[] = "[" . date('H:i:s') . "] " . $мэдэгдэл;
}
public function лог_харуулах(): void {
foreach ($this->лог as $мөр) {
echo $мөр . "\n";
}
}
}
trait Баталгаажуулагч {
public function хоосон_эсэх(string $утга, string $талбар): void {
if (trim($утга) === '') {
throw new InvalidArgumentException("{$талбар} хоосон байж болохгүй.");
}
}
public function урт_шалгах(string $утга, int $хамгийн_их, string $талбар): void {
if (strlen($утга) > $хамгийн_их) {
throw new InvalidArgumentException("{$талбар} {$хамгийн_их} тэмдэгтээс хэтрэхгүй байна.");
}
}
}
class Бүтээгдэхүүн {
use Лог_бичигч, Баталгаажуулагч; // Хоёр trait нэгэн зэрэг
private string $нэр;
private float $үнэ;
public function __construct(string $нэр, float $үнэ) {
$this->хоосон_эсэх($нэр, "Нэр");
$this->урт_шалгах($нэр, 100, "Нэр");
$this->нэр = $нэр;
$this->үнэ = $үнэ;
$this->лог_нэмэх("Бүтээгдэхүүн үүслээ: {$нэр}");
}
public function үнэ_өөрчлөх(float $шинэ_үнэ): void {
$this->лог_нэмэх("Үнэ өөрчлөгдлөө: {$this->үнэ} → {$шинэ_үнэ}");
$this->үнэ = $шинэ_үнэ;
}
}
$бараа = new Бүтээгдэхүүн("Ухаалаг утас", 1_200_000);
$бараа->үнэ_өөрчлөх(1_100_000);
$бараа->лог_харуулах();
// [10:30:01] Бүтээгдэхүүн үүслээ: Ухаалаг утас
// [10:30:02] Үнэ өөрчлөгдлөө: 1200000 → 1100000
?>
Мөргөлдөөнийг шийдэх
Хоёр trait нэг нэртэй арга агуулвал PHP мөргөлдөөн мэдэгдэх болно. insteadof болон as гэж шийдэж болно:
<?php
trait А_Лог {
public function лог(string $мэдэгдэл): void {
echo "[A] " . $мэдэгдэл . "\n";
}
}
trait Б_Лог {
public function лог(string $мэдэгдэл): void {
echo "[B] " . $мэдэгдэл . "\n";
}
}
class Систем {
use А_Лог, Б_Лог {
А_Лог::лог insteadof Б_Лог; // А_Лог-ийн лог()-г ашиглана
Б_Лог::лог as б_лог; // Б_Лог-ийнхийг ч дуудаж болно, өөр нэрээр
}
}
$с = new Систем();
$с->лог("Эхлэлт"); // [A] Эхлэлт
$с->б_лог("Туслах"); // [B] Туслах
?>
Trait дотор abstract арга
Trait нь abstract аргатай байж болно — ашиглаж буй класс тэр аргыг хэрэгжүүлэх үүрэгтэй:
<?php
trait Хэвлэгч {
// Классд хэрэгжүүлэх үүргийг үлдээнэ
abstract protected function агуулга(): string;
// Нийтлэг логик
public function хэвлэх(): void {
echo "--- хэвлэж байна ---\n";
echo $this->агуулга() . "\n";
echo "--------------------\n";
}
}
class Баримт {
use Хэвлэгч;
public function __construct(private string $текст) {}
// abstract арга заавал хэрэгжүүлнэ
protected function агуулга(): string {
return $this->текст;
}
}
(new Баримт("Энэ бол баримт бичиг."))->хэвлэх();
?>
Дараагийн хичээлд:
Static метод болон static шинж чанаруудыг судална. Объект үүсгэхгүйгээр шууд классаас дуудаж болдог static аргуудыг хэзээ, яагаад ашигладгийг жишээгээр тайлбарлана.