Русскоязычный форум, посвященный фреймворку Kohana

Все о фреймворке Kohana. Обсуждение уроков, документации.
Текущее время: 10 авг 2020, 13:53

Часовой пояс: UTC + 4 часа [ Летнее время ]




Начать новую тему Ответить на тему  [ Сообщений: 10 ] 
Автор Сообщение
СообщениеДобавлено: 28 ноя 2013, 03:17 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 05 июн 2012, 03:08
Сообщения: 213
Есть простой пример: вывести список пользователей и список их ролей
Код:
// Контроллер
class Controller_User extends Controller_Frontend
{
    public function action_index()
    {
        $users = ORM::factory('User');

        $total_items = $users
            
->reset(FALSE)
            ->count_all();

        $pagination = Pagination::factory(array(
            'total_items'    => $total_items,
            'items_per_page' => 10,
            'view'           => 'v_pagination'
        ));

        $users = $users
            
->offset($pagination->offset)
            ->limit($pagination->items_per_page)
            ->find_all();


        $this->content = View::factory('user/v_index',
            array(
                'pagination' => $pagination,
                'users'      => $users,
            ));
    }
}

// Модель
class Model_User extends Model_Auth_User
{
    public function get_roles()
    {
        return $this->roles->find_all();
    }
}


// Файл: views/user/v_index.php
foreach ($users as $user)
{
    echo '<br/>'.$user->id.' | '.$user->username.' | ';

    foreach ($user->get_roles() as $role)
        echo $role->name.', ';
}

echo $pagination;


/*

Выводит: id | username | roles
 
1 | guest | guest,
2 | amberlex | login, user, blogger, admin,
3 | kevgen | login, user, blogger, admin,
4 | user-demo | login, user,
5 | blogger | login, user, blogger,

*/
 

Ну вот собственно запросы
Изображение

т.е. на 5 пользователей 7 запросов (1 для подсчета, 1 для выборки пользователей, остальные - выборка ролей для каждого пользователя)

Как это можно оптимизировать или кешировать?

Вариант
Код:
public function get_roles()
{
    $cache = Cache::instance();
    $key = 'roles_user_'.$this->id;

    if ( ! $roles = $cache->get($key))
    {
        $roles = $this->roles->find_all(); 
        $cache
->set($key, $roles);
    }
    return $roles;
}
 
После кеширования выдает
Код:
ErrorException [ Warning ]: mysql_fetch_object(): supplied argument is not a valid MySQL result resource

Вариант
Код:
public function get_roles()
{
    $cache = Cache::instance();
    $key = 'roles_user_'.$this->id;

    if ( ! $roles = $cache->get($key))
    {
        $roles = $this->roles->find_all()->as_array(); 
        $cache
->set($key, $roles);
    }
    return $roles;
}
  
После кеширования без ошибок, но запросы все равно выполняются.
Но и этот и предыдущий, создают 5 папок в кеше на каждого пользователя - это ерунда какаято выходит, явно не оно.

_________________
http://de-en.info (работает на Kohana 3.3)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 28 ноя 2013, 13:05 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
в любом случае нужно дописать метод для получения такого списка.

вариант 1
1 запрос: получение списка пользователей
2 запрос: получение списка ролей на основании запроса 1
ну и потом когда циклом будешь пользователей выводить подциклом их роли(попутно удаляя отработанные значения, чтобы сократить в следующий подцикл)

вариант 2:
1 запрос: выбираешь список ролей (на тот маловероятный случай, когда какие-то роли не используются можно добавить проверку на связь ролей с таблицей users_roles)
2 запрос: выбираешь список пользователей, подзапросом выбираешь их роли и объединяешь их в строку(с разделителем ';' например)
Циклом выводишь пользователей, explode колонки с ролями пользователей и с помощью массива из запроса 1 выводишь их.
Можно конечно и 1 запросом этот вариант реализовать, но так придется больший объем данных передавать, а мне это кажется более медленным, хотя честно говоря не тестил.

