En Phalcon 3.X tenemos un componente llamado Criteria que nos permite crear un array de parámetros para crear una búsqueda desde un form.

Se puede combinar ese componente con otro llamado paginator para hacer una búsqueda y también poder paginar a la vez.

Ponemos de ejemplo una tabla llamada lenguajes_programacion y otra tabla de usuarios. Queremos un buscador que busque por nombre_lenguaje en la tabla lenguajes_programacion (con una foreing key llamada usuario_id) y otro field llamado nombre_usuario de la tabla usuarios. La vista del search con el paginador ya creado quedaría asi:

<div class="form-group">
    <label for="fieldnombreEvento" class="col-sm-2 control-label">Nombre evento</label>
    <div class="col-sm-10">
        <?php echo $this->tag->textField(["nombre_evento", "size" => 30, "class" => "form-control", "id" => "fieldnombreEvento"]) ?>
    </div>
</div>

<div class="form-group">
    <label for="fieldNickname" class="col-sm-2 control-label">Nickname</label>
    <div class="col-sm-10">
        <?php echo $this->tag->textField(["nickname", "size" => 30, "class" => "form-control", "id" => "fieldNickname"]) ?>
    </div>
</div>

<div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
        <?php echo $this->tag->submitButton(["Buscar", "class" => "btn btn-default"]) ?>
    </div>
</div>

<?php echo $this->tag->endForm(); ?>

<div class="row">
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>Nombre Usuario</th>
                <th>Lenguaje programación</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($page->items as $lenguaje): ?>
                <tr>
                    <td><?php echo $lenguaje->nombre_lenguaje ?></td>
                    <td><?php echo $lenguaje->nombre_usuario ?></td>
                </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
</div>

<div class="row">
    <div class="col-sm-1">
        <p class="pagination" style="line-height: 1.42857;padding: 6px 12px;">
            <?php echo $page->current, "/", $page->total_pages ?>
        </p>
    </div>
    <div class="col-sm-11">
        <nav>
            <ul class="pagination">
                <li><?php echo $this->tag->linkTo("admin/usuarios/index", "Primero") ?></li>
                <li><?php echo $this->tag->linkTo("admin/usuarios/index?page=" . $page->before, "Anterior") ?></li>
                <li><?php echo $this->tag->linkTo("admin/usuarios/index?page=" . $page->next, "Siguiente") ?></li>
                <li><?php echo $this->tag->linkTo("admin/usuarios/index?page=" . $page->last, "Último") ?></li>
            </ul>
        </nav>
    </div>
</div>

Ahora  nuestro controller llamado usuarios, tendrá un indexAction con la lógica del paginador.

Para la query que va a paginar y el paginador, tendremos que hacer uso en nuestra class de:

use Phalcon\Paginator\Adapter\Model as Paginator;
use Phalcon\Paginator\Adapter\QueryBuilder as PaginatorQueryBuilder;

Y ahora lo importate de la lógica.

Necesitamos que el criteria conozca los fields de nuestro $_POST y necesitamos coger los parámetros enviados desde el index para ambos modelos creados.

$queryLen = Criteria::fromInput($this->di, 'LenguajesProgramacion', $_POST);
$paramsLen = $queryLen->getParams();
$queryUsuarios = Criteria::fromInput($this->di, 'Usuarios', $_POST);

El problema que se encuentra al utilizar dos modelos es que recibimos parámetros de distintas queries. Por lo que tendremos que mezclar las conditions y el bind que te ofrece el criteria por defecto, quedando el código así:

$numberPage = 1;
$paramsPart = is_null($queryPart->getParams()) ? null : $queryPart->getParams();
if(isset($paramsPart) && !empty($paramsPart)){
    if (isset($params['conditions']) && !empty($params['conditions']) && (isset($paramsPart['conditions']) && !empty($paramsPart['conditions']))) {
    $params['conditions'] .= ' and ' . $paramsPart['conditions'];
    $params['bind'] = array_merge($paramsPart['bind'], $params['bind']);
} elseif (isset($paramsPart['conditions']) && !empty($paramsPart['conditions'])) {
            $params = $paramsPart;
        }
    }
$this->persistent->parameters = $params;

A continuación creamos la query a la cual pasamos los parametros ya bindeados con el criteria y con el join que necesitamos.

$builder = $this->modelsManager->createBuilder($params)
            ->columns(['Usuarios.nombre_usuario', 'LenguajesProgramacion.nombre_lenguaje'])
            ->from(LenguajesProgramacion)
            ->leftJoin('Usuarios', 'Usuarios.id = LenguajesProgramacion.usuario_id', 'Usuarios')
            ->orderBy('Usuarios.id');

Y ahora el paginador con el resultado del builder anterior.

$paginator = new PaginatorQueryBuilder(
    [
        'builder' => $builder,
        'limit'   => 10,
        'page'    => $numberPage,
    ]
);
$this->view->page = $paginator->getPaginate();

Lo importante de este ejemplo es el merge del bind y de las conditions. Lo demás se puede encontrar con facilidad en la doc de phalcon.