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

thetanaz's avatar

Having trouble discerning the proper Laravel application structure

So I will start by admitting that a month or two ago I knew zip about Laravel, and I have been learning it through some great videos on this very platform + the help of AI LLMs. While this has accelerated my progress it has also left me with quite a few gaps in my knowledge. Like for example, how to properly split application logic while not making it impossible to track down. Let me give you an example:

public function showByCategory(Request $request, $categorySlug, $subCategorySlug = null, $itemSlug = null)
    {
        $category = Category::where('name', $categorySlug)->firstOrFail();
        $subCategory = null;
        $item = null;

        if ($subCategorySlug) {
            $subCategory = SubCategory::where('name', $subCategorySlug)
                ->where('category_id', $category->id)
                ->firstOrFail();
        }

        if ($itemSlug && $subCategory) {
            $item = Item::where('name', $itemSlug)
                ->where('sub_category_id', $subCategory->id)
                ->firstOrFail();
        }

        $listingsQuery = Listing::query();

        if ($item) {
            $listingsQuery->where('item_id', $item->id);
        } elseif ($subCategory) {
            $listingsQuery->whereHas('item', function ($query) use ($subCategory) {
                $query->where('sub_category_id', $subCategory->id);
            });
        } else {
            $listingsQuery->whereHas('item', function ($query) use ($category) {
                $query->whereHas('sub_category', function ($subQuery) use ($category) {
                    $subQuery->where('category_id', $category->id);
                });
            });
        }

        $user = $request->user();
        $listings = $listingsQuery
            ->with('item.sub_category')
            ->withCount([
                'favoritedBy as is_favorited' => function ($query) use ($user) {
                    if ($user) {
                        $query->where('user_id', $user->id);
                    }
                }
            ])
            ->orderBy('updated_at', 'desc')
            ->paginate(20);

        if ($listings->currentPage() > $listings->lastPage()) {
            abort(404);
        }

        $listings->getCollection()->transform(function ($listing) {
     
            $listing->is_favorited = $listing->is_favorited > 0;
            return $listing;
        });

        return Inertia::render('CategoryListings', [
            'listings' => $listings,
            'currentCategoryBG' => $category->bg_name,
            'currentCategory' => $categorySlug,
            'currentSubCategory' => $subCategory ? $subCategory->name : null,
            'currentSubCategoryBG' => $subCategory ? $subCategory->bg_name : null,
            'currentItem' => $item ? $item->name : null,
            'currentItemBG' => $item ? $item->bg_name : null
        ]);
    }

This method is in my "ListingController" and it's used to show all the listings from any given category/subcategory or item. It's quite cumbersome and there is a whole lot of querying going on. Today I read on a random forum thread that "queries should always be stored in the Model they're..querying" but given the fact that this is a custom query that's pretty much only going to be used once I don't see how it would be better if it were stored in the Listing model. Right now I am only using models to define the fillable and protected properties, if something should be cast a certain way and the relationships between different tables. Here is the Listing Model as an example:

<?php

namespace App\Models;

use App\Condition;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Listing extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'item_id',
        'price',
        'sold',
        'title',
        'expires_at',
        'rating',
        'photos',
        'description',
        'views',
        'negoatiable',
        'condition',

    ];

    protected $casts = [
        'sold' => 'boolean',
        'expires_at' => 'date',
        'photos' => 'array',
        'condition' => Condition::class,
    ];
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function item()
    {
        return $this->belongsTo(Item::class);
    }

    public function favoritedBy()
    {
        return $this->belongsToMany(User::class, 'favorite_listings')->withTimestamps();
    }
    public function reports()
    {
        return $this->hasMany(ListingReport::class);
    }
}

I want to know if my project structure is bad, if I should have more methods in my models instead of my controllers, or if it's just a matter of taste? I don't want to be one of those devs that's done things the wrong way for years only to realize it when they arrive in a professional environment and feel like a dummy.

0 likes
8 replies
martinbean's avatar

@thetanaz I’m not sure I fully agree with the statement “queries should always be stored in the Model they’re querying”. Queries can be found in many other places: repositories, action classes, service classes, and so on.

Looking at your controller, it seems you’re doing too much. You have multiple optional parameters (category, sub-category, item) which means it’s concerned with at least two things: showing categories, and also showing an item. You should have two distinct controllers (a CategoryController and an ItemController for these purposes. You’ll find that’ll cut down the amount of code as code will only be where it needs to be.

2 likes
thetanaz's avatar

@martinbean So in my example it would actually be 3 controllers - Category,subcategory and item and I would split the slugs and their appropriate methods to those controllers. Gotta admit I've never even used repositories action classes and service classes. Laravel feels confusing because there are at least 2-3 ways to achieve the same goal so I never know what to choose.

martinbean's avatar

Laravel feels confusing because there are at least 2-3 ways to achieve the same goal so I never know what to choose.

@thetanaz This isn’t a Laravel-specific problem. There are multiple ways to achieve the same goal in any framework or language.

2 likes
jlrdw's avatar

@thetanaz I try to keep controllers slim.

I for example in one app have a complex search, I made an action class to return the query string required to the controller:

$qv = Check::search();

Returns an array of query vars.

Sometimes there is no "right" way as long as you are using proper security.

Also see https://laracasts.com/series/jeffreys-larabits/episodes/40

3 likes
thetanaz's avatar

@jlrdw I saw that video the other day and it kinda dissuaded me from splitting the method I gave as an example into separate pieces, because I know what it does, and also all scenarios render the same inertia page (whether the user has picked category only, or a subcategory, or an item) so it made sense for all of it to be in the same place.

martinbean's avatar

all scenarios render the same inertia page (whether the user has picked category only, or a subcategory, or an item)

@thetanaz That doesn’t really make any sense. Surely there’s some difference between viewing a category, and viewing a single item?

If I go to a news website, I don’t get the same view when viewing an article category (which would list many articles), and then viewing a single article.

1 like
thetanaz's avatar

@martinbean This is my bad on the naming. An Item is not an individual listing. So let me explain. If a category is House appliances, a subcategory is kitchen appliances, an item would be ovens, or microwaves. So it's not an item as in a singular item but as the type of item. The listing is the singular property that is unique. What I'm doing in the inertia view is just sorting the listings based on category/subcategory or item and displaying a small navigation section above the listings that shows the category > subcategory (if selected) >item (if selected). Perhaps I should've chosen a better naming than item, maybe product type idk.

Please or to participate in this conversation.