Caching is essentieel voor het kunnen bieden van hoge prestaties en goede schaalbaarheid. Het meteen implementeren van de beste strategie voor caching is belangrijk in het voorkomen van trage API’s en lange laadtijden van pagina’s. Laravel is één van de meest populaire PHP frameworks, dus het implementeren van de optimale strategie voor caching in Laravel is onmisbaar voor een goede gebruikerservaring en optimale bedrijfsvoering.

In dit artikel kijken we naar verschillende strategieën voor het implementeren en manipuleren van caching in Laravel. Je zult dus meer lezen over hoe caching met Laravel werkt, verschillende uitdagingen bij Laravel caching, en hoe je caching kan regelen voor Laravel toepassingen.

Dit artikel zal het meest handig zijn als je al wat basiskennis hebt, waaronder het volgende:

Laten we beginnen aan de lijst!

Bekijk onze videogids over Laravel cache

Waarom is caching belangrijk?

Nu internetbedrijven zo hard aan het groeien zijn, kan je overal statistieken vinden over hoe belangrijk de laadtijden en prestaties van je website zijn voor je SEO, gebruikerservaring en conversies. En die lijden er allemaal onder als je geen caching gebruikt. En dat begint allemaal met een goede cachingstrategie.

Een online onderzoek toonde aan dat 1 seconde vertraging bij het laden, Amazon jaarlijks $1,6 miljard aan omzet zou kosten.

Een onderzoek van Google liet zien dat als de zoekresultaten zelfs maar een fractie van een seconde trager zouden zijn, mensen minder zouden zoeken (voor de data-nerds, een vertraging van 400 ms zou een vermindering van 0,44 procent in zoekvolume betekenen). En dit ongeduld is niet beperkt tot zoekresultaten: vier van de vijf internetgebruikers browsen door wanneer een video hapert tijdens het laden.

Het kleinste beetje traagheid in de laadtijden van je webpagina’s kan dus een flinke impact hebben op de gebruikerservaring van je bezoekers, en daarmee op de omzet van je bedrijf.

Wat is caching in Laravel?

Laravel biedt een robuust en eenvoudig te gebruiken implementatie van caching en verschillende backends voor caching. Met een Laravel caching kan je efficiënt en effectief wisselen tussen verschillende caching engines, zonder ook maar een regel code te hoeven schrijven.

Je kan de configuratie van de Laravel cache vinden in de map config/cache.php, alhoewel je meestal alleen het bestand .env nodig hebt om te wisselen van caching backend.

Een Laravel cache biedt ook verschillende praktische methoden die we kunnen gebruiken voor het implementeren van verschillende cachingstrategieën.

Laravel cache drivers met vergelijkingen

Laravel cache biedt geweldige caching backends en drivers. Afhankelijk van je use case, kun je schakelen tussen deze om je applicatieprestaties en -laadtijd te verbeteren.

Daarnaast biedt een Laravel cache ook een naadloze manier om een custom backend te bouwen en die met je Laravel cache te gebruiken, maar dat is eigenlijk alleen nodig als onderstaande lijst geen kant-en-klare oplossing biedt voor je toepassing.

Tijd voor de lijst van alle backends die de Laravel cache te bieden heeft.

1. File

De file driver is de standaard backend die de Laravel cache zal gebruiken wanneer er geen driver gespecificeerd is in het .env bestand.

De file backend is ontworpen om alle gecachete data op te slaan in een versleuteld bestand, te vinden onder storage/framework/. Laravel maakt een versleuteld bestand met alle data en de cache key, elke keer wanneer er nieuwe data gecachet wordt. Hetzelfde gebeurt wanneer de gebruiker de inhoud probeert op te halen. De Laravel cache doorzoekt de map voor de opgegeven key, en wanneer die gevonden wordt, dan stuurt het de inhoud terug.

Aangezien de file backend perfect werkt, en je tijd en moeite bespaart voor het installeren en configureren van externe drivers, kan het ook perfect voor development zijn. Het is sneller dan de data direct van de database server halen.

