views:

158

answers:

5

A lot of frameworks use URL conventions like /controller/action/{id} which is great, but if you need any configuration beyond that, it's up to you to write your own routes.

How would you handle URLs like /users/{id}/friends on the backend? (to list all of a user's friends)

I'm thinking that in the controller, something like this would be appropriate:

class User {
    function index() {
        echo 'user index';
    }
}

class Friend extends User {
    function index($user_id) {
        echo 'friend index';
    }    
}

Then you would have the following map:

/users              -> User::index()
/users/{id}         -> User::view($id)
/users/{id}/friends -> Friend::index($user_id)

I wanted to put the Friend class inside the User class but apparently you can't do that in PHP so this is the best I could come up with. Thoughts?

What URL would use for editing your list of friends? /users/{id}/friends/edit could work, but it doesn't seem appropriate, since you should never be editing someone else's friend list. Would /account/friends/edit be a better choice? Where would you put the corresponding code for that? In a friend controller, or a user controller, or a specialized account controller?

Bonus question: which do you prefer? /photos/delete/{id} or /photos/{id}/delete


The answers:

So, what I've gathered from the answers is that if the "thing" is complicated (like "friends") but doesn't have its own controller, you can give it one without a model, or if it's not, you should stuff it in with whatever it's most closely related to. Your URLs should not influence where you put your code. Most people seem to think you should stick to /controller/action/{id} whever possible, because it's what people are familiar with.

No one really commented on the extended class aside from saying it's "awkward". Perhaps FriendList would have been a more appropriate class in that case if I really wanted to separate it out.

Thanks for all the answers :)

+2  A: 

The routes you're talking about, and the way you're using subclasses to achieve this structure, seems a bit awkward to me. The standard convention of /controller/action/{id} works great for simple actions, but if you're creating a complex application you will always need to create custom routes. There are probably some good guidelines to use when creating these routes, but it really boils down to staying consistent across your application and keeping things as simple as possible.

I don't see any good reason to have /user/{id}/friends mapping to a "Friend" controller. Why not just have "friends" be an action on the User controller? Once you actually drill down to view a specific friend's page, you could use a Friend controller (/friends/view/123) or you could repurpose your User controller so that it works for a friend or the currently logged in user (/user/view/123).

Re: the bonus question, I'd stick with /photos/delete/{id} (/controller/action/{id}) as that's the most widely accepted mechanism.

pix0r
+1  A: 

I would prefer /photos/{id}/delete. My reasoning is that if you take one component off the end of an URL, it should still make sense.

It's pretty easy to assume what /photos/{id} should do: view the set of photos for that {id}.

But what should /photos/delete do? That's really unclear.

I know that there's kind of a default convention of /controller/action/id, but that organization is for the sake of mapping to the class/method architecture of controllers. I don't think it's a good idea to organize the UI to accommodate the code (the URL is in a way part of the UI).


Re comments: Yes, /photos/{id} maybe makes more sense to view a given photo by its id. /users/{id}/photos perhaps to view a collection. It's up to you.

The point is that you should think of the UI in terms of users, not in terms of code organization.

Bill Karwin
Sounds reasonable. It isn't too hard to write the code for that either. But what about the rest of the stuff?
Mark
That's a good point. The `/controller/action/id` convention might be best suited for web services and not for public-facing websites, for this exact reason. I guess it depends if what you're building is more of a user-oriented content website or a developer/administrator-oriented application or service.
pix0r
PS: I would assume that `photos/{id}` views the photo given by {id}, not the set :) If you wanted a set, I'd use `/users/{id}/photos` to display all photos by that user.
Mark
@pix0r: just fyi, it's very user-oriented
Mark
+1  A: 

It also depends on how you are storing your data. I could imagine in some cases you need a 'friend-list' to be a entity in your model. A logical approach would then be to specify a unique identifier for each friend-list, a primary key.

This would logically result in the following route, as you only need a primary key of the friend-list to edit or delete it...

/friends/edit/{friendListId}

It's up to you to decide. As pix0r stated: convention for small applications is /{controller}/{action}/{id} where {id} should be optional to match with most of your websites actions. In some cases applications get big and you want to define specific routes with more than 3 elements. In some cases certain entities just get a bigger meaning (above example) and you could decide to define a custom controller for it (which makes the default route perfect again...).

