«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
https://github.com/kichiweb123/students Аноним 2017/11/17 04:24:00  №1093271
>>1093242

Теперь поговорим про MVC. Ты пишешь, что прочел урок про MVC, хорошо. Там было написано, что модель - это не один класс, а что-то вроде ядра приложения, которое может выполнить любую функцию, которая есть в приложении, но которое не занимается вводом и выводом данных пользователю.

Модель не обязана вообще быть классами, это может быть например набор функций. А может и набор классов. В моем уроке по MVC в коде модель - это классы PostService и Post.

Вот давай подумаем, какие функции есть в нашем приложении? Что должно быть в модели? И есть ли это в твоей модели? Попробуй у себя в коде найти функции, которые это делают (в одно действие):

- узнать, сколько всего в базе студентов (чтобы вычислить число страниц)
- получить список студентов частично (постранично)
- получить результат поиска студентов по ключевому слову постранично
- проверить введенную информацию о студенте на правильность, если есть ошибки, то получить список этих ошибок (например: "не заполнено имя", "имя не должно содержать цифры" и т.д.)
- добавить нового студента в базу
- обновить информацию о каком-то студенте в базе

Далее, у нас есть такое понятие, как "авторизация", пусть и без паролей. После регистрации пользователь оказывается "залогиненным" под определенным "аккаунтом" и может редактировать инфорамцию о себе. Раз так, где-то должен быть код, отвечающий за функционал авторизации:

- узнать, залогинен ли текущий пользователь?
- если да, идентифицировать пользователя (кто он?)
- залогиниться под аккаунтом студента X
- разлогиниться

Далее, в задаче написано, что таблицу можно сортировать по разным колонкам, делать в ней поиск по слову и переходить по страницам. Значит, нам нужна функция, которая получает на вход параметры вывода таблицы (сортировка, слово для поиска, страница) и формирует URL. Мы можем этот URL вывести на странице как ссылку, например "отсортировать по имени" или "перейти на 2-ю страницу с сортировкой по имени".

У каждой из этих функций должен быть определенный набор аргументов, которые она получает, и тип результата, который она возвращает. Ну, проще всего с функцией подсчета числа студентов - ей передавать ничего не надо, а вернуть она должна целое число (я использую тайп хинты из PHP7):

function getStudentCount():int { ... }

Такую функцию я у тебя вижу (TableModel::numOfRows), но она названа неправильно. Имена функций и методв начинаются с глагола, сделатьЧтоТо. В данном случае надо назвать её получитьКоличествоСтудентов, getStudentCount.

Но если мы посмотрим на твою функцию добавления студента (TableModel::saveTable), мы видим, что она спроектирована неправильно. В нее ничего не передается. А откуда тогда она знает, какую информацию надо вставить в базу? Она откуда-то ее берет сама. Это неудобно и неправильно. Ну например, если ты захочешь сгенерировать 100 случайных студентов скриптом - ты не сможешь использовать эту функцию. А надо, чтобы мог. Суть MVC как раз в том, что функция модели не пытается сама откуда-то брать данные, а получает их явно.

Далее, у тебя там в одной функции объединена проверка данных и вставка студента. Но это неудачное решение. Ведь проверку данных нам надо делать и при обновлении профиля. Придется копировать код проверки. Это неправильно. Надо сделать проверку данных отдельной функцией и вызывать ее там, где надо.

Функция добавления студента (без проверки данных) должна выглядеть так:

Входные данные: информация о студенте
Выходные данные: ничего

Разумеется, в коде, тем более в модели, не должны использоваться глобальные переменные (global), не должно быть обращений к $_GET, $_SERVER, $_POST, не должно ничего выводиться с помощью echo. Все, что нужно функции, ты передаешь ей явно, все, что она хочет вернуть, она возвращает через return. Я ведь по моему писал это в уроке про MVC.

Чтобы привести твой код к MVC, надо для начала сделать модель, то есть взять функции модели, которые я перечислил выше, определить их входные параметры и результаты, сделать их в коде. А потом уже писать View и Controller. Чтобы убедиться, что ты все понял, давай ты напишешь заголовки функций, которые я перечислил выше (без тела, только название, аргументы и тип результата). А я проверю и скажу, что не так. Список функций вверху примерный, можно его изменить при желании.

Жду от тебя список функций.

Что касается классов - перед каждым классом надо написать комментарий, для чего он нужен, что представляет объект этого класса, как его использовать. Если ты не можешь сформулировать это, то, наверно, этот класс не нужен.

