skip to Main Content

I have identified a validation issue in the controller.

The problem looks like this if you specify a connection in the model, and create validation in the controller. In the validation, specify unique columns, then the model is connected as by default.
My code snippets.

Model

protected $connection = 'api';
protected $table = 'pages';

protected $fillable = [
    'name', 'title', 'description',
    'keywords', 'slug', 'content', 'status',
    'sort', 'type'
];

public function sluggable(): array
{
    return [
        'slug' => [
            'source' => 'title'
        ]
    ];
}

Controller (resource)

public function store(PagesRequest $request)
{
    Pages::create($request->all());

    return view('pages.pages.index', [
        'pages' => Pages::orderBy('id', 'desc')->paginate(30)
    ])->with(['success' => true]);
}

PagesRequest

public function rules()
{
    return [
        'name' => 'required|string|min:5|max:255|unique:pages,name,'.@$this->page->id,
        'title' => 'required|string|min:5|max:255|unique:pages,title,'.@$this->page->id,
        'description' => 'required|string|min:5|max:255',
        'keywords' => 'required|string|min:5|max:255',
        'slug' => 'unique:pages,slug,'.@$this->page->id
    ];
}

As I understand it, in the rules method there is a request to the unique: pages database and here the model already works not through the connection, but through the default connection.

If I remove checks for "unique", then everything works.

PagesRequest

public function rules()
{
    return [
        //'name' => 'required|string|min:5|max:255|unique:pages,name,'.@$this->page->id,
        //'title' => 'required|string|min:5|max:255|unique:pages,title,'.@$this->page->id,
        'description' => 'required|string|min:5|max:255',
        'keywords' => 'required|string|min:5|max:255',
        //'slug' => 'unique:pages,slug,'.@$this->page->id
    ];
}

Is there any solution in this situation?

2

Answers


  1. Chosen as BEST ANSWER
    public function rules()
        {
            return [
                'name' => 'required|string|min:5|max:255|unique:api.pages,name,'.@$this->page->id,
                'title' => 'required|string|min:5|max:255|unique:api.pages,title,'.@$this->page->id,
                'description' => 'required|string|min:5|max:255',
                'keywords' => 'required|string|min:5|max:255',
                'slug' => 'unique:api.pages,slug,'.@$this->page->slug
            ];
        }
    

  2. If you are not sure, you can create an issue on the offical repository.

    I never faced this issue because I did not have this problem, but let me show you how to check if you are right or not, and then what can you do to solve it.

    First, go to the source code, as you did not specify which version you are using the steps are the same but you will have to check it on that version.

    So, validateUnique is the rule you are using, as you can see the rule is very simple, and parseTable is the one responsible for parsing what table you want and what connection.

    public function parseTable($table)
    {
        [$connection, $table] = str_contains($table, '.') ? explode('.', $table, 2) : [null, $table];
    
        if (str_contains($table, '\') && class_exists($table) && is_a($table, Model::class, true)) {
            $model = new $table;
    
            $table = $model->getTable();
            $connection ??= $model->getConnectionName();
    
            if (str_contains($table, '.') && Str::startsWith($table, $connection)) {
                $connection = null;
            }
    
            $idColumn = $model->getKeyName();
        }
    
        return [$connection, $table, $idColumn ?? null];
    }
    

    I will copy paste what that method does so we can quickly understand how is the connection get:

    1. First way: it is spliting the table’s string by a period/dot (.), it will either store a connection name (as a string) or store null on $connection.
    2. Second way depends if you passed a literal string or a class namespace (a string with ): if it is able to resolve the table using str_contains($table, '\') && ... && is_a($table, Model::class, true), it will execute $connection ??= $model->getConnectionName(); and use that connection. Using ??= means exactly $connection = $connection ?? $model->getConnectionName(); (that means that if $connection is not null, it will use that value, else execute getConnectionName(). To pass a "namespace", you must use ClassName::class, that will return a FQDN (Fully Qualified Domain Name -> SomethingInANamespaceClass -> AppModelsClass)

    More info here about ??=.


    So, possible solutions:

    1. Use the class FQDN: 'slug' => 'unique:'.Model::class.',slug,'.@$this->page->id
    2. Use the Rule class: 'slug' => [Rule::unique(Model::class, 'slug')->ignoreModel(@$this->page)]

    One quick tip, I think you are using @ to silence exceptions when you ask for @$this->page->id, don’t ever do that, never EVER use @ (at least if you are using a framework), you should literally catch the exception and "resolve it" when able or just let it go to the top, you don’t have a value for page in $this->page->id, then accessing ->id MUST throw an exception, the code is just going to be very confusing if you use @

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search