Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Troj's avatar
Level 4

Recently viewed session based

I'm trying to return recently viewed items, on the homepage, for the visitor based on their session. But i think because of the duplicate session query, even when it's not the database driver i'm using, the results from the session are always returned twice.

I asked in a related question about the duplicate session query. But it seems like there's not much i can do about that.

@bobbybouwmann's answered on a similar question about the duplicate session: "Anyway, you're not the only one with this issue. It seems to happen in the StartSession middleware and I don't think you can do anything about it".

Does anyone know how prevent the same result to appear twice?

In my controller

session()->push('deals.recently_viewed', $deal->getKey());

My complete controller

<?php

namespace App\Http\Controllers;

use App\Models\Deal;
use App\Filters\DealFilters;
use App\Models\Category;
use App\Models\City;
use App\Models\Address;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Http\Request;

class DealController
{

    public function show($countrySlug, $citySlug, Category $category, Address $address, Deal $deal)
    {
        $city = City::where('country_slug', $countrySlug)->where('slug', $citySlug)->firstOrFail();

        session()->push('deals.recently_viewed', $deal->getKey());

    	$mediaItems = $deal->getMedia('multi_collection');
    	$imagesPerChunk = count($mediaItems);

        return view('deals.show', [
            'city' => $city,
            'category' => $category,
            'address' => $address,
            'deal' => $deal,
            'mediaItems' => $mediaItems,
            'imagesPerChunk' => $imagesPerChunk
        ]);

    }

}

My composer

<?php

namespace App\Http\View\Composers;

use App\Models\Deal;
use Illuminate\View\View;

class InterestedComposer

{

    public function compose(View $view)
    {
        $deals = session()->get('deals.recently_viewed');

        $view->with([
            'recentlyViewed' => Deal::find($deals),
        ]);

    }
}

Blade

@foreach(Session::get('deals')['recently_viewed'] as $deals)
    <span>{{ $deals }} </span>
@endforeach

The result when i visit a page is the id of that deal will be returned twice. That obviously not what i want.

4  			4
0 likes
13 replies
MichalOravec's avatar

In the service provider where you register your composer add this to register method

But it probably doesn't cause that problem.

/**
 * Register the application services.
 *
 * @return void
 */
public function register()
{
    $this->app->singleton(InterestedComposer::class);
}

And I guess you have to check existense of that id in deals.recently_viewed, if it exists there you have to remove it and add it to the end.

Troj's avatar
Level 4

@michaloravec Ok i tried it but the problem still exists, do you have an idea where the problem could be? Is it not because of the duplicate session query you mean? I don't understand what you mean with: ".. if it exists there you have to remove it and add it to the end."

So i added the check if deals exists in the session.

@if(session()->has('deals'))

And i added your code to the register method in the appserviceprovider. Can you explain me also where the app is referring to?

<?php

namespace App\Providers;

use App\Models\Category;
use App\Http\View\Composers\CategoryComposer;
use App\Http\View\Composers\CityComposer;
use App\Http\View\Composers\InterestedComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(InterestedComposer::class);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        \View::composer(['includes.categories.*'], CategoryComposer::class);
        \View::composer(['includes.cities.*'], CityComposer::class);
        \View::composer(['includes.interested.*'], InterestedComposer::class);

        Blade::directive('money', function ($amount) {
            return "<?php echo number_format($amount, 2, ',', '.'); ?>";
        });

    }
}
MichalOravec's avatar
Level 75

Don't use wildcard *, be more specific to which view you want to add it.

View::composer([
    'includes.interested.SOMETHING'
], InterestedComposer::class);

The code in the controller should be something like this

if (in_array($deal->getKey(), $deals = session()->get('deals.recently_viewed', []))) {
    $index = array_search($deal->getKey(), $deals);

    session()->pull("deals.recently_viewed.{$index}");
}

session()->push('deals.recently_viewed', $deal->getKey());
Troj's avatar
Level 4

@michaloravec Nice now it returns the viewed id just once, so that works.

Is it the getKey that returns the id of the object? Is there a way i could get not only the id but also the name and description of that deal?

MichalOravec's avatar

Yes getKey() returns just primary key of your table, usually an id.

If you want to do that, just add $deal to the session, but then you need to use a collection to apply same logic in_array, array_search etc.

Troj's avatar
Level 4

@michaloravec Hmm i cant figure it out, i'm trying to do something but i can't get it right, could you give me another hint?

<?php

namespace App\Http\Controllers;

use App\Models\Deal;
use App\Filters\DealFilters;
use App\Models\Category;
use App\Models\City;
use App\Models\Address;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Http\Request;

class DealController
{

    public function show(Request $request, $countrySlug, $citySlug, Category $category, Address $address, Deal $deal)
    {
        $city = City::where('country_slug', $countrySlug)->where('slug', $citySlug)->firstOrFail();

        if (in_array($deal->getKey(), $deals = session()->get('deals', []))) {
            $index = array_search($deal->getKey(), $deals);

            $request->session()->pull("deals.{$index}");
        }

         session()->push('deals', $deal);

        $mediaItems = $deal->getMedia('multi_collection');
        $imagesPerChunk = count($mediaItems);

        return view('deals.show', [
            'city' => $city,
            'category' => $category,
            'address' => $address,
            'deal' => $deal,
            'mediaItems' => $mediaItems,
            'imagesPerChunk' => $imagesPerChunk
        ]);

    }

}
MichalOravec's avatar
$dealIds = collect(session()->get('deals', []))->pluck('id');

if ($dealIds->contains($deal->getKey()) && $index = $dealIds->search($deal->getKey())) {
    session()->pull("deals.{$index}");
}

session()->push('deals', $deal);
1 like
Troj's avatar
Level 4

@michaloravec In some cases now i'm getting "Serialization of 'Closure' is not allowed" error, after i added your code and when visiting a deals page. Any ideas why, or should i create a new thread for it

vendor/laravel/framework/src/Illuminate/Session/Store.php:129

    public function save()

    {

        $this->ageFlashData();

 

        $this->handler->write($this->getId(), $this->prepareForStorage(

            serialize($this->attributes)

        ));
Troj's avatar
Level 4

@michaloravec I've tried all day to make this work, but i failed. When i use your code and die and dump then i get this huge array. This is probably why the payload became too big. I need just a couple of fields from the database so i can show the visitor what deals they've seen before (title, image, price etc).

I thought maybe it's better to use the global session to just get the ID for a deal and then based on that retrieve data from the database. But i can't find anything about this, so i'm not sure if it's even possible.

MichalOravec's avatar

The first code what I gave you is about ids. If you have an array with ids then just use whereIn

$deals = Deal::whereIn('id', session()->get('deals.recently_viewed', []));

Please or to participate in this conversation.