Если что-то из написанного непонятно, или есть какие-то вопросы, задавай.
Ответы: >>1093364 >>1093391
Аноним 2017/09/26 14:33:44  №1066706
>>1066703
>Нужно обязательно проверять значения от пользователя
Я не совсем это имею ввиду. Про проверку это понятно.
>>1066704
Допустим, я хочу позволить сортировать по name, secondName и summary(это у меня так общий бал называется). А пользователь хуяк и в ссылке введет не students.loc/name/1 (поле сортировки и страница), а /email/1 - и у меня будет сортировка по имейлу, хули, есть же такая колонка. В принципе ничего страшного в любом случае, но меня скорее интересует вопрос хранения каких-либо параметров, которые можно было бы отдать пользователю для свободного изменения, но при этом слишком мелочных что бы для них делать таблицы в БД, как-то так.
Ответы: >>1066720
Аноним 2017/09/26 14:09:48  №1066698
Такая короче у меня дилема. Пилю я сортировку в запросе через order by, для задачки про студентов. В запрос колонка для сотрировки попадает от пользователя, и все бы хорошо, но что если пользователеь в ссылке введет хуиту? Т.е. колонку, которой нет. Думается мне, надо как-то отдельно хранить коллекцию колонок, по которым можно делать упорядочивание, но вот как это правильннй реализовать? Впилить где-то массив в контроллере и перед передачей параметра в запрос проверять на наличие такого значения в массиве? Но тогда, по идее, пользователь-админ не сможет самостоятельно добавлять новые колонки для возможной сортировки. Делать для этого специальную таблицу в БД? Но для такого мелкого чиха, и целая таблица, а потом еще обращаться к базе - слишком жирно мне кажется. Для задачки про студентов это наверное не критично, но вот как такие проблемы по идее надо решать?
Ответы: >>1066703 >>1066704
Аноним 2017/07/28 10:13:07  №1033108
>>1030198

В такой ситуации проще всего с помощью strace (под линукс) или procmon (винда) посмотреть, к каким файлам идет обращение при вызове gettext().

На винде в PHP5.4 у меня твой скрипт выдает:

> setlocale failed: locale function is not available on this platform....

Это и понятно, так как локали это линуксовая вещь и, хотя в винде есть их аналог, и PHP пытается его использовать, но называются сами локали там по-другому.

Например, у меня в винде var_dump(setlocale(LC_ALL, null)); выдает:

> string(26) "English_United States.1252"

В MSDN наверно можно найти, какие конкретно локали есть в винде.

Также, PHP5.3 не выдает ошибку, но сообщение не переводит. procmon не запускается и падает с ошибкой, потому посмотреть, куда он лезет, не могу.

На линуксе выдается ошибка в setlocale, из-за того, что у меня нет русской локали. Исправил локаль на "C", и увидел через strace, что код пытается лезть в /locale за сообщениями.

Просмотреть список доступных локалей в линуксе можно через locale -a, а текущую - набрав просто команду locale. В винде - в MSDN наверно.

Я вижу такие ссылки на MSDN:

- https://msdn.microsoft.com/en-us/library/x99tb11d.aspx
- https://msdn.microsoft.com/en-us/library/hzz3tw78.aspx

И вот тут интересная инфорамция на русском: https://anton-pribora.ru/articles/php/locales/

Видно, что в любом случае имена локалей не совпадают с линуксовыми.

В линуксе локали действительно называются вроде en_GB или en_GB.utf-8, но там есть подвох - гарантий, что та или иная локаль установлена в системе, нет, соответственно твое приложение рискует тоже не заработать (setlocale выдаст ошибку, если локаль не установлена в системе).

"локаль установлена в системе" - это значит, что где-то есть файлик(и), описывающие настройки для данной локали: формат чисел, дат, правила сортировки строк по алфавиту. В линуксе обычно локали устанавливаются системным менеджером пакетов.

В линуксе локаль задается через переменные окружения (обычно где-то в скрипте или конфиге, выполняемом при загрузке системы) и влияет на поведение программ и библиотек: на формат вывода чисел стандартными функциями (в PHP кстати тоже - при русской локали дробные числа выводятся с запятой вместо точки, что доставляет дополнительную боль), на сортировку строк стандартными функциями, а также на язык выводимых программами и библиотеками сообщений.

Переменные окружения хорошо подходят для задания локали по 2 причинам:

- они могут быть у каждой программы свои и можно одновременно работать с разными языками в нескольких консолях
- они наследуются от родителя при запуске дочернего процесса

Увы, тут есть и недостаток: переменные окружения общие для всех тредов многотредового процесса, то есть в случае с многопотоным PHP (например в mod_php) нельзя ставить независимо разные локали для разных потоков.

Локали влияют и на работу PHP, так как он использует библиотеки, которые смотрят на переменные окружения LC_.... и используют функции, зависящие от текущей локали. Локаль можно задавать через переменную LANG (общая настройка), LC_ALL или отдельно для каждой категории вроде LC_TIME.

То есть в Линуксе эти локали хорошо интегрированы, и разработчики программ ими пользуются. Что касается винды, надо читать доки по setlocale в MSDN.

Вернемся к gettext.

Если что, документация по сишной библиотеке gettext (а в PHP функции расширения gettext по сути просто вызывают эту библиотеку) есть тут: https://www.gnu.org/software/gettext/manual/gettext.html#gettext

Конкретно, вот документация по пути к файлам gettext: https://www.gnu.org/software/gettext/manual/gettext.html#Locating-Catalogs

