At the time of writing, I was working for a client which needed some help to make one project live. The project was 90% done, and it was written using symfony (1.4).
It was pretty small-sized: about 10 symfony modules (maybe fewer) and a two part model layer having a doctrine-mapped tiny database, and a kind of facade to Facebook APIs.
But surprisingly enough, a project this small contained a lot of complexity, duplicates, misplacement ...
The project was about functionnal, just needed a "final polish" from a user perspective, and some little missing features, but not huge of a deal.
On the other hand, the application was a hell to read, understand, and maintain already. Even before it went live.
The direct consequence of this is to make each subsequent development longer, and thus more expensive. The less-seen consequence is also to exhaust developpers because it make them feel unproductive. And you don't want your team to be exhausted. Never.
So please, please, please, think about the project lifetime when you're doing development. And even more important, show respect to your present and future coworkers, and thus to yourself ...
It should be pretty obvious to anybody, but I'm pretty sure all you have experienced this.
People tend to see controller methods as bins they can put any code in. They will just think "Hey, I'm pragmatic, and this works. Yay, kudos from my manager.".
That's for sure a pragmatic way to get kudos at first.
The fact is that the "you're wrong" part is completely invisible to your manager. And one day he wants to go live with his project.
The problems are coming now.
Manager: "Hey the app is slow, speed it up."
Dev: "Well I guess the model calls are getting long, it's doctrine fault."
M.: "Ok fix it."
D.: "Well, I'll have to go through all the controllers, change all calls ... It'll take one week at least."
M.: "Change controllers to fix the model? Huhu... I guess I missed something but hey, you're the pro, go ahead."
Then the developper takes two week to vaguely fix some calls, cache some really long request ... What I'd call "hide problems under the carpet".
But when you go live again, you just notice that only half the model problems are fixed. Woops, it happened that this famous model code in controllers was in fact present in quite a bunch of controllers, and the developper missed some.
And now we're starting to spot the exact same problem with some SOAP calls ...
Of course at the very last, you will end up fixing the app, at least enough for it to survive the ridiculous amount of target traffic, but the overall problem is not there ...
So let me recall some base principles.
One place for each stuff, and each stuff at the right place
Sounds idiomatic, but...
Model logic goes into the model.
The "model" layer is a facade that provides you with a business data access and manipulation API.
Write the API once, and then use it. Said simplier, you're not allowed to access data by anyelse mean. May it be database, SOAP/REST calls, or some other remote service providing some data to you (like Facebook) ...
Let me say it again, it's FORBIDDEN. Thinking of this «shortcut» as a way to speed development up is just stupid.
Organize your model layer
One big misconception a lot of people have is to think as their favorite ORM as the model layer of their project. Yes the ORM is a part of the model layer, but it's not necessary the whole model layer. You're allowed and encouraged to add proxies and facades in it, providing a nice, clean, and documented public API.
In an ideal world, the whole "data access" mechanism should be hidden and only a documented chosen subset of the model API should be used in your controllers.
Group features by concerns
You have plenty of tools to do this that are pretty much language/framework specific. In symfony (1), you have applications, modules, plugins ...
Take 5 minutes to think of the right place to do something, you'll save 10 minutes a day forever. Repeat.
Don't repeat yourself
What? This one again?
Yes, but I'll reword.
You're not allowed to have the same code, or same logic twice.
Let me give you some methodology for this. First of all, you must be familiar with your project, and know the codebase. You will obvsiously write duplicate logic if you are not.
If you're not familiar with the codebase, talk a lot, communicate, ask stupid questions to people around, ask again, grep the code ... In short, find a way to fill your knowledge gap.
If you're familiar with the codebase, then you know that the logic you're writing is already somewhere else. Unless the "elsewhere" code is useable to solve your problem without changing anything, go ahead and write the logic fast and dirty wherever you want, until you get it working.
Now is the time to refactor.
"There are two different times to do something. Now, and later. Later will in fact be never, 95% of the time."
So do it NOW, or it'll never be done.
Take the two code snippets, think of what ideal place this shoud go, what ideal API you want to use to do it ... And refactor now.
By the way it's a great time to add up some doc. Just a bit, just enough. What is this method, what is the input, what is the output. It should not take more than one minute, and this is API part, so you need to understand how it works at a glance.
The whole thing took maybe 10 minutes more than if you left duplicate code, and saved a lot of maintenance time ... An infinite maintenance time.
There should be one-- and preferably only one --obvious way to do it.
The Zen of Python, by Tim Peters
This article was written looking at a symfony project, but I'm pretty certain it can apply to all PHP projects, and by extend to all web projects designed under an MVC-like architecture.
May it be Python, Java, Ruby or anything ... The general idea is not language specific, even if you may have language specific ways to do the underlying stuff.
If you allow yourself to bypass the "let's do it clean" part, then you're not building web software, you're overcooking spaghettis (and you even forgot the salt).