I'd stick with the default route /controller/action/id but just don't start making controllers for everything (like friends) in the beginning. The Model-View-Controller pattern makes it very easy for you to change routes later on, as long as all your route-links and actions (forms etc.) are generated based on routes and actions. So you don't really have to bother that much :)

Ropstah
Sure, but I dont have friend-lists. Friends are tied to a specific user, not a group. And again, it's not really the point how I have my specific system set up, the question is how would you handle those situations where you *don't* have a separate entity for such things? In scenarios where your route simply *can't* be boiled down to /controller/action/id, what do you do then? And where do you put the code?
Mark
In your case you can still boil down to /controller/action/id => /user/editfriends/{userId} ? The question here is if YOU want it (read: your system design). How complicated is a friendlist? Does it need it's own controller? This should lead you to where to put the code..?
Ropstah
Hrm.... so if it's complicated give it it's own controller, otherwise stuff it in with whatever it's most closely related to?
Mark
Shortly: yes. You should just logically define groups of actions that relate to each other. Think about your functionality, modularize the whole thing and you will see controllers :)
Ropstah
+1  A: 

You can do either or. The problem is when you mix the two. /users/{id}/friends and /users/friends/{id} When someone has the id of "friends" this will fail. This may seem like a trivial case but it's very popular to use usernames for ids. You will have to limit user names for every action.


Sometimes you can't do /{controller}/{action}/{id}

I did a indie music site a while back and we did

/artist/{username}
/artist/{username}/albums
/artist/{username}/albums/{album}

We didn't want to test for conditionals so we didn't do

/artist/{username}/{album}

Since we didn't want to check for anyone with an album named "albums"

We could have done it

/artist/{username}
/artist/{username}/albums
/albums/{album}

but then we would lose the SEO advantage of having both the artist name and the album name in the URL. Also in this case we would be forcing album names to be unique which would be bad since it's common for artist to have album names the same as other artist.

You could do pure /{controller}/{action}/{id} but then you would lose some SEO and you can't do URL shortening.

/artist/view/{username}
/artist/albums/{username}
/album/view/{album}


Getting back to your example.

/users/{id}/friends/edit could work, but it doesn't seem appropriate, since you should never be editing someone else's friend list.

In this case it should be /friends/edit since your user id is duplicate information assuming your in a session somehow. In general you want to support URL shortening not URL expansion.

(Bonus question) Neither, i'd use REST. DELETE /photo?id={id}

gradbot
Everything you've said sounds good, but that still leaves the question of where to put the code. Where did you put the handler for /artists/{username}/albums? In the artists controlller, in the albums controller, or somewhere else?I contemplated REST in this question http://stackoverflow.com/questions/1016258/ but when it comes down it, you can't issue a DELETE with HTML, so you need a different URL, which could in turn issue a DELETE on the backend if you really wanted.
Mark
+1  A: 

The URLs themselves don't really matter too much. What is more important is what goes in each of your controllers. In your example you had your friend list extend the User class. If your list of friends is really just a list of users, maybe it should extend the Users controller so that you deal with lists of users in one place.

class Users {

    public function index() {
        $users = $this->findUsers();
    }

    protected function findUsers($userId=null) { ... }
}

class Friends extends Users {

    public function index($userId) {
        $users = $this->findUsers($userId);
    }
}

If you have a hard time figuring out which class to extend write out what you need from each of the classes and pick the one with the longest list.

rojoca
My logic was a bit different. Had it been /users/{id}/photos (photos are NOT users) to get all the photos owned by a specific user, I would have extended it the same way. The idea was to avoid function name clashing (rather than putting the friend functions directly into the user class), while still being able to look up the user who owns the item. But in this case, I would need access to both the user model, AND the photo model. In this case, "Photo" is a class that already exists... would it be more appropriate to put the function in the User class, or the Photo class, and what would I name
Mark
-- the function? User::listFriends() or Photo::byUser()?? I had design decisions where there's no clear answer :(
Mark
Use a controller for connecting a user with their photos, since this is business logic. Your User and Photo models should encapsulate only state and operations on their own data. That means you implement Photo::byUser($userId) because you're retrieving a list of photos. You make this call within some controller UserPhotos which has both a user model, and a photo model
rojoca
Could wind up with a *lot* of connector objects that way, no? Alright, well thanks for the input.
Mark