Om de file driver te gebruiken voeg je volgende code toe aan je .env bestand:

CACHE_DRIVER=file

2. Array

De array driver is een perfecte caching backend voor het uitvoeren van geautomatiseerde tests, en kan eenvoudig geconfigureerd worden met Github Actions, Jenkins, en dergelijke.

De array backend slaat de gecachete data op in een array in PHP en vereist dus geen installatie en configuratie van drivers. Dit werkt perfect voor geautomatiseerde tests, en het is ook iets sneller dan de file cache backend.

Om de array driver te gebruiken voeg je volgende code toe aan je .env bestand:

CACHE_DRIVER=array

3. Database

Wanneer je de database driver gebruikt, wordt data opgeslagen in het geheugen voor het huidige PHP proces. Daarom moet je een databasetabel aanmaken waar de gecachete data in opgeslagen kan worden. Daarnaast biedt de database caching schaalbaarheid, doordat de belasting voor de query wordt verdeeld van de backend naar verschillende frontends.

Je kan deze Artisan opdracht uitveren: php artisan cache:table om automatisch het database schema te maken dat de database driver nodig heeft.

De database driver wordt vooral vaak toegepast in situaties waarin je elke willekeurige software op je hostingplatform kan installeren.

Stel dat je een gratis hostingpakket hebt met een beperkt aantal opties. In dat geval raden we je aan om bij de file driver te blijven, aangezien de database driver anders meestal het zwakste punt in je toepassing is, en meer data proberen te duwen door een dergelijke bottleneck is in het algemeen een slecht idee.

Om de database driver te gebruiken voeg je volgende code toe aan je .env bestand:

CACHE_DRIVER=database

4. Redis

De redis driver gebruikt technologie voor caching binnen het geheugen, in dit geval de technologie Redis. Alhoewel het sneller is dan bovenstaande opties, moet je daarvoor wel externe software installeren en configureren.

Om de redis driver te gebruiken voeg je volgende code toe aan je .env bestand:

CACHE_DRIVER=redis

5. Memcached

Memcached staat bekend als de populairste in-memory caching optie. Als je wat extra serveronderhoud (extra services installeren en bijhouden) geen probleem vindt, dan is Memcached een goede optie voor een memory-based cache.

Om de memcached driver te kunnen gebruiken, moet de Memcached PECL package geïnstalleerd zijn.

Om de memcached driver te gebruiken voeg je volgende code toe aan je .env bestand:

CACHE_DRIVER=memcached 

De beste caching driver om te gebruiken en de prestaties van de caching driver hangen af van je toepassingen en de hoeveelheid data die opgehaald moet worden.

Gebruik en methoden van Laravel cache

Een Laravel cache biedt verschillende handige methoden waarmee je allerlei cachingstrategieën kan implementeren.

Hieronder een lijst met uitleg van de verschillende methoden (gecategoriseerd per use case):

  1. put()
  2. get()
  3. many()
  4. putMany()
  5. increment()
  6. decrement()
  7. forever()
  8. forget()
  9. flush()
  10. remember()
  11. rememberForever()

Opslaan in de cache

Het opslaan van nieuwe data in de caching is erg eenvoudig via de verschillende methoden, die allemaal een aantal optimale toepassingen hebben.

1. Cache::put()

Met deze methode kunnen drie parameters voor de key, de duur, en de data gecachet worden.

Zo kan je Cache::put() gebruiken:

Cache::put(key, data, duration)

$post = Post::find(1);

Cache::put('post_1', $post, 20);

De bovenstaande code zal het artikel inclusief unieke key 20 seconden lang opslaan.

2. Cache::putMany()

Deze methode slaat in één keer een array van data op in de cache, met dezelfde duur. Je kan hierbij twee parameters opgeven, namelijk data en seconds.

Zo kan je Cache::putMany() gebruiken:

Cache::putMany(data, duration) // syntax

$posts = Post::all();

Cache::putMany($posts, 20);
3. Cache::remember()

