PHP / Static метод ба хувьсагч

Static метод ба хувьсагч

Одоог хүртэл бид аргуудыг дуудахын тулд эхлээд new гэж объект үүсгэдэг байсан. Гэвч заримдаа объект огт шаардлагагүй — зүгээр л нэг тооцоо хийх, мэдээлэл авах, тохиргоо шалгах зэрэг үйлдэлд. Static метод болон хувьсагч нь яг ийм тохиолдолд тусалдаг: объект үүсгэхгүйгээр классаас шууд дуудаж болдог. static нь классын $this-гүй хэсэг гэж ойлгоход хялбар.

Static метод тодорхойлох

static түлхүүр үгээр тодорхойлж, :: (double colon) операторыг ашиглан дуудна:

php
<?php
class Математик {
    // Static метод — объект үүсгэхгүйгээр дуудана
    public static function дөрвөлжин(float $тоо): float {
        return $тоо ** 2;
    }

    public static function хоёр_дахь_язгуур(float $тоо): float {
        if ($тоо < 0) {
            throw new InvalidArgumentException("Сөрөг тооны язгуур байхгүй.");
        }
        return sqrt($тоо);
    }

    public static function хамгийн_их(float ...$тоонууд): float {
        return max($тоонууд);
    }
}

// :: оператороор шууд дуудна — new хэрэггүй
echo Математик::дөрвөлжин(5);              // 25
echo Математик::хоёр_дахь_язгуур(16);      // 4
echo Математик::хамгийн_их(3, 7, 2, 9, 1); // 9
?>

:: операторыг scope resolution operator гэж нэрлэдэг. Static аргад $this байхгүй — учир нь объект үүсгэгдээгүй.

Static хувьсагч — классын нийтлэг санах ой

static хувьсагч нь тухайн классын бүх объектод нийтлэг — нэг объект өөрчилбөл бусад объектод тусгагдана. Объектын тоо тоолох, нийтлэг тохиргоо хадгалах зэрэгт их хэрэгтэй:

php
<?php
class Холболт_Тоолуур {
    private static int $нийт_холболт = 0;
    private static int $идэвхтэй    = 0;

    public function __construct(private string $нэр) {
        self::$нийт_холболт++;
        self::$идэвхтэй++;
        echo "Холболт нээгдлээ: {$this->нэр} (идэвхтэй: " . self::$идэвхтэй . ")\n";
    }

    public function __destruct() {
        self::$идэвхтэй--;
        echo "Холболт хаагдлаа: {$this->нэр} (идэвхтэй: " . self::$идэвхтэй . ")\n";
    }

    public static function нийт(): int {
        return self::$нийт_холболт;
    }

    public static function идэвхтэй(): int {
        return self::$идэвхтэй;
    }
}

$а = new Холболт_Тоолуур("үндсэн");  // идэвхтэй: 1
$б = new Холболт_Тоолуур("нөөц");    // идэвхтэй: 2
$в = new Холболт_Тоолуур("гуравдах"); // идэвхтэй: 3

echo "Нийт нээгдсэн: " . Холболт_Тоолуур::нийт() . "\n"; // 3
echo "Одоо идэвхтэй: " . Холболт_Тоолуур::идэвхтэй() . "\n"; // 3

unset($б); // идэвхтэй: 2
echo "Одоо идэвхтэй: " . Холболт_Тоолуур::идэвхтэй() . "\n"; // 2
?>

self:: нь $this-> шиг боловч static контекстод ашиглагддаг — "энэ класс" гэсэн утгатай.

Singleton загвар — нэг л объект

Static-ийн хамгийн алдартай хэрэглээ бол Singleton загвар юм: бүх програмд зөвхөн нэг объект үүсэхийг хангана. Тохиргоо, лог, өгөгдлийн сан холболт зэрэгт ашигладаг:

php
<?php
class Тохиргоо {
    private static ?Тохиргоо $жишээ = null;
    private array $утгууд = [];

