Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Мужики, признавайтесь кто делал этот сайт на ларе) Пытаюсь оформить е-осаго полис через него, но перед оплатой стоит лютая капча:
https://eosago.soglasie.ru/images/captc … 5143933071
Которую ну вот прямо нереально пройти.
Причем прикол в том, что если убрать GET-параметр time, то там видно, что заюзана стандартная капча для 4й лары. Да и сервер при ответе дает понять, что это ларавел:
Set-Cookie:laravel_session=ХХХХХХХ; expires=Wed, 27-Dec-2017 18:41:00 GMT; Max-Age=7200; path=/; httponly
Причем в коде лютая ржака местами:
Вообщем, кто делал - отзовись! Скажи какой там чит надо применить, чтобы пройти её?) Пару раз проканывало вводить кириллицу за место части латинских букв, но в итоге валится ошибка со страницы: https://eosago.soglasie.ru/pay
Ошибка связи с сервером РСА
Повторите попытку позже
Если просто зайти на эту страницу - вообщем-то тоже самое.
Или всё таки гиблое дело и там сейчас понапихано так, что в любом случае не пройти капчу / не произвести оплату?
Если нужны только данные, а не сам объект, можно использовать ->toArray() (или toJson) на объекте:
$data = Model::get()->toArray();
Нужны только данные, чтобы вывести объект в шаблоне.
Но надо будет поменять вид обращений в массиве к этому объекту (сменить $obj->prop на $obj['prop']) - верно?
И такой способ является хорошим тоном или это уже слишком?
Сейчас подумалось: быть может имеет смысл мне кешировать результат выполнения, а не объекты?
Мне нужно кешировать:
1. view composers - не понимаю как
2. controllers - на сколько понял по гуглу - проще всего через view()->render() (с различным отображением для различных сессий возможно в этом нет смысла, как и с тем, что в кеше могут оказаться разные пагинаторы).
Начал прикручивать кеширование к разрабатываемому проекту и увидел интересную особенность сериализации данных в PHP (на сколько я понял) - он серилиазует весь объект целиком (что логично), но мне не хочется, чтобы кеш "разбухал" за счет лишних данных.
Это как-то решаемо (писать свой сериализатор не очень хочется)?
Просто дело вот в чем: я сериализую 5 объектов, полученных из БД, причем чтобы уменьшить количество данных - беру только те, что нужны (использую
->select('column1', 'column2')
). И получается такая ситуация, что для того, чтобы закешировать 280 байт данных (специально посчитал, но это только values, без key и т.д), я кеширую строку в 7950 байт, где большую часть данных занимают:
1. Массив fillable - в каждом объекте
2. Массив attributes (он нужен, я знаю), который дублируется точно такими же данными в массиве original - в каждом объекте
3. Кучей пустых свойств/массивов (casts, dateFormat, appends, events, observables, relations, touches, hidden, visible, guarded и т.д) - в каждом объекте
Мне не жалко места, но хочется разобраться и возможно обнаружить какие-то "best practice" по кешированию в laravel (мб вы сейчас сразу скажите, что мне не нужно кешировать объекты модели).
Опубликовал в виде статьи: https://laravel.ru/posts/768
Надеюсь, что здесь приветствуется такое
Спасибо за ссыль. Да, я уже давно собираюсь развиваться в сторону питона и джанго, не как не могу собраться)) но вы своими отзывами дали так сказать "волшебный пендаль" мне)) думаю в ближайший уик енд начну изучать его))
Вот это круто!) Главное - не откладывать. Мне будет интересно узнать ощущения после прохождения туториала - что понравилось/что не понравилось. Самое важное, не ставить всё в систему глобально, а юзать virtualenv - если бага какая-то ловится, то она гуглежом решается быстро - на stackoverflow очень много ответов по джанге/питону
Оооох, какой большой ответ) Часть из него не смогу парировать, т.к. не совсем понимаю о чем речь))) Поэтому оставим этот разговор на потом)
Добавлю только то, что django - тоже как раз сделан по принципу, что за выходные можно собрать прототип (и это подтверждено неоднократно), мб поэтому они так и схожи с laravel
К слову, мне было бы интересно узнать о первых впечатлениях от джанги от тех, кто до этого много писал на laravel. Т.к. я свои впечатления о ларавел рассказал, интересно каково с обратной стороны
Не заливайте фото на дропбокс) Ничего не отображается))
Забыл добавить важную особенность: в джанге не нужно писать валидаторы, если делать форму из модели. Т.к. модель знает что за тип данных, обязательное поле или нет, его размерность и т.д. В ларавел же нужно делать $validators и явно прописывать все правила, т.к. модель не знает о своем содержимом.
как раз механизм миграций в ларавеле почти полностью скопирован с рельсы. причина того что сделано именно так как раз в том чтобы в модели поля описывать было не нужно от слова вообще. базовая рабочая модель в ларе выглядит так:
class Post extends Model {}
А зачем нужна необходимость не описывать поля модели? В любом случае ведь при работе с моделью Post - мы должны знать какие поля есть в этой модели, чтобы не было вот такого:
$p = new Post();
$p->atata_not_found_field = 1;
Я соглашусь, что если мы пилим приложение поверх существующей структуры БД, то там и миграции не нужны по факту. Но в идеале то - приложение делается с нуля, значит и структура БД прорабатывается вместе с ним.
Ещё так же меня из-за этого крайне бесит задавать массив $fillable. Это какой-то непомерной длинны костыль) Я и так уже в миграции написал все поля, которые будут в этой модели, зачем мне разрешать записывать данные в них? Одно дело - исключать возможность записи - это несомненно полезная возможность. Но вот, имхо, по дефолту - нужно чтобы все поля были "fillable".
плюс – позволяет выбирать поля из базы частично – то есть не только select * from ..., а например select id, title from ... – в этом случае только выбранные поля будут доступны на объектах модели
Выбрать только определенные поля из БД - это везде не проблема, имхо)
по-моему как раз те виды active record-ов и data mapper-ов где приходится описывать структуру базы данных в моделях мне кажутся очень неудобными. особенно когда значимые настройки задаются через пхп-комментарии – вот это особенно люто бесит
С настройками в виде пхп-комментариев - не встречался, жесть :-D
Но вот пример из туториала джанги как там описывается модель и создается миграция:
from django.db import models
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
def __str__(self): # __unicode__ on Python 2
return self.full_name
class Article(models.Model):
pub_date = models.DateField()
headline = models.CharField(max_length=200)
content = models.TextField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
def __str__(self): # __unicode__ on Python 2
return self.headline
А затем из этих моделей генерируем миграцию:
$ python manage.py makemigrations
И применяем эту миграцию к БД:
$ python manage.py migrate
Всё! Огромный плюс описания полей в модели - это работа с IDE. IDE среда будет знать о полях и будет работать автокомплит, что очень полезно, когда у тебя в модели 50+ полей (реальный рабочий пример, причем там и вправду все поля нужные были и большинство вещей и так были вынесены в отдельные модели, просто структура была крайне сложная и нужно было хранить в модели большое число данных).
в моделях ларавеля все донастройки – это опция, их есть смысл трогать только когда структура базы отличается от той, которая получается по дефолту по соглашениям элоквента.
Вот, кстати, хорошо, что упомянули элоквент Я так и не понял зачем нужен DB Query Builder, когда есть Eloquent ORM. Я понимаю, что элоквент как раз таки использует квери билдер, но зачем они разделили эти сущности для работы с БД? Логичнее было бы сделать Query Builder в Eloquent, чтобы не было желания делать raw sql запросы (это меня всегда и везде бесило, 99% задач, что я делал - всегда решались средствами ORM, а если не выходило - нужна была денормализация, которая в итоге приносила только плюсы, т.к. неправильно продуманная структура БД - это априори работа с raw sql в итоге).
Ого, круто) Теперь понятно. Спасибо за развернутый ответ)
Пожалуйста)
Вообще, советую попробовать выполнить официальный туториал от неё: https://docs.djangoproject.com/en/1.11/#first-steps - даже в нем можно подчерпнуть много полезного.
PS: несомненный плюс джанги в том, что там есть автогенерируемая админка, которая покрывает 99% процентов задач при работе с ней без всякого волшебства.
Интересно, а почему с джанго на лару перешли, раз вы джанго так хорошо знаете?
С джангой опыт работы более 5 лет, наскучило уже)
Ну, а вообще - задача сейчас такая была - сделать на php, т.к. если бы я написал на python, то потом мало кто смог бы что-то поправить или изменить там из-за специфичности. Плюс в django очень мудреная и хитрая связка nginx+uwsgi, которую даже я не понимаю до конца. Также, для джанги нужна кучка зависимостей, поэтому её в принципе не реально на шаред хостинг поставить (даже пробовать не стоит). Ну и наконец, деплой - это очень мощная, но в тоже время тяжелая штука в арсенале джанги. Чтобы развернуть проект на сервере - нужно установить себе проект (это во всех деплоях, что я видел так было - может и есть другие, но давно уже не изучал эту тему), а это значит, что нужно поставить: virutalenv + pip + зависимости проекта.
В целом то джанга мне очень нравится, коммьюнити там очень хорошее и пакетов готовых под неё очень много. Но вот как раз одна из основных причин использования ларавел - это то, что потом нужно, чтобы конечный пользователь проекта смог что-то сменить внутри, а это совсем не питонист будет)
UPD3:
Чтобы предотвратить зеркало /?page=0 и убрать возможность заходить на страницы с page < 0, нужно в контроллере изменить
if ($page > ceil($total / $per_page)) {
На:
if (($page > ceil($total / $per_page)) || ($page < 1)) {
Хм. Столкнулся с тем, что мой пагинатор корректно работает только для /. В категориях он уже не знает о правильном пути, что странно (в пагинации всегда /?page=X, а не /category?page=X, как подразумевается).
Я думал, что в нём $this->path уже подразумевает "разруливание" таких ситуаций. Пойду думать как проще всего это обойти без костылей, если не напишу решения - буду благодарен за совет.
-----------
UPD:
Инициализацию пагинатора в контроллере следует сменить с:
new SEOFriendlyPaginator($items, $total, $per_page, $page);
на
new SEOFriendlyPaginator($items, $total, $per_page, $page, ['path' => $request->getPathInfo()]);
-----------
UPD2:
Тогда и редирект можно сменить
с
return redirect(route('home'), 301);
на
return redirect($request->getPathInfo(), 301);
Сделал для себя решение для того, чтобы убрать из пагинатора зеркало страницы /?page=1 (оно зеркалит /) и убрал вывод пустой страницы при запросе аля /?page=99999.
Выкладываю, чтобы вы покритиковали и сказали как можно было сделать проще и изящнее. В итоге буду не против, если кто-то выложит это в виде статьи, если я не сделаю этого в течении месяца после создания данной темы.
Изначально хотел просто подменить view (resources/views/vendor/pagination/default.blade.php), но уткнулся в то, что в LengthAwarePaginator нет метода для получения предыдущей страницы, есть только метод для получения url предыдущей страницы ($paginator->previousPageUrl()). Если пробовать разбирать через него, то это будет плохим способом, т.к. изначально пагинатор может выводиться на разных страницах, соответственно переписывать все пути - не кошерно.
Потом наткнулся на комментарий @FreeWebber и понял, что это можно сделать через наследования, придется только чуть-чуть поколдовать.
Пронаследовал Illuminate\Pagination\LengthAwarePaginator и изменил метод url() - сделал, чтобы при визите на любую страницу с ?page=X, где X > 1 - у первой страницы (page=1) не добавлялся GET-параметр "page=1":
<?php
namespace App\Utils;
use Illuminate\Support\Str;
use Illuminate\Pagination\LengthAwarePaginator;
class SEOFriendlyPaginator extends LengthAwarePaginator {
public function url($page)
{
if ($page <= 0) {
$page = 1;
}
$parameters = array();
if ($page > 1) {
$parameters = [$this->pageName => $page];
}
if (count($this->query) > 0) {
$parameters = array_merge($this->query, $parameters);
}
$url = $this->path;
$params_delimiter = (Str::contains($this->path, '?') ? '&' : '?');
$params_query = http_build_query($parameters, '', '&');
$fragment = $this->buildFragment();
if (!empty($params_query)) {
$url .= $params_delimiter . $params_query;
}
if (!empty($fragment)) {
$url .= $fragment;
}
return $url;
}
}
Затем в своем Controller использовал этот пагинатор и:
1. Сделал редирект с /?page=1 на /
2. Сделал вывод 404 ошибки, если каким-то образом у нас хотят получить страницу, которой не существует (аля /?page=99999)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
use App\Utils\SEOFriendlyPaginator;
class SectionController extends Controller
{
public function home(Request $request) {
$per_page = config('project.per_page'); // добавил соответствующий конфиг
$page = $request->input('page');
$items = Article::filtered(); // здесь применяется мой собственный scope - filtered, это что-то вроде published, но с нужной мне сортировкой
$total = $items->count();
if (!is_null($page)) {
if ($page == 1) {
return redirect(route('home'), 301); // здесь я делаю редирект на роут 'home', не придумал как сделать редирект через $request
}
if ($page > ceil($total / $per_page)) {
abort(404);
}
} else {
$page = 1;
}
$items = $items->skip(($page - 1) * $per_page)->take($per_page)->get(); // берем только нужные элементы
$items = new SEOFriendlyPaginator($items, $total, $per_page, $page);
return view('sections.index', [
'items' => $items
]);
}
}
И в дальнейшем во view я теперь делаю:
@foreach ($items as $item)
{{-- вывод каждого элемента --}}
@endforeach
{{-- Вывод пагинатора --}}
{{ $items->links() }}
Всё. Как уже и писал выше - предложения, критика и советы - приветствуются!
не знаю про django, а вот с ruby on rails у ларавеля как раз таки очень много общего
И с джангой очень много общего на самом деле, но некоторые вещи кажутся "недописанными", как, например, миграции в БД - в джанго поля задаются в модели, а потом генерируется миграция запуском команды. Поэтому априори получается в итоге проще, не нужно искать миграцию, чтоб вспомнить что за поля в БД есть в текущий момент - всё в модели прописано.
С рельсами, к сожалению, не знаком )
что-то точно не так, твой композер срабатывает на все виды, а не на какой-то конкретный. ты точно регаешь композеры в своём сервис-провайдере в boot() с помощью view()->composer('myview.name', SomeComposer::class) ?
Всё с точностью до наоборот
Я регистрирую его с помощью:
view()->composer('*', 'App\Http\ViewComposers\CategoriesComposer');
Большое спасибо за наводку, в первом аргументе прописал view, в которой будут использоваться данные из выполнения этого view composer и всё встало на свои места.
Я по своей неопытности подумал, что туда нужно передать view, в которых будет использоваться тот @include('view') как результат выполнения композера. Как уже писал выше - в привычной мне django совсем другая логика написания таких вещей, поэтому у меня в голове возник небольшой вакуум, который и привел к такому глупому и опрометчивому поступку.
PS: всё чаще задумываюсь по окончанию проекта написать статью "laravel vs. django", где показать как что-то делается в одном фреймворке и как в другом. Не знаю, правда, соберусь ли в итоге, т.к. времени не так много на такие вещи, но, думаю, и для меня было бы полезно изложить некоторые мысли, которые могли бы продвинуть на следующий уровень (знающие сказали бы как "правильно"), и все бы посмотрели что лучше реализовано в django и взяли на заметку (не знаю есть ли среди ваших-наших рядов ларавел контрибьюторы, но вдруг...).
Встретил интересную особенность (косяк?) - если я сделал View Composer и включил его, а внутри него выполняется запрос к БД, то в Debug Bar этот запрос выполнится ровно столько раз, сколько Views было загружено и выполнено. Да, я предусматриваю в дальнейшем кеширование, но сам факт того, что композер будет выполняться к каждой отдельной view (шаблону) - меня смущает.
Я что-то делают не так?
PS: View Composer использую как генератор блока "Категории" на всех страницах сайта.
Как же ужасно задокументированы View composers... Всё по коду хорошо, но описание применения - совсем невнятное.
В django framework было очень похожее на это: называлось template tags - это такие отдельные части приложения, которые могут появляться на любой странице. Например, список категорий сайта, список последних добавленных новостей и так далее. Суть в том, что их нужно выводить не через view, а они могут использоваться в любом шаблоне. Конечно, там это совсем по-другому реализовано (имхо, гораздо красивее и без надобности определять "области видимости"), но уж что имеем.
@constb, огромное вселенское СПАСИБО за такое подробное описание, всё наконец-то сошлось в голове
нужно что-то вроде https://github.com/RachidLaasri/LaravelInstaller взять за основу и сделать свой собственный веб-установщик. как потом решать задачу обновления на новые версии через веб-интерфейс, тут я не подскажу, скорее всего надо будет писать всё своё
Визуально выглядит очень круто, я как раз что-то такое хотел себе сделать, но более в человечном виде (не задавать .env плейн-текстом, а разбить это на шаги). Обновления на новые версии пока не предусматриваю, т.к. не ясно "выстрелит" ли вообще данная идея. Делаю хорошо и качественно пока для себя, но с возможностью расширения и для использования другими, поэтому по опыту стараюсь сразу же закрывать все такие огрехи, чтобы потом не переписывать всё с нуля.
но имей в виду – если твои пользователи умеют только залить что-то по фтп, скорее всего твой код будет крутиться на обычном дешманском шареде, а это значит – кэш либо файловый либо база, очередей – нет и не будет, сессии либо на файлах либо в базе и так далее
Тут проблем не возникнет, изначально всё будет сводиться к установке VestaCP + настройке чистого сервера под требуемое окружение (установка memcached (скорее всего), supervisord, monit и т.д.), поэтому априори они не смогут на шареде всё это добро развернуть
но наверное самая большая засада будет с тем что 99 из 100 будут лить твой код в корень веб-сервера и ругаться что ничего не работает. о том что корнем должен быть public – они сначала не прочитают, а потом если ты будешь это объяснять – не поймут что ты им объясняешь.
Да, думал над этим, пока первичным решением вижу разместить index.html в корень пакета (не в public), где написать о том, что они не поменяли DOCUMENT_ROOT. Потом, возможно, переделаю инсталлер, чтобы он менял эту настройку и ребутил веб-сервер, но это может оказаться сомнительной затеей, если они будут складывать конфиги апача/nginx куда-то в другие места
git pull – наше всё
ну на самом деле конечно там ещё некоторые команды надо выполнить, всё-таки есть в ларавеле и продакшен-специфичные оптимизации, и в maintenance mode есть смысл приложение переводить при обновлении. плюс если ассеты собираются на сервере – запускать gulp или mix. плюс если используются очереди – рестартовать обработчики
у меня в одном из проектов скрипт-обновлятор выглядит так:
#!/bin/bash php artisan down git pull composer install composer dump-autoload -o php artisan config:cache php artisan route:cache php artisan laroute:generate gulp --production php artisan queue:restart php artisan up
gulp – потому что проект на L5.2, laroute – пакет, который позволяет генерить маршруты в js-коде, аналогично route() на пхп-стороне. composer install включает в себя php artisan optimize, и в config/compile.php у меня добавлены некоторые из своих часто используемых классов
кроме «полного» обновлятора, есть ещё мини-обновлятор, который не пересобирает ассеты и работает намного быстрее
Большое спасибо за ценный ответ, подчерпнул для себя кое что новое
Но остались некоторые вопросы:
1. Есть ли готовые решения для разворачивания всего необходимого на сервере (Ubuntu 16.04)? В питонячих пакетах это много где называлось bootstrap или provision.
2. Git - воистину самое полезное и необходимое в разработке, но если думать о том, что мою разработку будут запускать win-юзеры, которые привыкли к установке CMS аля "скопируйте все файлы по ftp": как мне лучше построить систему, чтобы потом у них не возникло проблем?
Перефомулирую вопрос для лучшего понимания: могу ли я копировать локальную папку vendor на сервер без запуска "composer install"? И нужно ли предварительно как-то сказать локальному композеру, что это продакшен окружение?
Всем привет!
Подозреваю, что у каждого третьего тут написал свой специфический деплой лары на продакшен. Но возможно кто-то сделал универсальное решение, которое можно адаптировать и использовать у себя.
Ищу такое решение для разворачивания сервера и последующего деплоя ларавел на Ubuntu 16.04. Все дополнительные необходимые вещи прикручу сам (ещё большим счастьем для меня будет то, если не нужно будет ставить дополнительных пакетов, чтобы оно было независимое и работало без установки чего-то через композер/в систему)
Про envoy знаю, но даже с ним всё сведется к тому, что нужно будет писать полностью деплой с нуля. И меня больше всего страшит работа с композером (признаюсь: я юзаю его первый раз сейчас с ларой, локально на макоси проблем не возникло, всё поставил по инструкции Valet).
Я не понимаю могу ли я просто взять директорию с ларой из локального окружения и залить её по ftp (есессно, поменяв .env). Не понимаю есть ли в композере такие вещи как "develop" и "production" окружение?
Когда ранее работал с Django (python) - было множество прекрасных готовых решений под Ansible, Chef, Puppet для неё. Надеюсь, что и в сообществе Laravel что-то такое присутствует
нет, release() не увеличивает количество попыток. он просто возвращает задачу в очередь и откладывает её выполнение на указанное количество секунд
насчёт «не делать кучу действий за раз» – как раз наоборот, очереди спроектированы так чтобы можно было легко распараллеливать выполнение задач на множество процессов, разбивая их на отдельные логические «кубики»
Переписал на этот вариант и проверил - release() - увеличивает количество попыток
нет, release() не увеличивает количество попыток. он просто возвращает задачу в очередь и откладывает её выполнение на указанное количество секунд
Понял, спасибо.
Если после того как посплю - не придет озарения, то скорее всего так и перепишу работу, а то запуск через "планировщик" по расписанию меня очень сильно смущает тем, что пропадает "слушатель" и весь смысл очередей
ещё кстати паузу между задачами очень просто сделать, если вспомнить что очередь при работе генерирует события, в частности Illuminate\Queue\Events\JobProcessing перед выполнением задачи и Illuminate\Queue\Events\JobProcessed – после. если задать обработчик и в нём вызвать sleep() – ты и получишь задержку перед обработкой следующей задачи
если решишь так делать – настоятельно советую вынести задачи по апи в очередь с отдельным именем и в «засыпающем» обработчике проверять $event->connectionName, чтобы не тормозить работу вообще всех очередей без разбору
Спасибо, идея интересная (хоть и чуть костыльная) - как раз задачи по API будут в отдельной очереди лежать. Но я не понял про засыпающий обработчик и коннект - он может отвалиться или что может пойти не так?