We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Query item by slug instead of ID

Greetings!

I'm not good with PHP or phalcon, so apologize for what may be a vary basic question.

I want to have a link like this:

https://example.com/items/show/my-unique-slug instead of https://example.com/items/show/5

In my ItemsController I have the following:

    public function indexAction()
    {
        $this->view->title = 'Items';
        $this->view->items = Items::find(
            [
                "order" => "id",
                "limit" => 10,
            ]
        );
    }

    public function showAction($id)
    {
        $this->view->title = 'Show Item';
        $this->view->items = Items::find($id);
    }

Changing the param on showAction from $id to $slug will do nothing (I have a unique $slug for each item in the model) and the view will still only be accessible by the id.

Can anyone help me solve this?

Thanks



93.7k
Accepted
answer
edited Sep '17

Some things to get you started with:

1) Your urls should look something like: website.com/items/slug-of-item

2) Example route definition:

$router->add('/items/{slug}', 'Items::show')->setName('items-detail');

3) Getting the slug parameter from the url.

  • either you add it as parameter in the showAction() function or you use dispatcher like so: $this->dispatcher->getParam("slug")

4) You query the database, in your case using the orm models.

find() method returns multiple results, you have to use findFirst() to get single result.

Items::findFirstBySlug($slug)

or

Items::findFirstBySlug($this->dispatcher->getParam('slug'))

Note: the above findFirstByColumn are magic methods, if you are not comfortable with them the alternative is:

Items;:findFist([
    'conditions' => 'slug = :slug:',
    'bind' => [
        'slug' => $this->dispatcher->getParam('slug')
    ]
]);

Don't forget to sanitize those input params.

@Jonathan, afaik the orm escapes all of the code above, ofcourse you can sanitize for even better secuirty :)



2.2k

Nikolay,

Thank you. I happened to stumble upon a snippet at the same time as you posted your answer from the official phalcon blog tutorial: https://github.com/phalcon/blog-tutorial/blob/master/app/controllers/PostsController.php that does what I need.

So, in my showAction I only needed to change from

$this->view->items = Items::find($id);

to

    public function showAction($slug)
    {
        $this->view->title = 'Show Item';
        $this->view->items = Items::find(array(
            'slug = :slug:',
            'bind' => [
                'slug' => $slug,
            ],
        ));
    }

and that's basically it. No need to define the route. I'm not sure which is the prfered way but both seem to do the same thing. For some reason I only needed to change findFirst to find from the snippet or else I was getting a ' Trying to get property of non-object..' in my view.

Does this seem correct?

Thanks

edited Sep '17

It seems perfectly fine. I define my routes because in most cases i need to have "pretty" urls in Bulgarian to make SEO experts happy :)

find() will return resultset, so your result will be array(0 => .......), while findFirst returns only 1 record.

Well if you don't sanitize and user will pass some weird characters you might get pdoexception anyway if you have wrong/diffrent character coding, right?



2.2k
edited Sep '17

Ah, now I see why I got the error with findFirst. I was trying to fetch other records from the same row in my view.

As for the other suggestion from Jonathan, I'm sanitizing the input params, thanks. To be more specific, the slug is generated from a sanitized title via one of the icubator libraries.

Thank you all.



65

I read the source code in Model.zep, and I can confirm that the "By" functions (i.e. findBy<column> and findFirstBy<column>) bind the parameter's value, so findFirstBySlug($unsanitized_string) is safe.

@Jonathan, afaik the orm escapes all of the code above, ofcourse you can sanitize for even better secuirty :)