PHP / Trait үндэс

Trait үндэс

PHP нь зөвхөн нэг эх классаас өвлөж болдог — энэ нь заримдаа хязгаарлалт мэт санагддаг. Жишээлбэл, Log бичих, Timestamp хадгалах зэрэг нийтлэг код олон адил бус классд хэрэгтэй байдаг — гэтэл нэг нийтлэг эх классаас өвлөх боломжгүй. Trait нь яг энэ хэрэгцээг хангадаг: нэгэнт бичсэн аргуудын багцыг хэдэн ч классд "хуулж оруулах" боломж олгодог. Trait нь interface биш, класс ч биш — харин кодын дахин ашиглалтын тусгай механизм юм.

Trait тодорхойлох ба ашиглах

trait түлхүүр үгээр тодорхойлж, use гэж классд оруулна:

php
<?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
<?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
<?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
<?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 аргуудыг хэзээ, яагаад ашигладгийг жишээгээр тайлбарлана.