logo
Ask your Symfony questions! Pay money and get answers fast! (more info)

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.

$5
What's the best way to deal with subcategories in url?

The problem with the category urls is the fact that I am using slugs that relates an item to a category. So each category item has a slug field for the category name.

Lets say I have these categories:


    Animal
Dog
Cat
Plant
Rose




For example, to list items in the animal category, I click on the the animal category from a category list, I then get the route from the object and the URL will look like this:
/category/animal/ Items in the 'Animal' category are listed on the screen.

My routing rule looks like this: url: /category/:slug

If I drill down on Dog and click 'Golden Retriever' from the list, my url looks like this /category/dog/golden-retriever

But 'golden retriever' is a child of its parent category, animal. I would like for my URL to look like this: ''/category/animal/dog/golden retriever' when I drill down on 'golden retriever'.

So, the question is what is the best way to store the subcategory information when you only have one slug for each category_id? Is the way to do this is to have the dog category record have two slugs, one for the main category 'animal' and one its name 'dog' Should I create another field to store subcategories for each category record?

This question has been answered.

atentaten | 10/04/10 at 4:24pm Edit


(6) Responses

See a threaded view of answers?

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

  • avatar
    Last edited:
    10/04/10
    4:33pm
    Sergey Kushniruk says:

    Generate routes dynamically based on your categories:
    http://www.symfony-project.org/gentle-introduction/1_4/en/09-Links-and-the-Routing-System#chapter_09_sub_creating_rules_without_routing_yml

  • avatar
    Last edited:
    10/04/10
    4:38pm
    Joshua Estes says:

    You will have in your routing yml file two different urls (or as many as you want)

    /category/:category-slug/:sub-category-slug

    and the original

    /category/:category-slug

    you can give the different module/action or what not, might be best to use the same module/action and and check to make sure those are set.

    That should work. If you don't have a sub-category-slug either update your schema.yml or use the sub-cat-id PK

  • avatar
    Last edited:
    11/07/10
    5:32pm
    Nate Flink says:

    It's a little general, but here are a couple ideas.

    Anything after "/:slug" in the routing rule "/category/:slug" is considered a parameter.

    In other words /category/dog is a direct reference to the dog action of the category module.

    It's possible to handle the whole thing from /category/index by mapping /category/:whatever to always forward to category/index within a filter. (Need to watch out for too many forward loops by doing something like checking the action stack for the index action already existing.

    Now a routing rule like /category/index/:slug1/:slug2/:slug3 becomes usable and makes it useful to mangle those values, just about anywhere in the application you would like, as parameters. Filters are a good place to do things like this. One can force urls by adding parameter requirements as well. Typically, one can grab the parameters in the action.

    This question concerns the best way of "storing" subcategory information. I'm not really sure what is meant by storing it, but I don't take it as a question about how to persist the various urls which could possibly be sent, but rather what is the best means of mapping an arbitrary url/filepath with multiple levels to a slug type construct.

    Depending on how many levels deep it is needed to go something like:

    category_level_one:
    url: /category/:species
    param: { module: category, action: levelOne }
    requirements: {species: (?:dog|cat|chicken|lemur|fruitbat|pencil|frisbee)}

    category_level_two:
    url: /category/:species/:family
    param: { module: category, action: levelTwo }
    requirements: {species: (?:dog|cat|chicken|lemur|fruitbat|pencil|frisbee), family: (?:bloodhound|bulldog|dalmation)}

    etc ..

    If you've got tons of different possibilities than, it might not be possible to enforce requirements, and some other checking could occur in the action or filter.

  • avatar
    Last edited:
    11/07/10
    5:32pm
    Ludovic Fleury says:

    With this kind of context I would go for Doctrine NestedSet behavior (or propel nestedset) and I would use a custom routing.

    Using a generic routing like "/:categories"
    And adds "/" as separator, or using another separator ( undersocre ? ), then deal manually with the categories passed to the url (explode separator, retrieve parameters as node in the nested tree).
    ( I would use a custom route, extending the object route, wich will implememt this logic (separator+nestedset)).

    Thats how I see dynamics categories with unknown nested level.
    But as Nat Flink say, you've got tons of possibilities?

    Previous versions of this answer: 10/04/10 at 5:14pm

  • avatar
    Last edited:
    11/07/10
    5:32pm
    Loban Rahman says:

    If I understand you right, what you REALLY want is the ability to have an unlimited nesting of categories, and then show it in the url. For example, if you have

    Animal -> Dog -> Golden Retreiver

    you want it to be

    /category/animal/dog/golden-retriever

    And if you have Animal -> Dog -> Terrier -> Miniature Bull Terrier

    you want it to be

    /category/animal/dog/terrier/miniature-bull-terrier

    So, your Category should be using the NestedSet behavior (assuming you are using Doctrine), while your Item has one foreign key to Category.

    Finally, your route for any Item's VIEW should be as follows:

    /category/:category-breadcrumb/:slug

    Your action will obviously be loading the Item based on the ":slug" variable alone. The Item model should have a method getCategoryBreadCrumb() which will be used when generating the url for the item.

    UNFORTUNATELY, I'm not 100% sure if Symfony's routing system will barf if your breadcrumb has "/" in it. Please check on that.

    Finally, how to handle url's like /category/animal/dog, which is supposed to list all dogs? Well, you could make a route

    /category/:category-breadcrumb

    but Symfony might not be able to differentiate between that and the previous route. What you could do in this case is just keep the first route. In your VIEW action above, first try and find an item with ":slug". If not, call the LIST action, giving it a category ":slug". For example,

    /category/animal/dog

    Your view action first tries to load an item with slug "dog". When it fails, it then passes to the list action which loads all items with category "dog" (or any child categories of "dog").

    Obviously, your Category model should also have a getCategoryBreadCrumb() method to use when generating the url for a category (it will include everything before the category).

    Hope this makes sense.

  • avatar
    Last edited:
    11/07/10
    5:32pm
    Jimish Gamit says:

    Add one more filed in your table, let say 'parent_id'. So, now your table has 3 fields (id,slug & parent id). Now, any category who has no parent set 'parent_id' to 0 and for who has parent, set appropriate 'id' of parent category.

    So suppose category 'Animal' has 2 child category 'Animal_1' and 'Animal_2'
    again category 'Animal_1' has child category 'Dog' & 'Cat'. For category 'Animal_2' only child category is 'Snake'.

    ID | SLUG | PARENT_ID
    ____________________________
    1 | Animal | 0
    2 | Fruits | 0
    3 | Animal_1 | 1
    4 | Animal_2 | 1
    5 | Dog | 3
    6 | Cat | 3
    7 | Apple | 2
    8 | Snake | 4
    and so on...

    routing.yml
    category_level_one:
    url: /category/:slug_1
    param: { module: category, action: l_one }

    category_level_two:
    url: /category/:slug_1/:slug_2
    param: { module: category, action: l_two }

    category_level_three:
    url: /category/:slug_1/:slug_2/:slug_3
    param: { module: category, action: l_three }


    Now, fetch all in single query “Select * from TABLE” in let say $cat_result; This way you don't need to make lots of query

    /****Code Start****/

    $cat_slug = $parent_id = array();
    for($cat_result as $cat){
    $cat_slug[$cat['id']] = $cat['slug'];
    $parent_id[$cat['id']] = $cat['parent_id'];
    }

    for($cat_result as $cat){
    if($cat['parent_id']){
    $last_leaf_slug = $cat['slug'];
    if(parent_id[$cat['parent_id']]){
    // use @category_level_three:
    link_to($last_leaf_slug,”@category_level_three?slug_1=”.$cat_slug[parent_id[$cat['parent_id']]].”&slug_2=”.$cat_slug[$cat['parent_id']].”&slug_3=”.$last_leaf_slug);
    }else{
    // use @category_level_two:
    link_to($last_leaf_slug,”@category_level_two?slug_1=”.$cat_slug[$cat['parent_id']].”&slug_2=”.$last_leaf_slug);
    }
    } else {
    // use @category_level_one:
    link_to($cat['slug'],”@category_level_one?slug_1=”.$cat['slug'])
    }
    }


    /****Code End****/

    Previous versions of this answer: 10/07/10 at 7:13am | 10/07/10 at 7:15am | 10/07/10 at 7:17am | 10/07/10 at 7:18am | 10/07/10 at 7:20am | 10/07/10 at 7:22am | 10/07/10 at 7:22am | 10/07/10 at 7:24am

This question has expired.





Current status of this question: Completed



Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.