вариант 3
кешировать надо Model_User целиком, ну и в зависимости от параметров сохранять под определенной меткой например model:user:list:0:50 где 0 это offset, а 50 Limit. по поводу места, дискового пространства обычно хоть жопой жуй, а вот процессорная нагрузка строго лимитирована т.ч. за размер кеша можно особо не переживать - окупится.

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 28 ноя 2013, 13:59 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 05 июн 2012, 03:08
Сообщения: 213
Цитата:
в любом случае нужно дописать метод для получения такого списка.
Так есть вроде метод: $user->get_roles()

Странно, неужели никто не решал подобную задачу? Вроде довольно стандартная.
Например требуется вывести список записей блога.
К каждой записи из списка вывести:
-- список тегов
-- список категорий
На страницу из 15 записей будет 32 запроса (1 для подсчета, 1 для выборки; и в цикле: 15 для тегов и 15 категорий)

Задачу получилось решить вообще двумя запросами, только не знаю насколько это нормально будет.
Примерно так
Код:
# Для подсчета
SELECT COUNT(*) AS `records_found` FROM `users` AS `user`;

# Для выборки
SELECT
  GROUP_CONCAT(roles.id) AS `role_ids`,
  GROUP_CONCAT(roles.name) AS `role_names`,
  `user`.`id` AS `id`,
  `user`.`username` AS `username`

FROM `users` AS `user`

JOIN `roles_users`
  ON (`roles_users`.`user_id` = `user`.`id`)
JOIN `roles`
  ON (`roles`.`id` = `roles_users`.`role_id`)

GROUP BY `user`.`id`

LIMIT 15 OFFSET 0;
Изображение
Потом уже в шаблоне
Код:
foreach ($users as $user)
{
    echo '<br/>'.$user->id.' | '.$user->username.' | '.Helper::a_roles($user->role_ids, $user->role_names);
}

// И в класе Helper
// Ссылки ролей (или что там потребуется)
public static function a_roles($a, $b)
{
    $a1 = explode(',', $a);
    $a2 = explode(',', $b);
    $arr = array_combine($a1, $b1);

    $str = '';
    foreach ($arr as $id => $name)
    {
        // Ссылка для фильтра по ролям (для примера)
        $str .= HTML::anchor('user/index/'.$id, $name).', ';
    }
    return rtrim($str, ', ');
}
 

Два запроса, 15 записей (хоть 100) на странице
Только в случае блога, для тегов и категорий в запросе будет уже 4 GROUP_CONCAT
Думаю построить нужный массив быстрее, чем куча запросов? Как думаете?

_________________
http://de-en.info (работает на Kohana 3.3)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 28 ноя 2013, 16:36 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
Цитата:
Странно, неужели никто не решал подобную задачу? Вроде довольно стандартная.

а толку? ты все равно сделал по своему. в варианте 2 я написал почему твой вариант менее удачен.

Цитата:
Думаю построить нужный массив быстрее, чем куча запросов? Как думаете?

быстрее думаю(но простестить не мешает), только зачем тебе для вывода хэлпер, причем такой кривой? сделай парсинг прямо во вьюхе и все.

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 10:50 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 05 июн 2012, 03:08
Сообщения: 213
Цитата:
а толку? ты все равно сделал по своему
Вариант 1 я не понял
Цитата:
только зачем тебе для вывода хэлпер, причем такой кривой?
Чем кривой? Может так:
Код:
public static function a_roles($a, $b, $sep = ', ')
{
    $arr = array_combine(explode(',', $a), explode(',', $b));

    ob_start();
    foreach ($arr as $id => $name)
    {
        // Ссылка для фильтра по ролям (для примера)
        echo HTML::anchor(user/index/.$id, $name).$sep;
    }
    return rtrim(ob_get_clean(), $sep);
}
 

_________________
http://de-en.info (работает на Kohana 3.3)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 13:38 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
Цитата:
Вариант 1 я не понял

вариант 1
1 запрос: получение списка пользователей
SELECT * FROM users LIMIT 0, 10
2 запрос: получение списка ролей на основании запроса 1
SELECT roles.id, roles.name, users_roles.id AS user_id FROM users_roles LEFT JOIN roles ON roles.id = users_roles.role_id WHERE users_roles.id IN (список id пользователей из запроса 1)
так понятно?

Цитата:
Чем кривой?

названием метода, названием переменных, кривыми ссылками. второй вариант еще хуже.
смысл писать метод который используется только в одном месте? ведь этот класс будет постоянно в памяти болтаться.

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 15:15 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 05 июн 2012, 03:08
Сообщения: 213
Цитата:
названием метода, названием переменных, кривыми ссылками.
Думал кривой код в смысле алгоритма или неоптимизации. Вообще я про запросы речь вел и чтобы быстрее понять суть, а не вникать во вспомогательный код, укоротил его для примера.
У меня с небольшими изменениями (заменил $str на ob_start и перенёс Arr::combine в foreach) он выглядит так
Код:
/**
 * Get roles as list of anchors separated by commas
 *
 * @param  $role_ids    string ids roles separated by commas
 * @param  $role_names  string names roles separated by commas
 *
 * @return string
 */
public static function anchors_roles($role_ids, $role_names)
{
    // Separator for links
    $sep = ', ';

    // Convert strings to arrays
    $role_ids   = explode(',', $role_ids);
    $role_names = explode(',', $role_names);

    ob_start();
    foreach (Arr::combine($role_ids, $role_names) as $id => $name)
    {
        $url = Route::url('backend',
            array(
                'controller' => 'user',
                'action'     => 'index',
                'id'         => $id
            
)
        );

        echo HTML::anchor($url, $name).$sep;
    }

    return rtrim(ob_get_clean(), $sep);
}
 
Думаю для примера это было бы лишним.

Чтобы не висел в памяти можно перенести во вьюху (здесь тоже больше всего, чем в примере)
Код:
<table class="table">
    <?php foreach ($users as $user): ?>
    <tr>
        <td><?php echo $user->username ?></td>
        <td><?php echo anchors_roles($user->role_ids, $user->role_names) ?></td>
    </tr>
    <?php endforeach ?>
</table>


<?php 

// ===============
// === Helpers ===

/**
 * Get roles as list of anchors separated by commas
 *
 * @param  $role_ids    string ids roles separated by commas
 * @param  $role_names  string names roles separated by commas
 *
 * @return string
 */
function anchors_roles($role_ids, $role_names)
{
    // Separator for links
    $sep = ', ';

    // Convert strings to arrays
    $role_ids   = explode(',', $role_ids);
    $role_names = explode(',', $role_names);

    ob_start();
    foreach (Arr::combine($role_ids, $role_names) as $id => $name)
    {
        $url = Route::url('backend',
            array(
                'controller' => 'user',
                'action'     => 'index',
                'id'         => $id
            
)
        );

        echo HTML::anchor($url, $name).$sep;
    }

    return rtrim(ob_get_clean(), $sep);
}
Мне кажется так нагляднее, чем парсить в цикле и не очень представляю как это красиво сделать)

Пример 1 понял, попробую, спасибо.

_________________
http://de-en.info (работает на Kohana 3.3)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 15:40 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
не очень понимаю зачем эти пляски с бубном вокруг буфера обмена? по моему это более ресурсоемкий вариант по сравнению с первоначальным.
функция продолжит висеть в памяти после выполнения вьюхи если так хочется функцией то тогда используй замыкание.
во вьюху можно добавить кеширование через Fragment

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 17:04 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 05 июн 2012, 03:08
Сообщения: 213
Это так что-ли?
Код:
<table class="table">
    <?php foreach ($users as $user): ?>
    <tr>
        <td><?php echo $user->username ?></td>
        <td><?php echo $anchors_roles($user->role_ids, $user->role_names) ?></td>
    </tr>
    <?php endforeach ?>
</table>

<?php
$anchors_roles 
= function ($role_ids, $role_names) 
{
    ...
    ...
}

И не очень понимаю такую экономию. ORM вообще тащит все поля таблицы, хотя мне нужны всего несколько и в чем смысл экономить на функции?

_________________
http://de-en.info (работает на Kohana 3.3)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: 29 ноя 2013, 17:31 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
ага так, только обычно объявление классов и функций идет до их вызова.
если оптимизировать, то все. а то ты оптимизируя запросы наплодил кучу новых проблем.

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 10 ] 

Часовой пояс: UTC + 4 часа [ Летнее время ]


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Все о фреймворке Kohana  | 
Powered by phpBB® Forum Software © phpBB Group