Ook deze methode is een handige manier om caching te implementeren. De Cache::remember() methode accepteert drie parameters, een keyseconds, en closure, waarbij de laatste wordt gebruikt als data niet gevonden kan worden.

We kijken even hoe je Cache::remember() kan toepassen:

Cache::remember(key, duration, closure) // syntax

Cache::remember('posts', 20, function(){
  return Post::all();
});

Laravel caching biedt ook de Cache::rememberForever() methode, waarbij je geen seconds parameter kan opgeven, omdat de data voor altijd opgeslagen wordt.

4. Cache::forever()

Deze methode slaat de data voor altijd op in de cache server, zonder specifieke duur. Je kan het met de volgende code implementeren:

Cache::forever(key, data)

$post = Post::find(1);

Cache::forever('post_1', $post);

Ophalen van data uit de cache

De methoden in deze categorie kunnen data ophalen die je eerder opgeslagen hebt in de cache. De methoden reageren verschillend wanneer de data niet gevonden kan worden.

1. Cache::get()

De methode haalt data op van de cache server door middel van een specifieke key. Je kan een item vinden door onderstaande code te gebruiken:

Cache::get(key) // syntax

$posts = Cache::get('posts');
2. Cache::many()

Deze methode lijkt sterk op Cache::putMany(). Het wordt gebruikt om een array van gecachete data in één keer op te halen, via een array van gecachete keys. Je kan een array uit de cache ophalen met de volgende code:

Cache::many(keys) // syntax

const $keys = [
  'posts',
  'post_1',
  'post_2'
];

$posts = Cache::many($keys);
3. Cache::remember()

Je kan deze methode ook gebruiken voor het ophalen van gecachete data, door de cache server te controleren op basis van de opgegeven key. Als de data opgeslagen is in de cache, zal dit opgehaald worden. Als dat niet het geval is, zal de data opgehaald worden bij de Database Server en gecachet worden. De methode lijkt sterk op de Cache::rememberForever() methode, maar dan met een extra seconds parameter in de Cache::remember() methode.

Verwijderen van items uit de cache

De methoden in deze categorie worden toegepast om items uit de cache te verwijderen. We groeperen ze op functie:

1. Cache::forget()

De methode verwijdert één item uit de cache, op basis van de key:

Cache::forget('key');
2. Cache::flush()

Deze methode leegt alle cache engines. Hiermee worden alle items overal in de cache verwijderd:

Cache::flush();

Verhogen of verlagen van waarden in de cache

Je kan de waarden van een integer die opgeslagen is in de cache verhogen of verlagen, via de volgende methoden:

Cache::increment('key');

Cache::increment('key', $amount);

Cache::decrement('key');

Cache::decrement('key', $amount);

Laravel caching biedt nog allerlei andere handige methoden die we nog niet hebben laten zien, maar dit zijn alvast de belangrijkste. Je kan een overzicht van alle methoden in de officiële Laravel cache documentatie vinden.

Opdrachten voor de caching uitgelegd

Laravel biedt opdrachten waarmee je eenvoudiger en sneller kan werken met een Laravel cache. Hieronder de lijst met beschikbare opdrachten en bijbehorende functies.

Legen van de Laravel cache

Deze opdracht wordt gebruikt om de Laravel cache te legen, nog voordat het verloopt, via de terminal of console. Je kan hiervoor onder meer de volgende opdracht gebruiken:

php artisan cache:clear

Legen van de route-cache

Deze opdracht kan je gebruiken om de route-cache van je Laravel toepassing te legen. Voor het legen van je route-cache kan je de volgende opdracht gebruiken:

php artisan config:cache

Legen van gecompileerde view-bestanden

Deze opdracht kan je gebruiken om de gecompileerde view-bestanden van je Laravel toepassing te legen. Je kan het met de volgende code implementeren:

php artisan view:clear

Database tabel

Wanneer je de database driver gebruikt, moet je een database schema maken om de gecachete data op te slaan, met de naam cache. Je kan ook de Artisan opdracht gebruiken om het juiste schema aan te maken:

