Does my controller code look good?
On the surface the code in your question seems fine, but I can tell you from experience that once this door is cracked just a bit, it will continue to creak open wider over time.


"I'm just checking if we have an ID or category name."
"This just adds a few meta tags."
"It's already 200 lines; 50 more won't matter."


The linked controller, however, is doing way too much work. It should be passing data off to a model class (not the entity manager directly), placing whatever the view needs into the ViewModel, and that's it. Controllers are glue code. As you have it, you'll need to copy all of this code and modify it slightly to expose the CRUD interface in another form.

For the code you posted, I would prefer to separate the actions so each handles one specific use case: all items, items matching a category, and one item (not in the code but you mentioned it). Create a regex route for the last two. The beauty of this is that you don't need to do all the conditional checks--the dispatcher does it for you.

// see miscellaneous tips below
public function init() {
    $this-&gt;objectManager = $this-&gt;getServiceLocator()
        -&gt;get('Doctrine\ORM\EntityManager');
}

public function allAction() {
    return new ViewModel(array(
        'news' =&gt; $this-&gt;loadItems(),
        'categoryName' =&gt; null,
    ));
}

public function categoryAction() {
    $category = $this-&gt;objectManager
        -&gt;getRepository('\News\Entity\Category')
        -&gt;findOneByUrl((string) $this-&gt;params('category'));
    if (!$category) {
        return $this-&gt;redirect()-&gt;toRoute('news');
    }
    return new ViewModel(array(
        'news' =&gt; $this-&gt;loadItems(array('category' =&gt; $category-&gt;getId())),
        'categoryName' =&gt; $categoryName,
    ));
}

private function loadItems($options = array()) {
    $news = $this-&gt;objectManager
        -&gt;getRepository('\News\Entity\Item')
        -&gt;findBy($options, array('created' =&gt; 'DESC'));
    $items = array();
    foreach ($news as $item) {
        $buffer = $item-&gt;getArrayCopy();
        $buffer['category'] = $item-&gt;getCategory()-&gt;getName();
        $buffer['user'] = $item-&gt;getUser()-&gt;getDisplayName();
        $items[] = $buffer;
    }
    return $items;
}


This is about the same length of the original, but it's far less complicated. Each action is easy to follow and clearly lays out what it requires.

Miscellaneous

And here are a few tips after looking at your linked controller and view code:


You are accessing the entity manager in every action (sometimes pulling it from the registry twice in the same method). Do this once by storing it in an instance property in init.
The index and list actions are nearly identical. Refactor these to extract the common code into a new private method. It looks like this applies to some of the other actions, e.g., converting a news item into an array for the view with its associated category and user names.
You can simplify the title-setting with the Elvis operator: $title = categoryName ?: "Список новостей".
Every page should have an H1--even the "all news" index page.
An non-empty array is truthy in PHP. if(count($this-&gt;news) != 0): can be shortened to if($this-&gt;news):. If you want to be explicit, at least use if(!empty($this-&gt;news)):.
If $this-&gt;news will be an empty array instead of null or false when there are no items, you don't even need the if since looping over an empty array is a no-op.
You don't need the ; in &lt;?=...?&gt; since it's an expression instead of a statement. You also don't need it for one-line statements in &lt;?php ... ?&gt;, but we still use it to avoid bugs when someone adds a line.