Cara menggunakan contoh memcache php

Di bagian pertama seri ini, saya membahas cara membuat blog yang didukung Markdown di PHP menggunakan Slim Framework. Dalam bagian kedua dari seri ini, Anda akan mempelajari cara menggunakan Memcached dengan PHP untuk meningkatkan kinerja aplikasi.

Mari kita mulai

Prasyarat

Anda memerlukan yang berikut untuk mengikuti tutorial ini

  • Sedikit familiar dengan caching, Markdown, YAML, dan mesin template Twig
  • Beberapa keakraban dengan Standard PHP Library (SPL)
  • PHP 7. 4+ (idealnya versi 8) dengan ekstensi Memcached diinstal dan diaktifkan
  • Akses ke server Memcache
  • Komposer diinstal secara global

Mengapa menggunakan caching?

Sementara versi awal bekerja dengan sangat baik, kinerjanya akan mencapai puncaknya cukup cepat, karena — pada setiap permintaan — data blog dikumpulkan dari kumpulan file Markdown dengan frontmatter YAML di sistem file aplikasi, mem-parsing data artikel sebelum data blog dapat dirender

Untuk mengurangi kerumitan, artikel blog hanya berisi sedikit informasi. tanggal terbit, slug, sinopsis, judul, gambar utama, kategori, dan konten artikel

Data yang diurai ini kemudian digunakan untuk menghidrasi serangkaian entitas (

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
7) yang memodelkan artikel blog. Mengikuti hidrasi entitas, setiap artikel yang tidak diterbitkan (berdasarkan tanggal penerbitannya) akan disaring, dengan artikel yang tersisa diurutkan dalam urutan tanggal penerbitan terbalik

Saat membaca file dari sistem file aplikasi bukanlah pendekatan yang paling berkinerja, itu juga bukan pembunuh kinerja. Server penyebaran dapat dilengkapi dengan perangkat keras yang dioptimalkan dan disetel dengan benar, seperti teknologi NVMe

Namun, terlepas dari perangkat keras yang digunakan host, dan seberapa baik dioptimalkan, menjalankan pipa

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
8 penuh pada setiap permintaan, tidak efisien dan boros

Jauh lebih efisien untuk menjalankan pipeline lengkap hanya sekali, dan menyimpan data gabungan dalam cache. Kemudian, pada setiap permintaan berikutnya, hingga cache kedaluwarsa, baca data gabungan dan render. Melakukannya akan menghasilkan waktu muat halaman yang jauh lebih rendah, memungkinkan aplikasi untuk menangani lebih banyak pengguna, dan mengurangi biaya hosting dan dampaknya terhadap lingkungan

Ini tidak akan menjadi pendekatan yang paling dioptimalkan, tetapi ini akan meningkatkan kinerja aplikasi setidaknya 47%

Refactoring akan dipecah menjadi tiga bagian

  1. Refaktor proses agregasi data artikel untuk memungkinkan integrasi caching yang lebih mudah
  2. Tambahkan layanan Memcached ke wadah DI (Injeksi Ketergantungan) aplikasi
  3. Integrasikan layanan Memcached ke dalam proses agregasi data artikel

Kloning kode sumber

Untuk memulai, tiru kode sumber aplikasi ke mesin pengembangan Anda, ubah ke direktori kloning, dan periksa 1. 0. 0 tag dengan menjalankan perintah berikut

git clone https://github.com/settermjd/slimphp-markdown-blog.git slim-framework-markdown-blog
cd slim-framework-markdown-blog
git checkout tags/1.0.0 -b 1.0.0

1. 0. 0 tag berisi versi kode dari bagian pertama seri ini. Aplikasi, seperti yang akan terlihat di akhir tutorial ini, tersedia di tag

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
9

Instal dependensi yang diperlukan

Sekarang, Anda perlu menambahkan dua dependensi tambahan, sehingga aplikasi dapat meng-cache data artikel blog agregat di Memcached;

  • laminas-cache. Ini adalah implementasi caching luas yang menyerahkan implementasi cache spesifik ke pustaka adaptor yang mendasarinya
  • Laminas-cache-storage-adapter-memcached. Ini menangani interaksi dengan Memcached