php artisan cache:table

Strategieën voor caching in Laravel

Afhankelijk van de use case van je toepassing en precieze datastructuur, kan je meestal kiezen uit verschillende strategieën voor caching. Je kan zelfs een geheel eigen strategie maken die perfect bij jouw wensen past. Hieronder een aantal populaire caching-strategieën die je in je Laravel project kan gebruiken.

writeThrough

In de writeThrough strategie bevindt de cache server zich tussen de verzoeken en de database server, waardoor elke write bewerking via de cache server loopt voordat het naar de database server gaat. De writeThrough cachingstrategie lijkt sterk op de readThrough strategie.

Je kan deze strategie implementeren voor je Laravel cache met de volgende code:

public function writeThrough($key, $data, $minutes) {
    $cacheData = Cache::put($key, $data, $minutes)

    // Database Server is called from(after) the Cache Server.
    $this->storeToDB($cachedData)
    return $cacheData
}

private function storeToDB($data){
    Database::create($data)
    return true
}

writeBack (writeBehind)

Deze strategie is een meer gevorderde variant van de writeThrough strategie, doordat er een vertraging voor schrijfbewerkingen wordt toegevoegd.

Je kan dit ook de writeBehind strategie noemen, aangezien de vertraging wordt toegepast op de cache server, voordat de data naar de database server wordt verzonden.

Je kan deze strategie implementeren voor je Laravel cache met de volgende code:

$durationToFlush = 1; // (in minute)
 $tempDataToFlush = [];

  public function writeBack($key, $data, $minutes){
    return $this->writeThrough($key, $data, $minutes);
  }

  public function writeThrough($key, $data, $minutes) {
      $cacheData = Cache::put($key, $data, $minutes);
      $this->storeForUpdates($cacheData);
      return $cacheData;
  }

// Stores new data to temp Array for updating
  private function storeForUpdates($data){
    $tempData = {};
    $tempData['duration'] = this.getMinutesInMilli();
    $tempData['data'] = data;
    array_push($tempDataToFlush, data);
  }

// Converts minutes to millisecond
private function getMinutesInMilli(){
  $currentDate = now();
  $futureDate = Carbon(Carbon::now()->timestamp + $this->durationToFlush * 60000)
  return $futureDate->timestamp
}

// Calls to update the Database Server.
public function updateDatabaseServer(){
  if($this->tempDataToFlush){
    foreach($this->tempDataToFlush as $index => $obj){
      if($obj->duration timestamp){
        if(Database::create($obj->data)){
            array_splice($this->tempDataToFlush, $index, 1);
        }
      }
    }
  }
}

De writeBack methode callt de writeThrough methode, die de data opslaat op de cache server en in een tijdelijke array die later naar de database server gestuurd wordt, door middel van de updateDatabaseServer methode. Je kan een CronJob instellen om de database server elke vijf minuten te laten updaten.

writeAround

Met deze strategie kunnen write bewerkingen direct naar de database server gaan, zonder de cache server te updaten. Alleen tijdens de read bewerkingen wordt de cache server geüpdatet.

Stel dat een gebruiker een nieuw Article wil aanmaken, dan wordt het Article meteen op de database server opgeslagen. Wanneer een gebruiker het Article voor het eerst wil lezen, wordt het Article opgehaald van de database server en wordt de cache server bijgewerkt voor toekomstige verzoeken.

Je kan deze strategie implementeren voor je Laravel cache met de volgende code:

public function writeAround($data) {
    $storedData = Database::create($data);
    return $storedData;
}

public function readOperation($key, $minutes){
    $cacheData = Cache::remember($key, $minutes, function() {
      return Article::all();
    })
    return $cacheData;
}

Cache ernaast (Lazy Loading)

De database ligt bij deze strategie als het ware naast de verbinding, en de toepassing haalt de data eerste op bij de cache server. Als de data gevonden wordt, verzendt deze de data meteen naar de client. Zo niet, dus bij een miss, dan stuurt de databaseserver de data naar de cache server voor toekomstige verzoeken.

