Skip to content
Advertisement

Laravel 9 query builder where clause has empty model id

I’m working on a Laravel 9 project and have created a custom validation rule called ValidModelOwnership which should check that the field a user is trying to add is owned by a model based on some values passed to it.

I’ve written the rule, but when debugging and outputting $model->toSql() the id is empty?

What am I missing?

My rule:

<?php

namespace AppRules;

use IlluminateContractsValidationRule;
use IlluminateSupportFacadesLog;

class ValidModelOwnership implements Rule
{
    /**
     * The model we're checking
     */
    protected $model;

    /**
     * Array of ownership keys
     */
    protected $ownershipKeys;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct($model, $ownershipKeys)
    {
        $this->model = $model;
        $this->ownershipKeys = $ownershipKeys;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $model = $this->model::query();
        $model = $model->where($this->ownershipKeys);

        Log::debug($model->toSql());

        if (!$model->exists()) {
            return false;
        }

        return true;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return "The :attribute field doesn't belong to you and/or your company.";
    }
}

And my usage in my controller:

/**
 * Store a newly created resource in storage.
 *
 * @param  IlluminateHttpRequest  $request
 * @return IlluminateHttpResponse
 */
public function store($company_id, $buyer_id, Request $request)
{
    $this->authorize('create', BuyerTier::class);

    $validator = Validator::make($request->all(), [
        'name' => [
            'required',
            'string',
            Rule::unique(BuyerTier::class)
                ->where('buyer_id', $buyer_id)
                ->where('company_id', $company_id)
        ],
        'country_id' => [
            'required',
            'numeric',
            new ValidModelOwnership(Country::class, [
                ['company_id', 80]
            ])
        ],
        'product_id' => [
            'required',
            'numeric',
            new ValidModelOwnership(Product::class, [
                ['company_id', 80]
            ])
        ],
        'processing_class' => 'required|string',
        'is_default' => [
            'required',
            'boolean',
            new ValidDefaultModel(BuyerTier::class, $buyer_id)
        ],
        'is_enabled' => 'required|boolean'
    ]);

    if ($validator->fails()) {
        return response()->json([
            'message' => 'One or more fields has been missed or is invalid.',
            'errors' => $validator->messages(),
        ], 400);
    }

    try {
        $tier = new BuyerTier;
        $tier->user_id = Auth::id();
        $tier->company_id = $company_id;
        $tier->buyer_id = $buyer_id;
        $tier->country_id = $request->input('country_id');
        $tier->product_id = $request->input('product_id');
        $tier->name = trim($request->input('name'));
        $tier->description = $request->input('description') ?? null;
        $tier->processing_class = $request->input('processing_class');
        $tier->is_default = $request->boolean('is_default');
        $tier->is_enabled = $request->boolean('is_enabled');
        $tier->save();

        return response()->json([
            'message' => 'Buyer tier has been created successfully',
            'tier' => $tier
        ], 201);
    } catch (Exception $e) {
        return response()->json([
            'message' => $e->getMessage()
        ], 400);
    }
}

I’ve hard-coded my id’s to illustrate that even when set statically, it’s not passed through:

[2023-01-19 09:40:59] local.DEBUG: select * from products where (company_id = ?) and products.deleted_at is null

Advertisement

Answer

Laravel (and most other frameworks) extract out variables when building SQL queries to prevent SQL injection.

So the following eloquent query:

User::where('name', 'Larry');

will become:

SELECT * FROM `users` WHERE `name` = ?

and it will also pass an array of bindings: ['Larry']. When SQL processes the query it replaces replaces the ? with the values in the bindings.

So if you want to see the full query you need to log the SQL and the bindings:

Log::debug($model->toSql());
Log::debug($model->getBindings());
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement