/**
  * @Route("/", methods="GET")
  * @Request({"filter": "array", "page":"int"})
  */
 public function indexAction($filter = [], $page = 0)
 {
     $query = Project::query();
     $filter = array_merge(array_fill_keys(['search', 'order', 'limit'], ''), $filter);
     extract($filter, EXTR_SKIP);
     if ($search) {
         $query->where(function ($query) use($search) {
             $query->orWhere(['title LIKE :search', 'slug LIKE :search'], ['search' => "%{$search}%"]);
         });
     }
     if (!preg_match('/^(date|title|comment_count)\\s(asc|desc)$/i', $order, $order)) {
         $order = [1 => 'date', 2 => 'desc'];
     }
     $limit = (int) $limit ?: App::module('bixie/portfolio')->config('projects_per_page');
     $count = $query->count();
     $pages = ceil($count / $limit);
     $page = max(0, min($pages - 1, $page));
     $projects = array_values($query->offset($page * $limit)->limit($limit)->orderBy($order[1], $order[2])->get());
     return compact('projects', 'pages', 'count');
 }
<?php

use Bixie\Portfolio\Model\Project;
return ['name' => 'bixie/portfolio-projects', 'label' => 'Portfolio projects', 'events' => ['view.scripts' => function ($event, $scripts) use($app) {
    $scripts->register('widget-portfolio-projects', 'bixie/portfolio:app/bundle/widget-portfolio-projects.js', ['~widgets']);
}], 'render' => function ($widget) use($app) {
    $query = Project::query()->where(['status' => Project::STATUS_PUBLISHED]);
    switch ($widget->get('content_selection', 'random')) {
        case 'random':
            $query->orderBy('RAND()');
            break;
        case 'latest':
            $query->orderBy('date', 'desc');
            break;
        case 'pick':
            $query->whereInSet('id', $widget->get('items', []));
            $query->orderBy('title', 'asc');
            break;
        default:
            return 'No valid content select';
            break;
    }
    $query->limit($widget->get('count', 4));
    $projects = array_values($query->get());
    $config = $app->module('bixie/portfolio')->config();
    $teaser_config = $widget->get('teaser');
    return $app['view']('bixie/portfolio/widgets/portfolio-projects.php', compact('widget', 'projects', 'config', 'teaser_config'));
}];