    // private constructor — гаднаас new хэрэглэж болохгүй болно
    private function __construct() {
        $this->утгууд = [
            'апп_нэр'  => 'Mongolian Dev Platform',
            'хувилбар' => '1.0.0',
            'хэл'      => 'mn',
        ];
    }

    // Цорын ганц нэвтрэх цэг
    public static function авах(): static {
        if (static::$жишээ === null) {
            static::$жишээ = new static();
        }
        return static::$жишээ;
    }

    public function утга_авах(string $түлхүүр): mixed {
        return $this->утгууд[$түлхүүр] ?? null;
    }

    public function утга_тохируулах(string $түлхүүр, mixed $утга): void {
        $this->утгууд[$түлхүүр] = $утга;
    }
}

// Хаанаас ч дуудсан яг нэг л объект буцаана
$тохиргоо1 = Тохиргоо::авах();
$тохиргоо2 = Тохиргоо::авах();

$тохиргоо1->утга_тохируулах('хувилбар', '2.0.0');
echo $тохиргоо2->утга_авах('хувилбар'); // 2.0.0 — яг нэг объект тул хамт өөрчлөгдсөн

echo ($тохиргоо1 === $тохиргоо2) ? "Нэг объект мөн\n" : "Өөр объект\n"; // Нэг объект мөн
?>

static:: ба self:: — хожуу холболт

self:: нь код бичигдсэн классыг заадаг. static:: нь дуудагдаж буй бодит классыг заадаг — өвлөлттэй хамт ашиглахад ялгаа гарч ирдэг:

php
<?php
class Эх_Класс {
    public static function self_жишээ(): static {
        return new self();   // Үргэлж Эх_Класс үүсгэнэ
    }

    public static function static_жишээ(): static {
        return new static(); // Дуудагдаж буй классыг үүсгэнэ
    }

    public static function нэр(): string {
        return static::class; // Бодит классын нэр
    }
}

class Хүүхэд_Класс extends Эх_Класс {}

$а = Хүүхэд_Класс::self_жишээ();   // Эх_Класс объект
$б = Хүүхэд_Класс::static_жишээ(); // Хүүхэд_Класс объект

echo get_class($а); // Эх_Класс
echo get_class($б); // Хүүхэд_Класс
echo Хүүхэд_Класс::нэр(); // Хүүхэд_Класс
?>

static:: нь late static binding гэж нэрлэгддэг — өвлөлттэй классуудад factory метод бичихэд маш хэрэгтэй.

Factory метод — static-ийн практик хэрэглээ

Static методыг "үүсгэгч" болгон ашиглаж, объект үүсгэх янз бүрийн арга санал болгодог загвар түгээмэл байдаг:

php
<?php
class Огноо {
    private function __construct(
        private int $жил,
        private int $сар,
        private int $өдөр
    ) {}

    // Static factory методууд
    public static function өнөөдөр(): static {
        return new static(
            (int)date('Y'),
            (int)date('m'),
            (int)date('d')
        );
    }

    public static function мөрнөөс(string $огноо): static {
        [$жил, $сар, $өдөр] = explode('-', $огноо);
        return new static((int)$жил, (int)$сар, (int)$өдөр);
    }

    public function форматлах(): string {
        return "{$this->жил} оны {$this->сар}-р сарын {$this->өдөр}";
    }
}

$өнөөдөр = Огноо::өнөөдөр();
echo $өнөөдөр->форматлах(); // 2025 оны 1-р сарын 15

$тодорхой = Огноо::мөрнөөс('2024-12-31');
echo $тодорхой->форматлах(); // 2024 оны 12-р сарын 31
?>

Дараагийн хичээлд:

Namespace ойлголтыг судална. Том төслүүдэд классын нэрийн мөргөлдөөнийг хэрхэн зайлсхийдэг, use гэж хэрхэн оруулдаг, vendor namespace хэрхэн ажилладгийг жишээгээр үзнэ.