> Because many different languages for many different packages have to be stored we need some way to add these information to file message catalog files. The way usually used in Unix environments is have this encoding in the file name. This is also done here. The directory name given in bindtextdomains second argument (or the default directory), followed by the name of the locale, the locale category, and the domain name are concatenated:

> dir_name/locale/LC_category/domain_name.mo

> The default value for dir_name is system specific. For the GNU library, and for packages adhering to its conventions, it’s:

> /usr/local/share/locale

> locale is the name of the locale category which is designated by LC_category. For gettext and dgettext this LC_category is always LC_MESSAGES. The name of the locale category is determined through setlocale (LC_category, NULL). When using the function dcgettext, you can specify the locale category through the third argument.

То есть самый надежный способ, получается такой:

- ставить дефолтную локаль (чтобы все встроенные функции работали одинаково независимо от внешних настроек)
- задать язык с помощью выбора домена (например: myapp-ru)
- задать базовый путь для сообщений в bindtextdomain (например /x/y/z/messages/)

То есть переключать языки не с помощью локали, а выбором домена. Вообще, домен наверно был придуман для того, чтобы у приложения или группы приложений были свои независимые языковые файлы, но можно его использовать и для выбора языка.

Я проверил, локаль "C" работает и в линуксах, и в винде. Однако, при ее использовании gettext даже не пытается искать файл с сообщениям.

При выборе локали en_US.utf8 (только линукс, в винде не поддерживается), домена mydomain, папки /tmp, strace показывает, что gettext ищет файл переводов по следующим путям:

- /tmp/en_US/LC_MESSAGES/mydomain.mo
- /tmp/en/LC_MESSAGES/mydomain.mo

(так как в линуксе есть еще синонимы (алиасы) локалей и он их тоже перебирает).

То, что gettext не выводит никаких ошибок, это конечно, неудачное решение.

Также можно попробовать не использовать gettext, а например, стороннюю библиотеку Symfony Translator: https://symfony.com/doc/current/components/translation.html

Это примерно то же, но написано на PHP. Надо понимать, что в отличие от сишного расширения, которое умеет загружать данные один раз в память процесса при первом запуске скрипта, тут загрузка сообщений может работать медленнее (например чтение данных из mo-файла сделано на PHP и будет работать небыстро) и будет происходить при каждом выполнении скрипта. Однако, если использовать загрузку из PHP файлов, и opcache, то может быть, эти данные закешируются в shared memory и не будут грузиться при каждом запуске скрипта.

Стоит глянуть исходники этой библиотеки, чтобы лучше понять, как она работает. Она вроде умеет конвертировать сообщения в формат php-файлов.

Если ты встроил веб-сервер в процесс PHP и не умираешь после каждого запроса, то медленная загрузка не так критична.

Для форматирования строк (вставки чисел, подстрок, изменния формы слов в зависимости от числа или пола) стоит использовать возможности MessageFormatter в расширении Intl. То, что есть в gettext (ngettext) и в Symfony (transChoice) - это пародия на нормальный форматтер сообщений.

Я, признаюсь, сколько-то лет назад не знал про Intl и изобретал свои форматтеры со своим синтаксисом.

Также, я не уверен, что putenv на что-то влияет, так как она может быть только переопределяет переменные окружения внутри PHP для процессов-потомков и для getenv(), но не влияет на то, что прочитает библиотека gettext. Но я не уверен.
someApprentice 2017/07/21 15:43:30  №1029290
>>1028550
>В общем, есть 2 варианта, предлагаю сравнить их преимущества и недостатки.
Остановимся на первом варианте, т.к. во втором переусложнена генерация ключей. Может быть и нет, сначала мне нужно разобраться с шифрованием

>> Но я не понимаю как хранить ID сообщений - получается что для каждого пользователя оно теперь будет своё. Из этого вытекает, что нельзя будет как-то преобразовывать эти сообщения т.е. редактировать или удалять.
>Можно сделать 2 таблицы, одна для хранения id сообщений, другая для хранения зашифрованных копий сообщений для каждого получателя.
>При этом, чтобы связать сообщения, можно сделать какой-нибудь добавочный id. Можно например сделать таблицу сообщений (где и будет этот id), и отдельно таблицу, которая хранит копию сообщения для конкретного получателя.
Действительно, можно создать таблицу специально чтобы только хранить id, а ящики Inbox и Outbox будут хранить ещё и всё остальное (автора, контент, дату и т.д.).

>Тут правда есть проблема, что на сервере хранится полный список участников чата, который может заинтересовать злоумышленников и агентов правителтьства. Если ее не хранить на сервере в открытую (например, как-то хранить в зашифрованном виде в аккаунте пользователя), может быть было бы лучше.
Да, я тоже размышлял об этом. Мне нужно ещё подумать как тут лучше поступить.


Шифрование

Никак не могу понять как это сделать. Я уже немного знаком с ним, но почему-то даже не могу придумать функцию которая будет шифровать сообщения.

