i'm working on a graphql API using Laravel GraphQL.
As shown in the documentation "Privacy" section, it should be possible to add callback function to a GraphQLType fields privacy attribute. The field is supposed to return null, when the callback returns false.
Similar to the example in the laravel graphql Docs, i've added a privacy callback like so:
public function fields(): array {
return [
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
];
}
It appears to me, that this callback function never gets called.
I read something about a possible requirement, that i should resolve my query using the $getSelectFields function to query the $fields manually $with the selected columns. But unfortunately the $select
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
$fields = $getSelectFields();
$with = $fields->getRelations(); // empty array
$select = $fields->getSelect(); // empty array
return User::select($select)->with($with)->get();
}
In my case this does not make any difference.
In my query resolver i do as following:
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
/** #var SelectFields $fields */
$fields = $getSelectFields();
$select = $fields->getSelect();
$with = $fields->getRelations();
exit(var_dump($fields)); // #RESULT
}
My result looks like this:
object(Rebing\\GraphQL\\Support\\SelectFields)#4668 (2) {
[\"select\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
[\"relations\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
}
So my question is: "How do i use the privacy attribute callback in Laravel Rebing GraphQL?"
I'm using:
PHP 7.3
Laravel 7.17
Rebing Graphql Laravel 5.1
Thanks in advance,
greets Jules
Some more Details about my use case
EpUser.php
namespace App\GraphQL\Type;
use App\CommunityImage;
use App\User;
use Carbon\Carbon;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Facades\Auth;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class EpUser extends GraphQLType {
protected $attributes = [
'name' => 'EpUser',
'description' => 'A type',
'model' => User::class,
];
public function fields(): array {
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user',
'privacy' => function(User $user): bool {
return false;
}
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
'firstName' => [
'type' => Type::string(),
'description' => 'The firstName of user'
],
'lastName' => [
'type' => Type::string(),
'description' => 'The lastName of user'
],
'fullName' => [
'type' => Type::string(),
'description' => 'The fullName of user',
'selectable' => false,
'resolve' => function(User $user) {
return $user->firstName . " " . $user->lastName;
}
],
'gender' => [
'type' => Type::string(),
'description' => 'The gender of the user'
],
'isOnline' => [
'type' => Type::boolean(),
'description' => '',
'selectable' => false,
'resolve' => function(User $user, $args) {
return $user->isOnline();
}
]
];
}
[...]
And this is the UsersQuery which should respond with a user pagination object, that contains an array of users with a privacy attribute:
UsersQuery.php
namespace App\GraphQL\Query;
use App\Artist;
use App\FilePath;
use Closure;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\User;
class UsersQuery extends Query {
protected $attributes = [
'name' => 'UsersQuery',
'description' => 'A query',
'model' => User::class,
];
public function type(): Type {
return GraphQL::type('userPagination');
}
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = NULL, $getSelectFields = NULL): bool {
return Auth::check();
}
public function args(): array {
return [
'id' => [
'type' => Type::int(),
'description' => 'The id of the user'
],
'slug' => [
'type' => Type::string(),
'description' => 'The slug of the user'
],
'pagination' => [
'type' => Type::nonNull(GraphQL::type('paginationInput')),
'description' => 'The pagination of the users to query',
'rules' => 'required',
],
'search' => [
'type' => Type::string(),
'description' => 'a string to search for users'
],
'roles' => [
'type' => Type::listOf(Type::string()),
'description' => 'The roles of the user',
'rules' => 'sometimes|required|array|in:user,developer,administrator'
]
];
}
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
if(isset($args['id']) || isset($args['slug'])) {
if(isset($args['slug'])) {
$user = User::where('slug', $args['slug'])->first();
} else {
$user = User::find($args['id']);
}
return [
'items' => $args['pagination']['limit'] > 0 && $user ? [$user] : NULL,
'itemTotal' => $user ? 1 : 0
];
}
$sortBy = $args['pagination']['sortBy'] ?? 'id';
$sortByDesc = isset($args['pagination']['sortByDesc']) ? $args['pagination']['sortByDesc'] : true;
$sortByType = $sortByDesc ? 'desc' : 'asc';
$search = false;
if(isset($args['search']) && $args['search']) {
$search = true;
$query = User::search($args['search']);
} else {
$query = User::query();
}
if(!empty($sortBy)) {
$query->orderBy($sortBy, $sortByType);
}
// Todo: eloquent search can't serach for whereHas
if(isset($args['roles']) && !$search) {
if(is_array($args['roles'])) {
foreach($args['roles'] as &$role) {
$query->whereHas('roles',
function($q) use ($role) {
$q->where('name', $role);
});
}
} else {
$query->whereHas('roles',
function($q) use ($args) {
$q->where('name', $args['roles']);
});
}
}
if($search) {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->getWithLimitAndOffset($args['pagination']['limit'],
$args['pagination']['offset'])
];
} else {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->limit($args['pagination']['limit'])->offset($args['pagination']['offset'])->get()
];
}
return $userPaginationObject;
}
}
Related
I use laravel 8 & have 3 table:
Products, ProductPrice & ProductsPublisher:
this is my Products model for this relationship:
public function lastPrice(){
return $this->hasMany(ProductPrice::class)->where('status','active')->orderBy('created_at','DESC')->distinct('publisher_id');
}
and this is my productsPrice model for publisher relationship:
public function getPublisher(){
return $this->belongsTo(ProductsPublisher::class,'publisher_id');
}
now, i want to use laravel resource for my api, i wrote products resource:
public function toArray($request)
{
return [
'id' => $this->id,
'price' => lastPrice::make($this->lastPrice),
'status' => $this->status,
'slug' => $this->slug,
'title' => $this->title,
'description' => $this->description,
'txt' => $this->txt,
'lang' => $this->lang,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
but in lastPrice resource, when i wrote like this:
return [
'id' => $this->id,
'main_price' => $this->main_price
];
it give me this error:
Property [id] does not exist on this collection instance.
when i use this code:
return parent::toArray($request);
get response but because i need to use another relationship in my lastPirce for publishers, i cant use that code and should return separately my data.
What i should to do?
thanks
Edit 1:
this is my Controller Code:
$products = Product::where('id',$id)->where('slug',$slug)->where('status','confirm')->first();
if(!$products){
return $this->sendError('Post does not exist.');
}else{
return $this->sendResponse(new \App\Http\Resources\Products\Products($products), 'Posts fetched.');
}
and this is sendResponse & sendError:
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];
return response()->json($response, 200);
}
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if(!empty($errorMessages)){
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
thanks.
Edit 2:
i change my lastPrice Resource toArray function to this and my problem solved, but i think this isn't a clean way, any better idea?
$old_data = parent::toArray($request);
$co = 0;
$new_data = [];
foreach ($old_data as $index){
$publisher_data = Cache::remember('publisher'.$index['publisher_id'], env('CACHE_TIME_LONG') , function () use ($index) {
return ProductsPublisher::where('id' , $index['publisher_id'])->first();
});
$new_data[$co]['main_prices'] = $index['main_price'];
$new_data[$co]['off_prices'] = $index['off_price'];
$new_data[$co]['publisher'] = SinglePublisher::make($publisher_data);
$new_data[$co]['created_at'] = $index['created_at'];
$co++;
}
return $new_data;
I'm developing an API with Laravel. In one of the endpoint I'm accessing, some fields are showing a null value, but it should have some information.
Note the "addicionais_descricao" and "valor" fields, both always come with null values when I include them in the attributeitems array, but if I leave it at the initial level, the data is presented, but it doesn't solve my case, because I need this information with the attribute items:
enter image description here
This is where the endpoint calls, I make the query in the "Attribute" table, which has a relationship with the "Attributeitems" table, while the "attributeitems" table is linked to "Attribute" and "product".
public function show($id)
{
$atributos = Atributo::query('atributo')
->select(
'atributo.id',
'atributo.atrdescricao',
'atributoitens.atributo_id',
'atributoitens.produto_id',
'produto.prodescricao',
'produto.provalor'
)
->leftJoin('atributoitens', 'atributo.id', '=', 'atributoitens.atributo_id')
->leftJoin('produto', 'produto.id', '=', 'atributoitens.produto_id')
->where('atributo.id', '=', $id)
->get()->unique('id');
return AtributoResource::collection($atributos);
}
Resource Atributo:
public function toArray($request)
{
return [
'id' => $this->id,
'descricao' => $this->atrdescricao,
'atributoitens' => AtributoitensResource::collection($this->atributoitens),
];
}
Resource Atributo Itens:
public function toArray($request)
{
return [
'id' => $this->id,
'atributo' => $this->atributo_id,
'produtos' => $this->produto_id,
'adicionais_descricao' => $this->prodescricao,
'valor' => $this->provalor
];
}
What is the correct procedure for this situation?
Take this example as a reference :
Controller
$data = $shop->products()
->whereStatus(true)
->where('product_shop.active', true)
->where('product_shop.quantity', '>=', $this->min_product_qty)
->paginate(50);
return (new ProductCollection($data))
->response()
->setStatusCode(200);
ProductCollection
public function toArray($request)
{
return [
'data' => $this->collection
->map(function($product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
'brand' => $this->when($request->brand, $request->brand)
];
}
ProductResource
public function toArray($request)
{
return [
'type' => 'product',
'id' => (string) $this->id,
'attributes' => [
'uuid' => $this->uuid,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'thumb_path' => $this->thumb_path,
'cover_path' => $this->cover_path,
],
'relationships' => [
'brand' => $this->brand
]
];
}
Something like this should help you do what you want. I cant exactly do it for you. by the way why you are not using Eloquent, something like
Attribute::where(...)->with(['relation_1', 'products'])->get();
public function toArray($request)
{
return [
'id' => $this->id,
'attributes' => [...],
'products' => $this->collection
->map(function($this->product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
];
}
I am using Laravel-8 and Maatwebsite-3.1 package for excel upload:
I have this Excel sheet (The data is more thank that):
imports:
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Row;
use Maatwebsite\Excel\Concerns\OnEachRow;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\WithStartRow;
use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
use Maatwebsite\Excel\DefaultValueBinder;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsErrors;
use Maatwebsite\Excel\Concerns\SkipsOnError;
use Maatwebsite\Excel\Concerns\SkipsFailures;
use Maatwebsite\Excel\Concerns\SkipsOnFailure;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Validators\Failure;
use Throwable;
class EmployeeImport extends DefaultValueBinder implements OnEachRow, WithStartRow, SkipsOnError, WithValidation, SkipsOnFailure, WithEvents, WithCustomValueBinder
{
// set the preferred date format
private $date_format = 'Y-m-d';
// set the columns to be formatted as
private $date_columns = ['E'];
use Importable, SkipsErrors, SkipsFailures;
public function onRow(Row $row)
{
$rowIndex = $row->getIndex();
if($rowIndex >= 1000)
return; // Not more than 1000 rows at a time
$row = $row->toArray();
$employee_data = [
'first_name' => $row[0],
'other_name' => $row[1] ?? '',
'last_name' => $row[2],
'email' => preg_replace('/\s+/', '', strtolower($row[3])),
'dob' => $this->transformDate($row[4]),
];
$employee = create(Employee::class, $employee_data);
if (User::where('email', '=', $employee->email)->exists()) {
$user = User::update([
'first_name' => $employee->first_name,
'last_name' => $employee->last_name,
'active' => 1,
]);
}else{
$user = User::create([
'email' => $employee->email,
'first_name' => $employee->first_name,
'last_name' => $employee->last_name,
'active' => 1,
]);
}
$employee->update([
'user_id' => $user->id,
]);
}
public function startRow(): int
{
return 2;
}
public function customValidationAttributes()
{
return [
'0' => 'First Name',
'1' => 'Other Name',
'2' => 'Last Name',
'3' => 'Email',
'4' => 'Date of Birth',
];
}
public function rules(): array
{
return [
'*.0' => [
'required',
'string',
'max:50'
],
'*.1' => [
'nullable',
'string',
'max:50'
],
'*.2' => [
'required',
'string',
'max:50'
],
'*.3' => [
'required',
'email',
'max:100',
Rule::unique('employees', 'email')->where(function ($query) {
return $query->where('company_id', Auth::user()->company_id);
})
],
'*.4' => [
'required',
'date_format:Y-m-d',
'before:' . Carbon::now()->subYears(18)->format('Y-m-d')
],
];
}
public function chunkSize(): int
{
return 1000;
}
public function transformDate($value, $format = 'Y-m-d')
{
try {
return \Carbon\Carbon::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value));
} catch (\ErrorException $e) {
return \Carbon\Carbon::createFromFormat($format, $value);
}
}
}
Controller:
public function importEmployee(Request $request)
{
$validator = Validator::make($request->all(), [
'document' => 'file|mimes:xls,xlsx|max:5000',
]);
if ($request->hasFile('document'))
{
if($validator->passes()) {
$import = new EmployeeImport;
$file = $request->file('document');
$file->move(public_path('storage/employee_imports'), $file->getClientOriginalName());
Excel::import($import, public_path('storage/employee_imports/' . $file->getClientOriginalName() ));
return $this->success('Employees Imported.', [
'file' => $file
]);
}else{
return $this->error($validator->errors(), 422);
}
}
}
When I submitted, I got this error:
[2021-08-19 09:33:40] local.ERROR: Error: Call to undefined function
App\Imports\create() in
C:\xampp\htdocs\myapp\app\Imports\EmployeeImport.php:98
Stack trace:
#0 C:\xampp\htdocs\myapp\vendor\maatwebsite\excel\src\Sheet.php(301):
App\Imports\EmployeeImport->onRow(Array)
#1 C:\xampp\htdocs\myapp\vendor\maatwebsite\excel\src\Reader.php(114): Maatwebsite\Excel\Sheet->import(Object(App\Imports\EmployeeImport), 2)
#2
C:\xampp\htdocs\myapp\vendor\laravel\framework\src\Illuminate\Database\Concerns\ManagesTransactions.php(29): Maatwebsite\Excel\Reader->Maatwebsite\Excel{closure}(Object(Illuminate\Database\MySqlConnection))
This is the line 98:
$employee = create(Employee::class, $employee_data);
How do I resolve this?
Thanks
Change this line
$employee = create(Employee::class, $employee_data);
to
$employee = Employee::create($employee_data);
create is not a function alone. Check this link
https://laravel.com/docs/8.x/eloquent#inserting-and-updating-models
I am importing Excel File using Laravel-8 endpoints and Maatwebsite-3.1 package.
The way I did it is that, the Uploaded Excel file will first get to the storage (storage/file_imports/student_imports). Then Laravel pickes it up from there, stores it into the DB table through StudentImport using Maatwebsites.
StudentImport:
<?php
namespace App\Imports;
use App\Models\User;
use App\Models\Student;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\WithBatchInserts;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\SkipsErrors;
use Maatwebsite\Excel\Concerns\SkipsOnError;
use Maatwebsite\Excel\Concerns\SkipsFailures;
use Maatwebsite\Excel\Concerns\SkipsOnFailure;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Validators\Failure;
use Throwable;
class StudentImport implements
ToModel,
WithValidation,
WithHeadingRow,
SkipsOnError,
SkipsOnFailure,
WithBatchInserts
{
protected $companyId;
public function __construct()
{
$this->companyId = Auth::user()->company_id;
}
use Importable, SkipsErrors, SkipsFailures;
public function model(array $row)
{
$student_data = [
'first_name' => $row[0],
'other_name' => $row[1] ?? '',
'last_name' => $row[2],
'email' => preg_replace('/\s+/', '', strtolower($row[3])),
'gender' => $row[4],
'nationality_id' => $this->getNationality() ?? '',
'school_id' => Auth::user()->school_id,
];
$student = Student::create($student_data);
if (User::where('email', '=', $student->email)->exists()) {
$user = User::update([
'first_name' => $student->first_name,
'other_name' => $student->other_name,
'last_name' => $student->last_name,
'complete_profile' => 1,
'active' => 1,
'user_type' => 'Driver',
'company_id' => Auth::user()->company_id,
'updated_at' => date("Y-m-d H:i:s"),
'updated_by' => Auth::user()->id,
]);
}else{
$user = User::create([
'email' => $student->email,
'username' => strtok($row[3], '#'),
'password' => bcrypt("123456"),
'first_name' => $student->first_name,
'other_name' => $student->other_name,
'last_name' => $student->last_name,
'activation_token' => str_random(10),
]);
}
}
public function headingRow(): int
{
return 1;
}
public function getRowCount(): int
{
return $this->rows;
}
public function customValidationAttributes()
{
return [
'0' => 'First Name',
'1' => 'Other Name',
'2' => 'Last Name',
'3' => 'Email',
'4' => 'Gender',
];
}
public function rules(): array
{
return [
'*.0' => [
'required',
'string',
'max:50'
],
'*.1' => [
'nullable',
'string',
'max:50'
],
'*.2' => [
'required',
'string',
'max:50'
],
'*.3' => [
'required',
'email',
'max:100',
Rule::unique('studentss')->where(function ($query) {
return $query->where('school_id', Auth::user()->school_id);
})
],
'*.4' => [
'required',
'string',
'max:20'
],
];
}
public function batchSize(): int
{
return 1000;
}
public function chunkSize(): int
{
return 1000;
}
public function onFailure(Failure ...$failures)
{
// Handle the failures how you'd like.
}
public function customValidationMessages()
{
return [
'1.in' => 'Custom message for :attribute.',
'nim.unique' => 'Custom message',
];
}
}
Controller:
public function importStudent(Request $request)
{
try {
$user = Auth::user()->id;
$validator = Validator::make($request->all(), [
'document' => 'file|mimes:xlsx|max:10000',
]);
if($validator->fails()) {
return $this->error($validator->errors(), 401);
} else {
$check = User::where('id', $user)->pluck('id');
if($check[0] !== null || $check[0] !== undefined) {
$file = $request->file('document');
$file->move(public_path('storage/file_imports/student_imports'), $file->getClientOriginalName());
Excel::import(new StudentImport, public_path('storage/file_imports/student_imports/' . $file->getClientOriginalName() ));
return $this->success('Students Successfully Imported.', [
'file' => $file
]);
} else {
return $this->error('Not allowed', 401);
}
}
} catch(\Exception $e) {
return $this->error($e->getMessage());
}
}
As I stated earlier it will first store the Excel file into:
storage/file_imports/student_imports
Then pick it from there and store in the DB.
This code is in the controller above.
The file is store in the
public/storage/file_imports/student_imports
as expected, but nothing is found in the DB. Yes it displays Success with no error.
What could be the problem and how do I resolve it?
Thanks
model method in your StudentImport class must return new instance of Eloquent model.
You shouldn't make create or update method call through your model in here.
Maatwebsite-Excel manage your insert process with own manager. Check source code.
https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/src/Imports/ModelImporter.php#L74
https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/src/Imports/ModelImporter.php#L92
https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/src/Imports/ModelManager.php#L45
https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/src/Imports/ModelManager.php#L64
I'm inserting a record to a polymorphic imageable table, however it says column thread_id not found. I have not declared this thread_id column and I don't know where it's pulling it from. Here is the code it's trying to run.
protected static function bootRecordImage()
{
if (auth()->guest()) return;
foreach (static::getMethodToRecord() as $event) {
static::$event(function ($model) use ($event) {
$body = request()->body;
preg_match_all('/<img .*?(?=src)src=\"([^\"]+)\"/si', $body, $matches);
$images = $matches[1];
if($event == 'created') {
foreach ($images as $image) {
$model->images()->create([
'user_id' => auth()->id(),
'imageable_id' => $model->id,
'imageable_type' => get_class($model),
'path' => $image
]);
}
}
if($event == 'deleting') {
foreach ($images as $image) {
$model->images()->delete([
'user_id' => auth()->id(),
'imageable_id' => $model->id,
'imageable_type' => get_class($model),
'path' => $image
]);
if (File::exists(public_path($image))) {
File::delete(public_path($image));
}
}
}
});
}
}
My store method:
public function store(Request $request, Channel $channel, Spam $spam)
{
if (!auth()->user()) {
return back()->withInput()->with('flash', 'Sorry! You must be logged in to perform this action.');
}
if (!auth()->user()->confirmed) {
return back()->withInput()->with('flash', 'Sorry! You must first confirm your email address.');
}
$this->validate($request, [
'title' => 'required',
'body' => 'required',
'channel_id' => 'required|exists:channels,id',
'g-recaptcha-response' => 'required'
// yes it's required, but it also needs to exist on the channels model, specifically on the id
]);
$response = Zttp::asFormParams()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.recaptcha.secret'),
'response' => $request->input('g-recaptcha-response'),
'remoteip' => $_SERVER['REMOTE_ADDR']
]);
// dd($response->json());
if (! $response->json()['success']) {
throw new \Exception('Recaptcha failed');
}
$spam->detect(request('title'));
$spam->detect(request('body'));
$thread = Thread::create([
'user_id' => auth()->id(),
'channel_id' => request('channel_id'),
'title' => request('title'),
'body' => request('body'),
//'slug' => str_slug(request('title'))
]);
return redirect('/forums/' . $thread->channel->slug . '/' . $thread->slug);
}
As you can see, no where is a thread_id mentioned, yet in the error it looks like it's trying to insert into a thread_id column that I've never declared.
Thanks for reading.
I put the polymorphic relation in the model and the trait. Remove it from the Model and you're good to go.