Untuk menginstalnya, jalankan perintah di bawah ini, di direktori root proyek

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached

Caching terintegrasi

Jika Anda melihat publik/index. php, Anda akan melihat bahwa data blog awalnya digabungkan dalam layanan DI bernama

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
0

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);

Kemudian, diurutkan dan difilter dalam definisi penangan rute default

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});

Pendekatan ini bekerja, bagaimanapun, itu membuat penerapan caching bermasalah karena proses penyortiran dan pemfilteran dijalankan pada setiap permintaan, menghilangkan sebagian besar manfaat caching

Untuk memfaktor ulang aplikasi agar caching dapat diimplementasikan secara efisien akan memerlukan perubahan pada sejumlah aspek, tetapi tidak terlalu banyak

Refaktor BlogItem agar dapat diserialisasi

Hal pertama yang harus dilakukan adalah memfaktorkan ulang

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
7 sehingga dapat diserialkan. Alasannya adalah karena variabel anggota
$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
_2 adalah objek DateTime. Itu akan merusak serialisasi, karena banyak kelas PHP bawaan, termasuk DateTime,

Jadi, hal pertama yang harus dilakukan adalah

  1. Refaktor
    $container->set(
        ContentAggregatorInterface::class,
        fn() => (new ContentAggregatorFactory())->__invoke([
            'path' => __DIR__ . '/../data/posts',
            'parser' => new Parser(),
        ])
    );
    
    _2 menjadi string
  2. Refaktor metode
    $container->set(
        ContentAggregatorInterface::class,
        fn() => (new ContentAggregatorFactory())->__invoke([
            'path' => __DIR__ . '/../data/posts',
            'parser' => new Parser(),
        ])
    );
    
    _4 untuk mengembalikan objek
    $container->set(
        ContentAggregatorInterface::class,
        fn() => (new ContentAggregatorFactory())->__invoke([
            'path' => __DIR__ . '/../data/posts',
            'parser' => new Parser(),
        ])
    );
    
    5

Untuk melakukannya, di src/Blog/Entity/BlogItem. php, ubah tipe

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
_2 menjadi string, seperti pada contoh di bawah ini

private string $publishDate;

Kemudian, refactor metode

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
_7, hapus kasus inisialisasi khusus untuk
$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
2, dengan menggantinya dengan kode di bawah ini

public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}

Terakhir, perbarui metode

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
_4 agar cocok dengan kode di bawah ini, sehingga mengembalikan objek
$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
5 yang diinisialisasi dengan
$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
2

public function getPublishDate(): DateTime
{
    return new DateTime($this->publishDate);
}

Refaktor ContentAggregatorFilesystem

Hal berikutnya yang harus dilakukan adalah memperbarui src/Blog/ContentAggregator/ContentAggregatorFilesystem. php, untuk melakukan dua hal

  1. Pindahkan penyortiran item ke metode
    $app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
        $view = $this->get('view');
        /** @var ContentAggregatorInterface $contentAggregator */
        $contentAggregator = $this->get(ContentAggregatorInterface::class);
        $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
        $items = $contentAggregator->getItems();
        usort($items, $sorter);
        $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
            new ArrayIterator($items)
        );
        return $view->render(
            $response,
            'index.html.twig',
            ['items' => $iterator]
        );
    });
    
    _2
  2. Buat metode baru, bernama
    $app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
        $view = $this->get('view');
        /** @var ContentAggregatorInterface $contentAggregator */
        $contentAggregator = $this->get(ContentAggregatorInterface::class);
        $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
        $items = $contentAggregator->getItems();
        usort($items, $sorter);
        $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
            new ArrayIterator($items)
        );
        return $view->render(
            $response,
            'index.html.twig',
            ['items' => $iterator]
        );
    });
    
    _3, yang hanya mengambil item yang diterbitkan

Pertama, perbarui

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
_2 agar sesuai dengan versi di bawah ini

public function getItems(): array
{
    $sorter = new SortByReverseDateOrder();
    usort($this->items, $sorter);

    return $this->items;
}

Kemudian, tambahkan metode

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
_3 di bawah ini, setelah
$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
2

public function getPublishedItems(): \Traversable
{
    return new PublishedItemFilterIterator(
        new ArrayIterator($this->items)
    );
}