Je kan deze strategie implementeren voor je Laravel cache met de volgende code:

public function lazyLoadingStrategy($key, $minutes, $callback) {
  if (Cache::has($key)) {
      $data = Cache::get($key);
      return $data;
  } else {
      // Database Server is called outside the Cache Server.
      $data = $callback();
      Cache::set($key, $data, $minutes);
      return $data;
  }
}

Bovenstaande code toont de implementatie van een naastliggende cache, die equivalent is aan de methode Cache::remember.

Read Through

Deze strategie is technisch tegenovergesteld van de naastliggende cache methode. In dit geval bevindt de cache server zich tussen het verzoek van de client en de database server.

Verzoeken gaan direct naar de cache server, en deze moet de data vinden op de databaseserver, als het niet in de cache server zelf gevonden kan worden.

Je kan deze strategie implementeren voor je Laravel cache met de volgende code:

public function readThrough($key, $minutes) {
      $data = Cache::find($key, $minutes);
      return $data;
}

private function find($key, $minutes){
    if(Cache::has($key);){
      return Cache::get($key);
    }

    // Database Server is called from the Cache Server.
    $DBdata = Database::find($key);
    Cache:put($key, $DBdata, $minutes);
    return $DBdata;
}

En dat is het al weer! We hebben nu een aantal populaire strategieën voor caching besproken die je meteen kan implementeren in je volgende Laravel toepassing. Onthoud daarbij dat je ook een geheel eigen strategie kan bedenken, als dat beter bij je project past.

De User Interface van een Laravel app cachen

Het cachen van de UI van een Laravel app staat bekend als Full Page Caching (FPC). Deze term slaat op het proces waarbij de HTML respons van een applicatie in de cache opgeslagen wordt.

Dit is ideaal voor toepassingen waarin de dynamische HTML data niet constant verandert. Je kan de HTML respons dan caching voor een snellere weergave en verwerking van de HTML.

We kunnen FPC implementeren met de volgende code:

class ArticlesController extends Controller {
    public function index() {
        if ( Cache::has('articles_index') ) {
            return Cache::get('articles_index');
        } else {
            $news = News::all();
            $cachedData = view('articles.index')->with('articles', $news)->render();
            Cache::put('articles_index', $cachedData);                                         
            return $cachedData;           
        }  
    }
}

Wellicht valt je al meteen op dat we controleren of de articles_index pagina al bestaat op de cache server. Vervolgens sturen we de complete pagina terug door deze te tonen met de Laravel methoden view() en render().

Is de pagina niet aanwezig, dan verwerken we de pagina en slaan we de output op in de cache server voor volgende verzoeken, voordat we de verwerkte pagina doorgeven aan de browser.

Een Laravel app bouwen

Tijd om in de praktijk te brengen wat we tot nu toe hebben geleerd, door een nieuw Laravel project te maken, met een Laravel cache.

Als je nog nooit Laravel hebt gebruikt, kan je lezen wat Laravel is, en eens naar onze lijst met de beste Laravel tutorials kijken om er mee te beginnen.

Laravel klaarzetten

Allereerst maken we een nieuwe Laravel instance door middel van onderstaande opdracht. Je kan eventueel de officiële documentatie gebruiken voor meer informatie.

Open je console en ga naar de map waar je je PHP projecten bewaart, voordat je onderstaande opdrachten uitvoert. Zorg ervoor dat Composer goed geïnstalleerd en geconfigureerd is.

composer create-project laravel/laravel fast-blog-app

// Change directory to current Laravel installation
cd fast-blog-app

// Start Laravel development server.
php artisan serve

Configureren en vullen van de database

Nu moeten we de database opzeten, een nieuw Article model maken, en 500 datapunten simuleren om de database te kunnen testen.

