Need help with showing posts by category

Published 2 weeks ago by emco83

I'm struggling with this functionality in Laravel for a week, so guys please help me to solve this issue.

In my database I have these tables:

categories - id, created_at, updated_at

categories_translations id, category_id, locale, user_id, name, slug, created_at, updated_at

teams - id, created_at, updated_at

teams_translations - id, team_id, locale, title, slug, image, body, category_id, created_at, updated_at

EDIT: I've forgot to put category_id previously here.

I'm using Laravel translatable 3rd-party from here: https://github.com/dimsav/laravel-translatable

So far I've created relationship successfully with these tables and in my admin section I'm showing created teams with related categories. Also on frontpage I'm showing all teams in one page list. When I click on specific team, it shows the team page. That's great.

I'm struggling with showing teams by categories.

So, this is my code.

Category model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;

class Category extends Model
{
    use Translatable;

    public $translatedAttributes = ['name', 'slug', 'status', 'user_id', 'image', 'locale'];

    public function teams()
    {
        return $this->hasMany(Team::class);
    } 
}

CategoryTranslation model:

<?php

namespace App;

use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;

class CategoryTranslation extends Model
{
  use Sluggable;

  protected $touches = ['category'];
  public $timestamps = false;

  /**
   * Return the sluggable configuration array for this model.
   *
   * @return array
   */
  public function sluggable()
  {
      return [
          'slug' => [
              'source' => 'name',
              'onUpdate' => true
          ]
      ];
  }

  public function category() {
    return $this->belongsTo('App\Category');
  }
}

Team model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;

class Team extends Model
{
    use Translatable;

    public $translatedAttributes = ['title', 'slug', 'body', 'status', 'user_id', 'image', 'category_id', 'locale'];

    public function category() {
       return $this->belongsTo(Category::class);
    }
}

TeamTranslation model:

<?php

namespace App;

use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;

class TeamTranslation extends Model
{
    use Sluggable;

    protected $touches = ['team'];
    public $timestamps = false;

    /**
     * Return the sluggable configuration array for this model.
     *
     * @return array
     */
    public function sluggable()
    {
        return [
            'slug' => [
                'source' => 'title',
                'onUpdate' => true,
            ]
        ];
    }

    public function team() {
      return $this->belongsTo(Team::class);
    }
}

Part of my route:

Route::get('/team/category/{category}', ['uses' => '[email protected]', 'as' => 'teamCategory']);

Part of my TeamController:

    public function teamCategory(Category $category) {

      return $category;
    }

And here's my view:

@extends('layouts.frontend')

@section('title', __('t.team'))

@section('content')
  <div class="main team-list">
    <div class="container">
      <div class="col-md-3">
        <ul>
          @foreach($categories as $category)
            <li><a href="{{ route('teamCategory', $category->slug) }}">{{ $category->name }}</a></li>
          @endforeach
        </ul>
      </div>
      <div class="col-md-9">
        @foreach($teams as $team)
          @if($team->status == 1)
            <div class="row">
              <div class="image">
                <a href="{{ route('team.team', $team->slug) }}"><img width="120" src="{{ Storage::url($team->image) }}" ></a>
              </div>
              <div class="title">
                {{ $team->title }}
              </div>
              <div class="body">
                {!! str_limit($team->body, 300) !!}
              </div>
              <div class="category">
                  {{ $team->category ? $team->category->name : 'No category' }}
              </div>
              <a class="more">More</a>
            </div>
          @endif
        @endforeach
      </div>
    </div>
  </div>
@endsection

For a example I have a category name php, so in the view when I click on the category link, it goes to this url: mywebsite.com/en/team/category/php and it shows: Sorry, the page you are looking for could not be found.

I've tried to add this method in the Category model:

public function getRouteKeyName() {
      return 'name';
    }

and it shows:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'name' in 'where clause' (SQL: select * from `categories` where `name` = php limit 1)

Anyone help?

Best Answer (As Selected By emco83)
emco83

Thanks to @Funfare I've fixed this problem by using the second option suggested by @lostdreamer_nl : changing the route with this code:

Route::resource('/admin/categories', 'CategoriesController', ['parameters' => [
    'categories' => 'cat'
]]);