Kemudian, tambahkan pernyataan

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
_7 berikut ke bagian atas file

use ArrayIterator;
use MarkdownBlog\Iterator\PublishedItemFilterIterator;
use MarkdownBlog\Sorter\SortByReverseDateOrder;

Refactor wadah DI

Sekarang, saatnya untuk memfaktorkan ulang wadah DI. Untuk melakukan itu, di public/index. php, ganti layanan

$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
_0 dengan definisi berikut

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
0

Kemudian, tambahkan pernyataan

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
_7 berikut ke bagian atas file

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
_1

Kode yang direvisi dimulai dengan mengambil cache aplikasi dari layanan yang belum ditentukan bernama

private string $publishDate;
0. Kemudian, ia memeriksa apakah cache memiliki item bernama
private string $publishDate;
1, yang akan berisi artikel gabungan

Jika demikian, itu mengembalikan item yang di-cache. Jika tidak, saluran agregasi konten lengkap dijalankan, entitas

private string $publishDate;
2 gabungan disimpan dalam cache aplikasi, lalu dikembalikan

Tambahkan layanan Memcached ke wadah DI aplikasi

Selanjutnya, Anda perlu menambahkan layanan cache (

private string $publishDate;
0) yang menjadi sandaran layanan agregasi konten yang direfaktorisasi (
private string $publishDate;
1). Untuk melakukan itu, di public/index. php, tambahkan kode di bawah ini setelah service ________14______1

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
_2

Kemudian, ganti

private string $publishDate;
6 dengan nama host, atau alamat IP, dari server Memcached Anda

Setelah menambahkan layanan DI baru, tambahkan pernyataan

$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
7 berikut ke bagian atas file

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
_3

Kode menentukan server Memcached untuk terhubung, di

private string $publishDate;
8. Kemudian, ini menginisialisasi objek
private string $publishDate;
_9, yang menangani interaksi dengan server Memcached, meneruskannya ke
private string $publishDate;
8

Setelah itu, ia menggunakan objek tersebut untuk menginisialisasi dan mengembalikan objek

public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
1. Objek ini tidak benar-benar diperlukan. Namun, melakukan hal itu memberikan antarmuka yang agak sederhana ke objek
private string $publishDate;
9 , yang mungkin biasa Anda gunakan jika Anda telah mengimplementasikan caching sebelumnya; . e. , metode seperti
public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
3,
public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
4,
public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
5, dan
public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
6

Refactor handler rute default

Hal terakhir yang harus dilakukan, di public/index. php, adalah untuk memperbarui kedua definisi rute. Pertama, ganti rute default dengan kode berikut

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
_4

Rute mengambil layanan

private string $publishDate;
_1 DI, memanggil
$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    $view = $this->get('view');
    /** @var ContentAggregatorInterface $contentAggregator */
    $contentAggregator = $this->get(ContentAggregatorInterface::class);
    $sorter = new \MarkdownBlog\Sorter\SortByReverseDateOrder();
    $items = $contentAggregator->getItems();
    usort($items, $sorter);
    $iterator = new \MarkdownBlog\Iterator\PublishedItemFilterIterator(
        new ArrayIterator($items)
    );
    return $view->render(
        $response,
        'index.html.twig',
        ['items' => $iterator]
    );
});
3 untuk mengambil item yang diterbitkan, dan menyetel item yang diambil sebagai variabel templat
public function populate(array $options = [])
{
    $properties = get_class_vars(__CLASS__);
    foreach ($options as $key => $value) {
        if (array_key_exists($key, $properties) && !empty($value)) {
            $this->$key = $value;
        }
    }
}
9. Ini membuat definisi rute jauh lebih ringkas, dan tidak menggabungkan beberapa masalah

Selanjutnya, ganti definisi rute kedua, dengan kode berikut

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
5

Perubahan ini diperlukan agar, juga, mereferensikan layanan

private string $publishDate;
1 DI, bukan layanan
$container->set(
    ContentAggregatorInterface::class,
    fn() => (new ContentAggregatorFactory())->__invoke([
        'path' => __DIR__ . '/../data/posts',
        'parser' => new Parser(),
    ])
);
0 yang tidak digunakan lagi

Uji perubahannya