Знаю как сгенерировать ключ с помощь протокола Диффи - Хеллмана, имею небольшое представление о открытых и закрытых ключах, но как с помощью них (ключей) зашифровать сообщение - не представляю.

>Также, вопрос еще, где хранить ключи. Можно иметь единый ключ пользователя, хранящийся на сервере и зашифрованный паролем (устройства скачивает шифрованный ключ и расшифровывает локально).
>устройства скачивает шифрованный ключ и расшифровывает локально
Меня сначала смутило что без js тогда не будет работать приложение, но потом я подумал что можно будет при залогинивании выдавать кукис с расшифрованным ключем.

>Мне конечно схема с локалшьным хранением ключа кажется более надежной, но более сложной в реализации.
Значит нужно оставить это на дальнейшую реализацию.


>> Когда при выводе нового элемента, мы устанавливаем на него обработчик, то мы его не переназначаем, а добавляем новый, и каждый раз когда происходит это событие, обработчик выполняется столько раз, сколько мы выводили элемент
>Это проблема в твоем коде, что ты так делаешь. Не надо назначать обработик несколько раз.
>Надо либо ставить обработчик только один раз, при создании или обнаружении элемента
У меня элемент постоянно обновляется и постоянно нужно ставить новый обработчик.

>либо же (если есть много однотипных элементов) выгоднее ставить не много обработчиков, а один обработчик на родительский элемент и ловить всплывающие снизу дерева DOM события.
Вот так https://jsfiddle.net/jsqt57hz/2/ ?

>В твоем примере кода, когда ты вставляешь HTML код из template в DOM, тогда же надо и ставить обработчики, и делать это только один раз.
А я приводил в пример такой вариант, вы сказали что у виджета может быть свой обработчик событий:

https://arhivach.org/thread/266631/#1018809
>> А что если элемент появляется не сразу? Логично будет задать тогда при его выводе элемента (то есть обработчик будет частью отображения) или в контроллере?
>Во-первых, "установка обработчика" не обязательно значит "установка обработчика на DOM элемент". В виджете может быть своя система событий, и ставится обработчик на внутреннее событие виджета, а не на DOM элемент.


> У меня есть идея как при отключенном js опускать окно диалога до последнего сообщения - использовать якори.
> Это должно быть сделано с помощью функций JOIN и GROUP:
Я не очень понял, как выравнивание сообщений связано с SQL-запросами?
Не выравнивание, а опускание скролла сообщений до последнего сообщения, если я правильно понял терминологию.

Мы с помощью SQL-запросов получаем контакты и последние сообщения, и в ссылку контакта вставляем якорь на id последнего сообщения:

><a class="contact" ... href="conversation.php? ... #message-ID">someUser</a>
><div id="message-ID ... class="message">

Заодно можно сделать и сортировку контактов по последним полученным сообщениям.
Ответы: >>1030003
Аноним 2017/07/18 04:48:24  №1027651
https://github.com/someApprentice/chat/blob/master/public/js/conversation.js#L82
Что можно сделать после успешной отправки сообщения?

Можно обновить все сообщения, но сообщения и так обновляются каждые пол секунды. Как-то не хочется оставлять пустую функцию. Или её можно не писать?


https://arhivach.org/thread/261841/#1000396
>Ты не обрабатываешь ошибки. Видимо, уроки, которые ты читал, упускают такие важные вещи. Вот тебе тогда мои маленькие советы про использование аякса: https://github.com/codedokode/pasta/blob/master/js/ajax.md
https://github.com/someApprentice/chat/blob/master/public/js/chat.js#L59-L69
https://github.com/someApprentice/chat/blob/master/public/js/conversation.js#L85-L92

Стоит ли так выборочно обрабатывать ошибки? Для пользователя достаточно вывести ошибку соединения, а для разработчиков выбросить в консоль статус и текст ошибки.

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

https://arhivach.org/thread/266631/#1010744
>> Кстати, это нормально делать перевод на новую строку в цикле https://github.com/someApprentice/chat/blob/master/public/js/conversation.js#L280-L284 ?
>Это какие-то кривые костыли. Получается, логика отображения разбросана между шаблоном и JS кодом. Мы ведь в PHP не пытаемся так делать после генерации HTML из шаблона? И тут не надо. Наверняка там есть какие-нибудь хелперы для таких преобразований. В чем проблема сделать какой-нибудь хелпер для преобразования текстов сообщений?
К сожалению, у меня не получилось найти в ejs аналог функции nl2br и при этом чтобы экранировались тэги. В таком случае, будет приемлемым оставить этот костыль?
Или лучше, можете посоветовать какой-нибудь шаблонизатор который справиться с этой задачей?

И что если я захочу не экранировать определённые тэги? Должено быть какое-то решение чтобы справиться с этими задачами.

https://github.com/someApprentice/chat/blob/master/src/Controller/ConversationController.php#L78-L115
Это нормально, что у меня такая простыня с обработкой ошибок?