Now everything works great! :)

lostdreamer_nl

Looking at their documentation (the country example), the field you use to select the category should actually be a field in the category table.

In their example they have the country_code in the countries table, and the name etc. in the translations table.

Which makes sense, because the slug within the translations doesn't have to be unique anymore

But if you really want to, you can override the route model binding yourself: This should work (untested)

class TranslatableServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Route::bind('category', function ($value) {
            return Category::whereHas('translations', function ($query) use ($value) {
                $query->where('locale', App::getLocale())->where('slug', $value);
            })->first();
        });
        // add other translatable models here as well
    }
}

emco83

Hi, lostdreamer_nl. Thank you for your answer.

I've tried that too before.

With

public function teamCategory(Category $category) {

      return $category;
    }

I'm getting json result succesfuly , but when I tried like this:

public function teamCategory(Category $category) {

      return $category->teams;
    }

I'm getting this error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'teams.category_id' in 'where clause' (SQL: select * from `teams` where `teams`.`category_id` = 1 and `teams`.`category_id` is not null)

Any help?

lostdreamer_nl

In your original post you say:

teams: id, created_at, updated_at

teams_translations: id, team_id, locale, title, slug, image, body, created_at, updated_at

But in your Team model you have category_id as fillable, and a belongsTo relationship.

I dont see the category_id on your team table. Did you forget to put it in it's migration?

emco83

I have category_id in the team_translations table, otherwise I won't get any category values in the team list pages in the admin page. In Team model I'm passing the category_id in $translatedAttributes array.

I'm sorry, when I was writing here, I've missed to put category_id.

As far as I can see from the error it's not passing category_id properly into team_translations, because the function is looking at team table and there's no category_id column there.

emco83

lostdreamer_nl, I've followed your advice by adding the TranslatableServiceProvider class as a new service provider, I've deleted the category_id from team_translations table and moved that column to teams table. Finally I see results when I check with controller with this code:

public function teamCategory(Category $category) {

      return $category->teams;
    }

Later, I've set that with view and it's filtering great! Yay!!! :)

But, now when I enter the admin section and try to edit some category, for an example /en/admin/categories/10/edit I'm getting this error:

Missing argument 1 for App\Http\Controllers\CategoriesController::edit()

That's because of the custom service provider and in the admin section I'm using $id (10 is the id), not $category...damn...

Is there any fix for that? How can I set that service provider to act only on specific place?

lostdreamer_nl

So the problem is now that you have implemented custom binding, you'll need to use that binding everywhere (or use a different name in the route).

So there are 3 fixes:

  1. Use the slug in the admin panel instead of the ID
  2. in your routing for the admin panel, use a different name for {category} in the route
  3. have the server provider check if you're using a (translated) slug or it's ID

I think option 1 and 2 speak for themselves. For option 3 you could perhaps do the following in the service provider:


        Route::bind('category', function ($value) {
            if(intval($value) != 0) {
                // using the category id
                return $query->where('id', $value)->first();
            } else {
                // using the translated slug
                return Category::whereHas('translations', function ($query) use ($value) {
                    $query->where('locale', App::getLocale())->where('slug', $value);
                })->first();
            }
        });

emco83

Thank you very much for your explanation, lostdreamer_nl! Maybe I'm gonna try the first or second option later, but I've tried now using your code and I'm getting this error:

Undefined variable: query
lostdreamer_nl

@emco83 Oops, my mistake, that's what you get for posting untested code:. This should be working:


        Route::bind('category', function ($value) {
            if(intval($value) != 0) {
                // using the category id
                return Category::where('id', $value)->first();
            } else {
                // using the translated slug
                return Category::whereHas('translations', function ($query) use ($value) {
                    $query->where('locale', App::getLocale())->where('slug', $value);
                })->first();
            }
        });

emco83

I've just tried again and now I'm getting this error:

Property [id] does not exist on this collection instance. 
emco83

Thanks to @Funfare I've fixed this problem by using the second option suggested by @lostdreamer_nl : changing the route with this code:

Route::resource('/admin/categories', 'CategoriesController', ['parameters' => [
    'categories' => 'cat'
]]);

Now everything works great! :)

Please sign in or create an account to participate in this conversation.