Dengan semua perubahan yang dilakukan, jalankan aplikasi dengan menjalankan perintah berikut di direktori root proyek

composer require \
    laminas/laminas-cache \
    laminas/laminas-cache-storage-adapter-memcached
_6

Jika Anda lebih suka menggunakan Docker Compose, yang saya lakukan, unduh direktori docker dan docker-compose. yml file dari 2. 0. 0 tag dari repositori GitHub proyek. Kemudian, mulai aplikasi dengan menjalankan perintah berikut

public function getPublishDate(): DateTime
{
    return new DateTime($this->publishDate);
}
2

Kemudian, di browser pilihan Anda — sebelum memuat aplikasi — buka tab atau jendela baru. Kemudian, buka Alat Pengembang dan buka tab Jaringan

Sekarang, buka http. // localhost. 8080. Setelah halaman selesai dimuat, pilih permintaan HTML dan, di panel sebelah kanan, ubah ke tab Waktu

Cara menggunakan contoh memcache php

Catat waktu "Diunduh". Dengan ini sebagai permintaan pertama, tidak ada konten yang di-cache yang tersedia, sehingga proses agregasi konten lengkap akan selesai, dan hasilnya disimpan di Memcached

Sekarang, muat ulang halaman. Waktu pengunduhan harus terasa kurang dari permintaan awal, konten diambil dari cache, bukan dikumpulkan

Cara menggunakan contoh memcache php

Dengan menggunakan caching, pemuatan halaman kedua sekitar 75% lebih cepat daripada pemuatan pertama, di mana cache tidak dihangatkan

Itulah cara meningkatkan performa aplikasi web PHP menggunakan Memcached

Kadang-kadang, seperti dalam aplikasi ini, terlepas dari manfaat kinerja yang dibawanya, caching tidak dapat langsung dilakukan. Namun, memungkinkan untuk kasus penggunaan khusus Anda, dengan sedikit pemfaktoran ulang, Anda dapat memanfaatkannya, dan mulai menangani lebih banyak pengunjung secara signifikan

Tertarik untuk meningkatkan kinerja aplikasi lebih jauh lagi?

Matthew Setter adalah Editor PHP di tim Twilio Voices dan (tentu saja) seorang pengembang PHP. Dia juga penulis Deploy With Docker Compose. Saat dia tidak menulis kode PHP, dia mengedit artikel PHP yang bagus di Twilio. Anda dapat menemukannya di msetter@twilio. com, Twitter, dan GitHub

Nilai posting ini

1 2 3 4 5

Penulis

  • Cara menggunakan contoh memcache php
    Matius Setter

Peninjau

  • Cara menggunakan contoh memcache php
    Niels Swimberghe


Pos terkait

Cara menggunakan contoh memcache php

Cara Mengirim Email di PHP pada tahun 2023

23 Januari 2023

Pada artikel ini, Anda akan mempelajari tiga cara berbeda untuk mengirim email dengan PHP;

Cara menggunakan contoh memcache php

Laravel Breeze vs Laravel Jetstream

20 Januari 2023

Pada artikel ini, Anda mendapatkan pengantar komprehensif untuk Laravel Breeze dan Jetstream, termasuk menginstalnya, diskusi tentang perbedaan dan persamaannya, dan kapan menggunakannya

Cara menggunakan contoh memcache php

Bantu Mereka Membantu Orang Lain

06 Desember 2022

Dalam tutorial ini, Anda akan mempelajari cara membuat aplikasi untuk membantu siapa saja dengan cepat menyumbang ke badan amal dan nirlaba menggunakan Slim Framework, Vue. js, dan Tailwind CSS

Cara menggunakan contoh memcache php

Pelajari Bahasa Maasai dengan Cara yang Menyenangkan

20 Oktober 2022

Dalam tutorial ini, Anda akan membuat aplikasi yang membantu Anda mempelajari bahasa Maasai dengan cara yang mudah dan menyenangkan menggunakan WhatsApp

Cara menggunakan contoh memcache php

Pub/Sub di Laravel - Pemahaman Mendalam

20 Oktober 2022

Dalam tutorial ini, Anda akan belajar tentang Pub/Sub, pola desain perangkat lunak berbasis pesan dan cara mengimplementasikannya di Laravel