https://arhivach.org/thread/261841/#1000396
>Также, некоторые разработчики активно используют возможности HTTP (дополнительные заголовки, методы вроде PUT/DELETE, коды ответов), а некоторые не используют.
>Также, перед проектированием АПИ, советую почитать про REST и HATEOS (и далее, стандарт про шаблоны URL https://tools.ietf.org/html/rfc6570 ), но тут конечно есть разные мнения, ну к примеру в иделогии REST одним запросом нельзя получить несколько несвязанных массивов данных, а HATEOS раздувает размер ответа.
Я плохо понял что такое REST. Это просто архитектура которая позволяет обрабатывать запросы с методами вроде PUT\DELETE?

Я пока не могу придумать для чего использовать такие методы.


https://arhivach.org/thread/266631/#1010325
>Пересоздавать полный список сообщений неэффективно.
Я никак не могу придумать как выводить только новые сообщения. Определенно, нужен setTimeout\setInterval чтобы постоянно обновлять данные с сервера, но как при этом выводить только те сообщения которые ещё не выведены в шаблон?


https://github.com/someApprentice/chat/blob/master/src/Model/Database/MessageGateway.php#L25
Поскольку, я теперь вывожу сообщения за определённый период времени, то если за этот промежуток не было сообщений, при открытии диалога выведиться пустое окно сообщений из-за чего вытекает баг с автоматической подрузкой более старых сообщений, из-за того что отсутвует скролл блока сообщений. Это можно исправить увеличивая отрезок времени и повторном обращении к БД, до тех пор пока не получим нужное количество сообщений:

$count = $db->getMessagesCount();

$offset = 1;

if ($count > 10) {
while(count($messages) < 10) {
$offset++;
$messages = $db->getMessages(..., $offset);
}
}

Не будет ли это лишней нагрузкой на БД?


У меня есть идея как при отключенном js опускать окно диалога до последнего сообщения - использовать якори. Я уже делаю что-то подобное при получении более старых сообщениях (при выключенном js):

https://github.com/someApprentice/chat/blob/master/templates/chat.phtml#L24
><a ... href="conversation.php? ... #message-ID">Получить больше сообщений</a>

https://github.com/someApprentice/chat/blob/master/templates/chat.phtml#L32
><div id="message-ID ... class="message">

Точно так же можно сделать в ссылке контакта.

Но для этого нужно сделать несколько лишних запросов к БД:

$contacts = $db->getContacts();

$c = array();

foreach ($contacts as $contact) {
$lastMessage = $db->getLastMessage(...);

$c[] = array(
'contact' => $contact,
'lastMessage' => $lastMessage
);
}

Я согласен, что на первый взгляд идея может показаться нелепой, но примерно такой же код придётся делать, если нужно будет отсортировать контакты по последним сообщениям.

Опять же, не создаст ли это большую нагрузку на БД? Учитывая, что с включенным js, при сортировке по последним сообщениям, обновление контактов будет происходить каждую секунду.


https://github.com/someApprentice/chat/blob/master/src/Controller/AuthController.php#L101-L103
https://github.com/someApprentice/chat/blob/master/public/js/conversation.js#L217
Не подвергаю ли я пользователей опасности, открывая кукисы для js?

Просто при открытии диалога нет возможности получить токен. Если только не спрятать его где-то на странице. Я сомневаюсь что это будет правильным решением, но с другой стороны не вижу никакой проблемы. Как лучше поступить?


У меня есть переменные, которые пишутся ни как camelCase или snake_case, например moremessages. Я знаю, что это плохой плохой вариант, но мне он кажется вполне читабельным. Лучше будет переимновать?
Ответы: >>1028199 >>1030006
https://github.com/someApprentice/chat/ Аноним 2017/06/03 13:32:03  №1000397
>>990621

https://github.com/someApprentice/chat/blob/master/public/api/getmessages.php
Тут слишком большая вложенность блоков.

Также, я вижу в АПИ немало копипасти вида

> 'id' => $message->getId(),
> 'author' => $message->getAuthor()->getName(),
Может, это стоит хотя бы в функцию вынести (экспортирование данных из объекта).

Также, если ты отдаешь JSON, нужно выставлять (и проверять на клиенте) правильный Content-Type. Ну это же основы HTTP.

https://github.com/someApprentice/chat/blob/master/src/Model/Database.php
Тут, возможно, лучше было сделать на каждую сущность свой класс. Также, как и с валидацией.

> $user->setId($result['id']);
> $user->setLogin($result['login']);
> $user->setName($result['name']);
Возможно, тут стоит сделать метод для создания объекта из массива.

Также, нет SQL-дампа базы в репозитории.

Насчет проектирования БД - там тоже много тонкостей есть, можно делать нормальзованную схему, но лучше денормализовывать ради повышения производительности. Ну например, возьмем то же сообщение. Можно хранить его в одном экземпляре, а можно в двух - в inbox получателя и в outbox отправителя. Это может облегчить выборку последних N сообщений, так как твой запрос с OR вряд ли хорошо ложится на индексы (вдобавок у тебя еще и сортировки там нет).

Вообще, SQL базы не очень хорошо подходят для таких задач, насколько я знаю, все нагруженные чаты в итоге пишут какие-то свои хранилища, заточенные именно под хранение сообщений. Но это конечно долго и сложно.

Также, у тебя система никак не оптимизирована для работы с большими объемами данных. Что, если у пользователя тысячи сообщений - ты все их разом загружаешь? Что, если у него тысяча контактов и чатов? Тут надо предусматривать частичную выборку данных.

https://github.com/someApprentice/chat/blob/master/src/Model/Validator.php#L6
> const LOGIN_ERROR = "Логин должен быть короче 20 английских символов";
Константы обычно так не используют, константа сама по себе обозначает какой-то вариант, а что в ней хранится, не важно.

https://github.com/someApprentice/chat/blob/master/src/View/View.php
Тут не уверен, что нужно на каждый шаблон писать по функции. Обычно просто делают функцию вида render($template, array $variables), а подготовку переменных делают в контроллере.

> name="password" pattern="(.){6,20}"
Почему пароль ограничен 20 символами? Чтобы подбирать было проще?

> https://github.com/someApprentice/chat/blob/master/templates/chat.phtml#L24
> <div class="content"><?= $message->getContent() ?></div>
Тут не XSS?

В форме отправки нет защиты от XSRF.

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

https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
https://getcomposer.org/doc/02-libraries.md#lock-file
https://blog.engineyard.com/2014/composer-its-all-about-the-lock-file

Насчет MVC, там у тебя все запутано. Ну вот например тут модель лезет в DOM:

https://github.com/someApprentice/chat/blob/master/public/js/chat.js#L51

Модель ведь по задумке должна хранить данные и реализовывать логику работы приложения, а не считать величину отступа где-то на странице. У тебя тут не MVC, а просто часть кода случайно помещена в классы с названиями Controller и Model.

> $('.contacts-not-found').remove();
Вот вместо постоянного копирования этих кусков кода, обычно удобнее найти элемент и сохранить ссылку на него в переменную и далее работать с этой ссылкой. Это сделает и код аккуратнее, и заодно и ускорит работу, так как $(..) имеет сложность O(N) от числа элементов DOM на странице, там оптимизирован только поиск по id.

Вот еще пример плохого использования jQuery: $('textarea[name="message"]').val($('textarea[name="message"]').val() + "\n");

Вообще, по поводу JS, я конечно могу дать такой урок про MVC, но учти, что он довольно сложный https://github.com/codedokode/pasta/blob/master/js/minesweeper-mvc.md Может быть ты что-то из него захочешь взять. Только помни, что не стоит переусложнять код без необходимости.

Если есть время, глянь Knockout и React, Vue, Angular, Angular-light. Также, сразу добавлю, когда авторы Реакта пишут что-то вроде "реакт быстрый потому что использует Virtual DOM, а не медленный DOM", это надо понимать как "реакт быстрее аналогичного решения, которое работало по тем же принципам (перегенерация вида при изменениях), но постоянно бы обращалось к DOM", а не как "реакт быстрее остальных подходов". Ну например для обновления диалога, я не уверен что подход реакта будет быстро работать, это надо мерять.

Также, у тебя по моему не очень удачно реализован переход по истории:

> Contreller.prototype.getConversation = function(withUser) {
> $.getJSON('api/getmessages.php?with=' + withUser, function(results) {
> window.history.pushState({}, '', '/conversation.php?with=' + withUser);
Во-первых, с историей лучше работать через JS-библиотеку, это позволит поддерживать браузеры без pushState(). Во-вторых, если со страницы диалогов вызвать эту функцию, в историю не запишется переход на ту же самую страницу? В-третьих, ты делаешь переход только после получения ответа, это плохо, так как при проблемах со связью и обновлении страницы переход не произойдет. Ну и нет обработки ошибок при выполнении запроса.

Вообще, для работы с историей, есть такая штука, как клиентский роутер: https://www.google.ru/search?q=js+router&newwindow=1&gbv=1&sei=TWEyWc6YBYLVsAHegKrACg

Их много, есть встроенные в фреймворки вроде Angular/React, есть отдельные.

Также, работу с АПИ наверно было бы лучше вынести в отдельный класс. Чтобы иметь возможность писать что-нибудь вроде backend.getMessages(...). Это позволит, может быть, позже туда вкрутить поддержку кеширования данных и оффлайн-режима.

В document.ready у тебя код идет стеной. Но лучше было бы, возможно, разбить экран на компоненты (список контактов, диалог и тд), и каждый реализовать в своем классе.
Ответы: >>1004895 >>1005629
Аноним 2017/05/25 03:29:18  №996038
>>994740

> можно обойтись и одним, и просто переименовывать php\html файлы. Значит, нужно будет иметь в бд как и оригинальное имя, так и то которое храниться на сервере?
Переименовывать надо в любом случае, так как могут быть файлы с одинаковыми именами, с нечитаемыми символами, файлы с названием .htaccess которые переопределяют настройки веб-сервера, .gitignore которые влияют на git и тд.

> Чтобы получить оригинальное имя при скачивании можно добавлять в ссылку атрибут download="...", но это, наверно, не очень надёжно. Лучше при скачивании самому выставлять заголовки с помощью php. Это хорошо тем, что можно выставить в заголовке filename оригинальное имя файла, я прав?
Нельзя, так как в HTTP заголовках можно использовать только символы ASCII (не так давно добавили возможность указывать кодировку и вставлять символы не из ASCII). атрибут download работает только в относительно новых браузерах. По моему в комментариях к задаче написано, что самый надежный способ, работающий везде - это поместить имя файла в конец URL.

Отдавать файл в идеале лучше всего веб-сервером, а не PHP, опять же это по моему описано в комментариях к задаче.

> Почему нельзя содержать такой тип? Страница будет открываться в любом случае?
Не помню, почему. Может стоит проверить экспериментально? Если attachment не указан, то браузер смотрит на Content-Type и если тип ему знаком, пытается отобразить содержимое, иначе предложит сохранить. А если attachment указан, то по идее должен сразу предложить сохранить.

Может это защита от какой-нибудь уязвимости, когда злоумышленник загрузит HTML-файл и попытается как-то с его помощью украсть например куки пользователя пользуюсь тем, что файл находится на том же домене что и сайт. Не помню.

> Здесь нету, какой-то серьёзной проблемы, просто я не хочу делать отдельный контроллер. Может здесь можно что-нибудь посоветовать?
Есть такие варианты:

- отдавать средствами PHP
- ставить заголовки и передавать отдачу серверу через X-Sendfile (Апач), X-Accel-redirect (nginx). Это обычно используется для учета и ограничения скачиваний, когда перед скачиванием запускается php скрипт.
- написать правила для веб-сервера так, чтобы по определенной ссылке он отдавал файл с указанным в ней именем. Вроде такого: ссылка имеет вид /download/1.txt/название.txt и по ней отдается файл, хранящийся под именем 1.txt.

> я не хочу делать отдельный контроллер.
Ну это слабый аругмент. Есть возможность скачивания файла, логично для нее сделать контроллер.

> Генерацию директорий к файлу можно сделать проще - независимо от всего генерировать новую директорию для файла
На каждый файл свою папку генерировать - не очень эффективно, рекурсивный обход всех файлов будет медленнее из-за этих папок (хотя я не тестировал, у меня есть такое ощущение). Лучше всего по 100-1000 файлов в папку класть.

> Я правильно делаю, что передаю роутер в шаблон, чтобы сгенерировать ссылку?
Так можно делать, но по моему опыту лучше сделать свой класс-генератор ссылок, это лучше по таким причинам:

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

Ну например у тебя код имеет вид

$router->pathFor('download', ['id' => $file->getId()])?>

А могло бы быть

$urlGenerator->getDownloadUrl($file)

Хотя может для маленького проекта это лишнее усложнение. Но мне все же кажется что полезно так сделать.

Вместо переменных для защиты от CSRF удобнее передавать сам объект в шаблон.

> А как делается мультиязычность? Это, наверно, делается с помощью middleware - сначала определяется какой язык должен быть, а потом передаются данные на нужном языке. Где эти данные лучше хранить? В бд или константах прямо в коде (например EN_TITLE, RU_TITLE)?
Не, middleware тут не поможет, надо закладывать в архитектуру.

Стоит наверно сделать разные URL для разных версий страниц, иначе нельзя дать ссылку на страницу на определенном языке и они не будут нормально индексироваться. Википедия напримр использует на каждый язык свой поддомен. Можно использовать и папку в URL.

Насчет БД - есть 2 варианта: либо делать несколько колонок, либо выносить переводимые части в отдельные таблицы (например вида (fileId, language, description)). То есть либо располагать переводы "горизонтально" (в одной строке в разных колонках) либо "вертикально" (в разных строках).

Более того, бывает, что перевода нет. То есть есть статья на одном языке, а на другом языке перевод пока не готов. Или решено его не делать. Нужно решать, что делать в таком случае.

Далее, надо перевести интерфейс и сообщения в коде. Тут решение давно известно - gettext или аналогичный велосипед. Мы оборачиваем все сообщения в вызов функции, например:

echo message($language, 'File uploaded successfully'); // выведет фразу на текущем языке

Затем мы скриптом-сканером извлекаем список фраз и отдаем переводчикам. Они дают нам список переведенных фраз, мы его загружаем в программу и при выводе выбираем нужную фразу.

Как я написал выше, есть расширение gettext для этого. В нем есть функция перевода (с коротким именем _), сделаны форматы файлов для хранения переводов, и есть готовые программы для работы с переводами (например Poedit).

gettext используется не только в PHP, но и в других языках программирования. Советую его изучить.

Далее, в фразы иногда надо подставлять переменные. Например:

- Иван написал новый пост

Подход вроде echo $userName . message($l, 'написал новый пост'); не сработает, так как в некоторых языках имя "Иван" может оказаться в середине или конце фразы, и переводчикам неудобно работать с куском предложения.

Далее мы наткнемся на еще одну проблему. Фразы (внезапно) могут меняться в зависимости от пола, числа предметов:

- Иван загрузил 3 фотографии в альбом
- Маша загрузила 5 фотографий в альбом

И, что еще веселее, формы слов и правила их использования в разных языках разные. Решается это с помощью введения синтаксиса для подстановки переменных в фразы, для выбора варианта слова в зависимости от условий:

echo message($lang, 'Peter has {0, plural, =0{no cat} =1{a cat} other{# cats}}', ['cats' => $cats])

Тут можно изобретать свой синтаксис, а лучше взять готовый из ICU (поддерживается в расширении Intl):

- http://php.net/manual/ru/messageformatter.formatmessage.php
- http://userguide.icu-project.org/formatparse/messages (англ, подробно)

Стандартный формат хорош тем, что переводчики могут быть с ним знакомы, плюс могут быть инструменты для работы с ним. Не вижу особой выгоды изобретать тут велосипед.

Ну еще из мелочей - в разных странах разные форматы вывода дат, дробных чисел (точка или запятая), денежных сумм и валют. Это решается созданием соответствующих функций, или использованием классов из расширения Intl: http://php.net/manual/ru/book.intl.php

В разных языках разные правила сортировки строк. Это описано тут https://github.com/codedokode/pasta/blob/master/php/collation.md

Ну и стоит почитать про Юникод, может там еще какие-то подвохи по мелочи есть. ну например, если ты хочешь написать регулярку для проверки имени, надо учитывать, что имена в разных языках могут содержать символы разных алфавитов. Может тут стоит использовать символьные классы вроде \p{L}.

Еще есть языки, где текст пишется справа налево, а чтобы жизнь медом не казалась, иностранные слова там пишутся слева направо. Соответственно надо изучить возможности HTML/CSS (атрибут dir и соотв-е свойство), а также юникодные символы выбора направления вывода текста.

В общем, простор для изучения тут большой. Начни с gettext и Intl.
Ответы: >>996039 >>996055 >>998186
Аноним 2017/05/18 14:28:58  №992728
>>992103

> $totalNumberOfBills
лучше $billCount или просто $count

> if ($numberOfBills > $totalNumberOfBills) {
> $numberOfBills = $totalNumberOfBills;
Это лучше сделать через min/max вместо if

Решено верно.

>>991964

Да, php и sql. Посмотри ОП пост, там есть уроки и задания по теме.

>>991932

У тебя форматирование очень странное, что за лестница?

Также, проверка на дабл стоит в конце и до нее выполнение просто не дойдет - раньше сработает одно из 3 предыдущих условий. В группе if/elseif/else сработать может только один блок.

Попробуй вместо mt_rand поставить даблы и посмотреть, что выйдет.

>>991867

Ставь команды echo и выводи значение переменных, чтобы видеть как имено выполняется цикл. Может это поможет.

>>991859

Здесь нужно выполнить расчет кредита, но для 3 разных комбинаций начальных условий. Здесь нам помогут функции. Мы создаем свою функцию расчета кредита, которая на вход получает его условия, а на выходе например возвращает сумму выплат. И вызываем эту функцию 3 раза, получаем 3 суммы выплат, которые и выводим на экран.

>>991856

Урок в ОП посте читал про установку PHP? Ставишь PHP на компьютер, после чего ставишь IDE ("среду" как ты называешь) вроде Netbeans, Eclipse PDT (беспл), PhpStorm (платно) и в них настраиваешь расположение интерпретатора, чтобы они могли его запускать.

>>991822

Хороший совет. utf8mb4 это какая-то улучшенная версия utf8 в MySQL, как я помню.

>>991821

utf8mb4 наверно. utf8_unicode_ci это не кодировка, а collation (набор правил сравнения и сортировки строк): http://gahcep.github.io/blog/2013/01/05/mysql-utf8/

Лучше брать utf8, так как завтра у тебя встретится какой-нибудь хитрый символ в тексте, и не получится его сохранить. Также utf8 самая популярная кодировка и менее популярные кодировки могут какими-то функциями просто не поддерживаться. Хотя сейчас ситуация противоположная - регулярки (RLIKE) в MYSQL не поддерживают utf-8.

>>991789

Не надо так писать. Просто человек что-то не понял, а ты его сразу в необучаемые записал.

>>991702

Что-то по ссылке не виден код.

Ответы: >>992732
Аноним 2017/05/16 15:35:08  №991740
>>991691
>Нужно генерировать разные ссылки в зависимости от текущей сортировки.
Как запоминать текущую сортировку?
>То есть если у нас включена сортировка +name, то ссылка на колонке должна содержать -name.
Что? Нажимаю на name - контроллер получает параметры /name/asc
Нажимаю снова на name - контроллер получает все те же параметры /name/asc КАК запомнить и переключить ASC в DESC?
Ответы: >>991789 >>991808 >>991903