Open je database client en maak een nieuwe database aan. Wij doen dit met de naam fast_blog_app_db en vullen vervolgens de gegevens van de database in bij het .env bestand:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=fast_blog_app_db
DB_USERNAME=//DB USERNAME HERE
DB_PASSWORD=//DB PASSWORD HERE

Daarna voeren we onderstaande opdracht uit om de migratie te voltooien en meteen het Article model te maken:

php artisan make:model Article -m

Open de nieuw aangemaakte migratie die je kan vinden onder database/migrations/xxx-create-articles-xxx.php en plak de volgende code:

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

En vervolgens kan je onderstaande opdracht uitvoeren om een nieuwe seeder te maken voor testdata:

php artisan make:seeder ArticleSeeder

Open het nieuwe bestand onder database/seeders/ArticleSeeder.php en plak onderstaande code daarin:

<?php
namespace Database\Seeders;
use App\Models\Article;
use Illuminate\Database\Seeder;
class ArticleSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Article::factory()->count(500)->create();
    }
}

Open het bestand DatabaseSeeder.php in dezelfde map en voeg daar deze code aan toe:

<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(ArticleSeeder::class);
    }
}

En dan voer je deze opdracht uit om een nieuwe “factory” aan te maken:

php artisan make:factory ArticleFactory

Open dit nieuwe bestand vanuit database/factories/ArticleFactory.php en plak weer de volgende code:

<?php
namespace Database\Factories;
use App\Models\Article;
use Illuminate\Database\Eloquent\Factories\Factory;
class ArticleFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Article::class;
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->text(),
            'description' => $this->faker->paragraph(20)
        ];
    }
}

Nu kan je onderstaande opdracht uitvoeren om het nieuwe schema te migreren en meteen testdata aan te maken:

php artisan migrate --seed

Maken van de Article controller

Nu zullen we een controller maken en de routes instellen voor het afhandelen van verzoeken en ophalen van data op basis van bovenstaande model.

Voer de volgende opdracht uit om een nieuwe ArticlesController te maken in de map app/Http/Controllers :

php artisan make:controller ArticlesController --resource

Open het bestand en voeg de volgende code toe aan de class:

// Returns all 500 articles with Caching
public function index() {
  return Cache::remember('articles', 60, function () {
      return Article::all();
  });
}

// Returns all 500 without Caching 
public function allWithoutCache() {
  return Article::all();
}

Daarna kan je het bestand api.php openen dat je in de map routes/ kan vinden, om onderstaande code te plakken, zodat we een eindpunt hebben waar we onze data vandaan kunnen halen:

Route::get('/articles', 'ArticlesController@index');

Route::get('/articles/withoutcache', 'ArticlesController@allWithoutcache');

Testen van de prestaties

Als laatste testen we de prestaties van de respons van onze app met en zonder de Laravel cache.

Dit screenshot laat de responstijden van de API zien mét een geïmplementeerde cache:

Laravel API responstijden met cache.
Laravel API responstijden met cache.

En dit screenshot laat de responstijden van een API zonder cache zien, waarbij je kan zien dat deze tijden ten op zichte van de gecachete versies wel 5000% hoger liggen:

Laravel API responstijden zonder cache.
Laravel API responstijden zonder cache.

Samenvatting

In dit artikel hebben we gekeken naar verschillende strategieën voor het implementeren en bewerken van Laravel caching door een nieuw project te bouwen, de responstijden te benchmarken, en de resultaten te vergelijken.

Je hebt ook gelezen hoe je verschillende caching drivers en methoden in Laravel kan gebruiken. Daarnaast hebben we verschillende strategieën voor caching besproken zodat je kan bepalen wat het best bij jou past.

Voor meer Laravel, lees eens onze selectie van de beste Laravel tutorials. Of je nou een beginner bent, of al een gevorderde Laravel developer, er zit voor iedereen wat bij.

Als je nog vragen hebt over Laravel caching, laat het ons dan vooral weten in de reacties.

Solomon Eseme

I am a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Masteringbackend.com. Follow me on Twitter, LinkedIn, and About Me