«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
Аноним 2019/05/03 21:19:15  №1393636 1
Как выполняется код в пыхе при одновременном запросе? Представим ситуацию, что два пользователя(или больше) нажали одновременно одну кнопку, которая общается с базой данных. В этом запросе берётся ид последней записи(count всех записей), создаётся новая запись и берётся её ид. Возможно ли, что возьмётся ид второй созданной записи(тип сначала от первого юзера, потом от второго)? Или лучше самостоятельно присвоить ид +1 от последнего?
Ответы: >>1393656 >>1394575
Аноним 2019/05/03 22:07:10  №1393656 2
>>1393636
Пых сам не обрабатывет http запросы. Запросы обрабатываются сервером (apache, nginx) затем на каждый запрос создается (гурбо говоря) отдельный процесс php, которому через стандартный ввод stdin и переменные окружения передаются параметры запроса. Данные попадают в массив $GLOBALS. Все запросы обрабатываются независимо.

Пых сам не обрабатывет SQL запросы. Он соеденяется с СУБД и передает через сокет запрос. СУБД выполняет запрос независимо от процесса PHP

id записи в базе данных обычно автоматически инкрементируется при создании новой записи. На момент выполнения запроса к СУБД таблица блокируется на запись для других запросов (грубо говоря). Все изменения с таблицей будут последовательны.
Ответы: >>1394166 >>1394573
Аноним 2019/05/04 20:15:55  №1394166 3
>>1393656
>Пых сам не обрабатывет http запросы. Запросы обрабатываются сервером (apache, nginx) затем на каждый запрос создается (гурбо говоря) отдельный процесс php, которому через стандартный ввод stdin и переменные окружения передаются параметры запроса.
Ваш пых совсем не умеет в демонизацию и асинхронность?
Ответы: >>1394178 >>1394181 >>1394569
Аноним 2019/05/04 20:25:23  №1394178 4
Аноним 2019/05/04 20:26:20  №1394181 5
>>1394166
Он же интерпретируемый.
Ответы: >>1394184
Аноним 2019/05/04 20:27:48  №1394184 6
Аноним 2019/05/05 15:26:47  №1394569 7
170-312

>>1394459

Твой код с try/catch написан неправильно. try/catch если и ставят, то только 1 раз, в самом начале программы, а не вокруг каждого вызова функции. Потому код там будет выглядеть так:

$dbh = new PDO('mysql:host=localhost;dbname=""', $user, $pass);

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

Глобальный обработчик исключений ставят либо с помощью set_exception_handler, либо с помощью try/catch в самом начале программы.

>>1394386

У тебя какая-то ошибка синтаксиса - какой-то лишний символ (например: неразрывный пробел), или какая-то слишком старая версия PHP, или какое-нибудь двоеточие вместо точки с запятой, или русская буква вместо латинской.

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

>>1394269

array_slice. Не ленись, открой раздел про функции работы с массивами и прочитай список с краткими описаниями. Это все равно на собеседовании могут спросить.

Вот, читай список тут: https://www.php.net/manual/ru/ref.array.php

>>1394228

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

>>1394166

Для демонизации там есть php-fpm. Зачем нужна "асинхронность", если синхронный код писать проще и он понятнее, я не понял. Но если тебе хочется мучаться с промисами и евент-лупами, то ReactPHP к твоим услугам.

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

Хотя есть ситуации, где нужен именно асинхронный код. Тут, конечно, все корявенько, но сама возможность есть.

И я не считаю, что нода это что-то плохое, но есть ситуации, где PHP просто удобнее использовать.

А еще, PHP хорошо оптимизирован, есть типизация и у нас готовят JIT.
Ответы: >>1394581 >>1394643
Аноним 2019/05/05 15:29:48  №1394573 8
>>1393712

По поводу импортов: я не очень понимаю, зачем ты в model/message.py импортируешь наследников. Обычно импортируют только то, что нужно в данном файле. И обычно наследник импортирует предка, а не наоборот. Потому я думаю, эти импорты надо просто убрать:

# models/message.py
import text_message
import voice_message

Зачем они добавлены? Странно, что у тебя предок зависит от своих наследников (что они ему нужны).

Также, ты похоже выбрал Concrete Table Inheritance, возможно, что запросы к ней потребуют лишних UNION, судя по мануалу: https://docs.sqlalchemy.org/en/13/orm/inheritance.html#concrete-table-inheritance

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

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

> Как обычно делаются такие файлы? Через Докер (не разу с ним не знакомился)?

В dev среде можно использовать docker-compose для оркестрации докеров с отдельными приложениями. Докер, как правило, используется чтобы упаковать в образ программу с нужными ей библиотеками, например, определенную версию Питона или Ноды, чтобы ее не надо было устанавливать в систему руками. Код твоего приложения в докер-образ не кладется, а подмонтируется в него как внешний раздел. docker-compose заниамется тем, что просто запускает несколько докеров (например: микросервис авторизации и основное приложение). На Винде и Маке Докер запускает код в виртуальной машине с линуксом, а файлы прокидываются через сетевую файловую систему со всеми вытекающими.

Ты можешь найти готовый пример приложения на PHP + nginx + mysql в докере и разберешься, я думаю.

> Лучше сделать это на системном языке и чтобы он был кроссплатформенный. Можете что-нибудь посоветовать пожалуйста?

Обычно используют Питон, bash для таких скриптов. Если у тебя код на Питоне, логично в нем сделать CLI скрипт для генерации токенов.

>>1393656

У тебя описан CGI, он не используется на практике, используется FastCGI, а там все посложнее, и один процесс php обрабатывает много запросов по очереди.

Ответы: >>1397648
Аноним 2019/05/05 15:37:44  №1394581 9
>>1394569
>Твой код с try/catch написан неправильно. try/catch если и ставят, то только 1 раз, в самом начале программы, а не вокруг каждого вызова функции. Потому код там будет выглядеть так:
ну, кстати, тут я бы поспорил. Разные мнения на этот счёт, моё таково, что в try catch надо оборачивать каждый (в разумных пределах) вызов функции, где возможен выброс исключения, и которое ты планируешь обработать.
Ответы: >>1394582 >>1394733
Аноним 2019/05/05 15:38:38  №1394582 10
>>1394581
то есть несколько try/catch в одном коде это ок, особенно если разные исключения ловятся и они в разных местах возникают
Ответы: >>1394733
Аноним 2019/05/05 18:32:37  №1394643 11
>>1394569
>Твой код с try/catch написан неправильно. try/catch если и ставят, то только 1 раз, в самом начале программы, а не вокруг каждого вызова функции. Потому код там будет выглядеть так:
Так стойте, что это значит? А что тогда должен ловить try/catch?

>>1394513
Вот посмотри мой код, пожалуйста. Где тут ошибки. Мне нужно оборачивать запрос $dbh->query($query); в try/catch? Если да, то какую ошибку функция query() может выбрасывать? Тоже PDOException?

<?php
require_once __DIR__ . '/login.php';
try {
$dbh = new PDO('mysql:host=localhost;dbname=', $un, $pw);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo 'Подключение не удалось: ' . $e->getMessage(); //$e->getMessage() не нужно писать, получается?
}

$query = "CREATE TABLE student (
id SMALLINT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
surname VARCHAR(50) NOT NULL,
gender ENUM('male', 'female') NOT NULL,
groupNumber VARCHAR(5) NOT NULL,
email VARCHAR(30) NOT NULL UNIQUE,
totalPoints SMALLINT UNSIGNED NOT NULL,
yearOfBirth YEAR NOT NULL,
localOrNonresident ENUM('local', 'nonresident') NOT NULL,
PRIMARY KEY (id)
)";

$dbh->query($query);
$dbh = null;
?>
Ответы: >>1394656 >>1394732
Аноним 2019/05/05 19:41:31  №1394656 12
>>1394643
Я не оп.
Популярное решение - это централизованный обработчик исключений в ядре твоего фреймворка, как можно ближе к точке входа. В зависимости от режима DEVELOPMENT/ PRODUCTION ты можешь выводить ошибки на экран или молча логировать в файл Собственно единая точка входа и для этого тоже нужна.
>>1394607
Я не оп.
Под хелперами обычно понимают методы/функции шаблонизатора, хотя зависит о терминологии конкретного фремворка.

Сервисы обычно имеют отношения к Dependency Inversion https://ru.wikipedia.org/wiki/Принцип_инверсии_зависимостей фреймворка. Предполанается что ты пишешь в ооп стиле. У тебя куча классов зависящих друг от друга наследованием и композицией (когда один инстанс класса содержит ссылку на инстанс другого класса). Чтобы инстанцировать класс тебе нужно, предварительно инстанцировать кучу других классов, от которых он зависит путем композиции. DI во фреймворке:
1 позволяет обобщеным образом описать зависимости между классами (иногда автоматически интроспекцией получается инстанцировать класс)
2 позволяет осуществлять замену классов зависящих от друг друга наследованием или наследующих один интерфейс

Ты дожен понять что DI это не дополнительный улучшизм, а основной метод содания объектов во фрейймворке. Сервисы это классы твоей бизнес модели как ты выразился. Если твой класс ниотчего не зависит то тебе ненужно вставлять его в DI Собственно ты приходишь к утилитам.
Аноним 2019/05/06 05:52:34  №1394732 13
>>1394643

> А что тогда должен ловить try/catch?

Ты урок про исключения прочел? Смысл исключений как раз в том, чтобы не писать кучу if или кучу try/catch.

> Вот посмотри мой код, пожалуйста. Где тут ошибки

- пользователю не показывается красивая страница ошибки
- информация об ошибке не попадает в лог ошибок
- для показа страницы ошибки try/catch пишут один раз в начале программы, а не вокруг каждого вызова функции

В твоем случае надо либо использовать set_exception_handler, либо оборачивать все в огромный try/catch.

> Если да, то какую ошибку функция query() может выбрасывать? Тоже PDOException?

А в мануале ответа нету? Тут например: https://www.php.net/manual/ru/pdo.error-handling.php

Ошибки естественно могут быть: ошибка в SQL-запросе, ошибка связи с БД.

> Мне нужно оборачивать запрос $dbh->query($query); в try/catch?

Не нужно.

Что использовать: PDO или mysqli - не принципиально, они оба поддерживают исключения.

>>1394604

Померяй или погугли тесты.

Ответы: >>1394743
Аноним 2019/05/06 05:54:58  №1394733 14
>>1394590

> Есть класс WebReader

Это называется обычно HttpClient. И есть уже готовые клиенты, например, Guzzle.

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

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

> Это будет нормально - передать ему исключение, на основе которого он либо изменит свойства класса (модифицирует запрос)

Непонятно, кстати, почему у тебя параметры запроса хранятся в свойствах класса. У тебя объект представляет один запрос и для нового запроса создается новый объект? Если нет, то это может быть ошибкой в архитектуре. Ну например: что, если ты в процессе обработки одного запроса попытаешься через этот класс сделать другой? Свойства второго запроса затрут первый?

>>1394581

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

>>1394582

Несколько try/catch пишется, если ты знаешь, как обработать конкретную ошибку. Иначе пишется один общий обработчик ошибок на все случаи жизни.

>>1388385

В нем ничего не заменено, просто QUERY_STRING - это то, что идет ПОСЛЕ знака вопроса в URL (потому знака вопроса в ней нету) и она вообще может отсутствовать. Открой мануал и проверь: https://www.php.net/manual/ru/reserved.variables.server.php

Также, можешь прочитать урок про структуру URL на всякий случай: https://github.com/codedokode/pasta/blob/master/network/urls.md - там написано, что такое query string

>>1387384

Урок по MVC https://github.com/codedokode/pasta/blob/master/arch/mvc.md
Ответы: >>1394909
Аноним 2019/05/06 06:46:42  №1394743 15
image.png (119, 1250x796)
796x1250
>>1394732
>чтобы не писать кучу if или кучу try/catch.
В смысле? На той же яве и в C++ try/catch нужно было на каждый чих ОТДЕЛЬНО устанавливать, а в PHP он как то по другому действует? Тип как try/catch может выглядеть ОДИН на всю программу?

>В твоем случае надо либо использовать set_exception_handler, либо оборачивать все в огромный try/catch
Я чет не понимаю. try/catch должен оборачивать что? ??

>А в мануале ответа нету? Тут например
Там написано, для того чтобы получить ошибку нужно вызвать какую то функцию. И я не увидел какую именно ошибку выбрасывает запрос. Мне же нужно знать, какую именно ошибку выбрасывает запрос, чтобы поймать ее.

Ответы: >>1394746 >>1394747
Аноним 2019/05/06 07:01:29  №1394746 16
>>1394743
Короче, вот если так сделать, то что будет. Это кстати разработка БД, мне не нужно выдавать ошибку


<?php
set_exception_handler(function (Throwable $exception) {
//Код из мануала ОПа
error_log($e->__toString());

header("HTTP/1.0 503 Temporary unavailable");
header("Content-type: text/plain; charset=utf-8");

});
require_once __DIR__ . '/login.php';

$dbh = new PDO('mysql:host=localhost;dbname=', $un, $pw);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //И куда теперь эту штуку девать?
$query = "CREATE TABLE student (
id SMALLINT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
surname VARCHAR(50) NOT NULL,
gender ENUM('male', 'female') NOT NULL,
groupNumber VARCHAR(5) NOT NULL,
email VARCHAR(30) NOT NULL UNIQUE,
totalPoints SMALLINT UNSIGNED NOT NULL,
yearOfBirth YEAR NOT NULL,
localOrNonresident ENUM('local', 'nonresident') NOT NULL,
PRIMARY KEY (id)
)";

$dbh->query($query);
$dbh
Ответы: >>1394749
Аноним 2019/05/06 07:01:39  №1394747 17
>>1394743

Пусть у тебя все запросы идут через index.php. Тогда можно написать в нем:

require_once '....error_handler.php';
initErrorHandler();

try {
require_once 'bootstrap.php';
$fc = new FrontContrller();
$fc->handleRequest();
} catch (Throwable $e) {
handleException($e);
}

Альтернатива:

require_once '....error_handler.php';
initErrorHandler();
set_exception_handler('handleException');
$fc = new FrontContrller();
$fc->handleRequest();

Соответственно, во всех остальных местах кода try/catch не нужен, так как исключение будет поймано в index.php. Объясни теперь, откуда у тебя идея, что его надо писать много раз?

В мануале специально дан такой пример, чтобы с помощью расположения try/catch показать, в каком месте выбрасывается исключение. Это не значит, что ты его должен копировать строка в строку. Более того, там объясняется, как отключить выброс исключений и использовать ERRMODE_WARNING, потому и try/catch так поставлен, чтобы исключение не вылетело наружу из кода. А у тебя цели "отключать" исключения нету. То есть ты нашел в мануале пример, который вообще отношения к твоей ситуации не имеет.

В яве и Си++ то же самое, не требуется везде ставить try/catch.

Видимо, я плохо в уроке про исключения это объясняю. Но тебе определенно надо изучить исключения получше.
Ответы: >>1394765
Аноним 2019/05/06 07:04:56  №1394749 18
>>1394746

Если это скрипт, который запускается вручную из консоли (командной строки), то вообще писать try/catch не нужно, так как PHP в этой ситуации при исключении выведет информацию о нем и завершит скрипт. И соответственно, header в консоли не имеет никакого смысла.

Если это не скрипт в консоли, а для веба, то надо делать, как я описал выше, либо set_exception_handler, либо все завернуть в единственный try/catch.
Ответы: >>1394772
Аноним 2019/05/06 08:09:22  №1394765 19
>>1394747
Еще больше запутался.
require_once '....error_handler.php';
Что это за многоточие и что это подключаем тут?

initErrorHandler();
И эта функция что делает?? Не гуглится

Скажи я правильно понял или нет, пожалуйста.
Берем главный файл index.php. В него входят две первые строчки, которые я не понял и дальше включаем try. В try идет подключение файла bootstrap. А bootstrap это толи модель, толи просто инициализатор в MVC. Там хранятся файлы:
1) Класс, который описывает сущность студент
2) Класс, который работает с базой данных(паттерн TableDataGateway)
3) Класс, который проверяет правильность ввода данных.
Так вот в тех классах исключения писать не надо? Мне не нужно в классе, который реализует паттерн TableDataGateway использовать отлов ошибок/исключение/try/catch?
Подключение к БД в каком месте идет? В TableDataGateway или в index?
Просто если в index, то что мне делать в конструкторе public function __construct(PDO $pdo) { ... } класса TableDataGateway? Взять данные из базы данных в массив?
И где мне тогда подключаться к базе данных? В try/catch до bootstrap?

$fc = new FrontContrller();
$fc->handleRequest();
Это я тоже не понял. Это шаблоны?

И почему в альтернативе не описана функция set_exception_handler('handleException')? Ты подразумевал, что она где то описана?
Ответы: >>1394771 >>1415600
Аноним 2019/05/06 08:26:49  №1394771 20
>>1394765
>А bootstrap это толи модель, толи просто инициализатор в MVC.
Это прелоадер называется. Там инициализируются зависимости, окружение и тд.
Аноним 2019/05/06 08:32:56  №1394772 21
>>1394749
Кстати я подразумевал, что этот скриgn будет использован один раз. То есть он нужен был для GITHub, чтобы можно было включить один раз и все. Он создал бы базу данных и больше не понадобился.
Ответы: >>1394785
Аноним 2019/05/06 09:05:23  №1394785 22
>>1394772

Лучше использовать для этого SQL-дамп. Просто создаем файл с расширением .sql и в него пишем нужные SQL-команды, с точкой с запятой в конце каждой. Дампы можно генерировать программами вроде mysqldump или phpmyadmin, но можно и писать руками (заодно поучишься SQL).

Загружается дамп через программу-клиент БД или через командную строку.

- http://www.mysql.ru/docs/man/mysqldump.html
- пример первого попавшегося, найденного в гугле, дампа: https://github.com/cymitty/Student-list/blob/master/studentlist.sql

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

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

Почитать про командную строку простыми словами: https://github.com/codedokode/pasta/blob/master/soft/cli.md
Аноним 2019/05/06 19:18:00  №1394909 23
>>1394733
> вынести решение вопроса "что делать при ошибке" в отдельный метод
Вот это мне нравится. Вытащу тогда содержимое catch во вспомогательный метод типа tryToRepairTheRequest().
В родителе будет
protected function tryToRepairTheRequest(\ErrorException $e) {
// можем что-то сделать? если нет, throw $e;
}
В наследниках буду делать
private function tryToRepairTheRequest(\ErrorException $e) {
try { parent::tryToRepairTheRequest($e); }
catch (\ErrorException $e) { // специальный код для наследника, а если не вышло то опять же throw $e; }
}

>У тебя объект представляет один запрос и для нового запроса создается новый объект?
Нет. Запросы могут идти через один объект, а вот ответы - уже новые объекты.
$reader = new WebReader($defaultStreamContext, $maxQtyAttempts); // контекст стрима, который будет использован, если в get() никакой не указан, и кол-во попыток перезапросить при сбоях
$response = $reader->get('https://google.com'[, $streamContext]); // $response - объект класса WebResponse, содержащий ответ
$response2 = $reader->get('https://yandex.ru');

> что, если ты в процессе обработки одного запроса попытаешься через этот класс сделать другой?
Не понял. У меня нет никакой асинхронности и тредов. Вызвал get() - получи WebResponse или эксепшен.
>Свойства второго запроса затрут первый?
Ну, каждый запрос плодит по совершенно независимому WebResponse. WebReader при этом может оставаться один. Все, что он знает - какие хттп-заголовки и параметры стрима выставлять. $url нигде не запоминается и живет только внутри get(), который под капотом вызывает return $this->doRequest($url, $streamContext, $this->qtyAttempts), если ему нравятся переданные параметры. А doRequest() возвращает new WebResponse($http_response_header, $content), куда кладет всё полученное, если запрос удался.
Ответы: >>1398391 >>1402423 >>1402834
someApprentice 2019/05/11 13:13:34  №1397648 24
image.png (249, 1920x1080)
1080x1920
image.png (276, 1920x1080)
1080x1920
image.png (356, 1920x1080)
1080x1920
image.png (290, 1920x1080)
1080x1920
>>1394573
>По поводу импортов: я не очень понимаю, зачем ты в model/message.py импортируешь наследников. Обычно импортируют только то, что нужно в данном файле. И обычно наследник импортирует предка, а не наоборот. Потому я думаю, эти импорты надо просто убрать:
>
># models/message.py
>import text_message
>import voice_message
>
>Зачем они добавлены? Странно, что у тебя предок зависит от своих наследников (что они ему нужны).
Я понял свою ошибку. Я когда пытался получить все сообщения я использовал команду вида session.query(Message).join(Message_Reference).filter(Message_Reference.user == self.user).all() и получал только сущности родительского класса Message, и я подумал что родителю необходимо знать о детях чтобы получить их всех.
Я теперь понял, что импорты должны выполнятся там где выполняется этот запрос - это работает.

>Также, ты похоже выбрал Concrete Table Inheritance, возможно, что запросы к ней потребуют лишних UNION, судя по мануалу: https://docs.sqlalchemy.org/en/13/orm/inheritance.html#concrete-table-inheritance
К сожалению, мне не удалось найти какой паттерн наследования использует psql. Я косвенно предположил, что это именно он.pic-1

Использование UNION приводит к нагрузке?

Вообще, ORM не очень дружат с наследованием, как я могу посудить. Отношения между сущностями тяжело совершить с помощью встроенных инструментов и приходится делать метод для совершения запроса в ручную.pic-2 При получении сущностей выдается массив из сначала сущностей родителя, затем сущности ребёнка.pic-3 Я не знаю должно ли быть такое поведение. Хочется самому отсортировать только сущности детей и возвращать этот массив.


>> Следует сделать скрипт, который будет принимать все credentials, выполнять всю установку, а на выход выдавать все хэши и токены. Возможно генерировать сразу .env файл.
>
>Я думаю, достаточно только сгенерировать токены. В .env может быть еще куча других параметров. Но можно, конечно, выдавать заготовку .env файла.
>
>> Как обычно делаются такие файлы? Через Докер (не разу с ним не знакомился)?
>
>В dev среде можно использовать docker-compose для оркестрации докеров с отдельными приложениями. Докер, как правило, используется чтобы упаковать в образ программу с нужными ей библиотеками, например, определенную версию Питона или Ноды, чтобы ее не надо было устанавливать в систему руками. Код твоего приложения в докер-образ не кладется, а подмонтируется в него как внешний раздел. docker-compose заниамется тем, что просто запускает несколько докеров (например: микросервис авторизации и основное приложение). На Винде и Маке Докер запускает код в виртуальной машине с линуксом, а файлы прокидываются через сетевую файловую систему со всеми вытекающими.
>
>Ты можешь найти готовый пример приложения на PHP + nginx + mysql в докере и разберешься, я думаю.
Я ошибся когда писал, что у разных ролей wamp'а разные uri. Сейчас я сделал авторизацию действий засчет ролей и даже для сервера не нужно применять динамическую авторизацию.pic-4

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


>> Лучше сделать это на системном языке и чтобы он был кроссплатформенный. Можете что-нибудь посоветовать пожалуйста?
>
>Обычно используют Питон, bash для таких скриптов. Если у тебя код на Питоне, логично в нем сделать CLI скрипт для генерации токенов.
>Если у тебя код на Питоне
Страница выдаётся из Ноды, API я собираюсь переписать на PHP, а код wamp'а на Питоне.


>>1394587
>Такие запросы с джойнами будут плохо работать на больших нагрузках. Они же почти не оптимизируются индексами никак и требуют перебор строк. Возможно, тут придется сделать денормализацию, например, добавить в Dialog либо в Participant дополнительные поля. Чтобы, например, запрос бы имел вид
>
>SELECT FROM Participant WHERE user = ? AND partner = ? AND private =1
>
>Это ложится на индексы. Но, конечно, денормализацию стоит делать во вторую очередь.
Я думаю, что партнер должен добавляться скорее в ссылку на конференцию, потому что получатель это отдельная сущность, которая не отвечает за то с кем она должна вести диалог. А если партнёров будет много (публичная конференция)? Конечно тут можно прийти к созданию отдельной таблицы Partners... не будет ли и здесь плохо работать запрос с джоинами на больших нагрузках?

Далее, даже учитывая что мы создадим поле partner в Conference_Reference, это создаст запрос для получения вида:

// неизвестно какой пользователь сначала "создал" конференцию,
// а какой оказался получателем (партнёром)
SELECT FROM Conference_Reference WHERE (user = sender.id OR user = receiver.id) AND (partner = receiver.id OR partner = sender.id)

Такой запрос может вернуть две записи и сама его форма не элегантна.

Я предлагаю вернуться к идеи выше с массивами >>1387125

SELECT FROM Conference WHERE private = true AND (sender.id = ANY(participants) AND receiver.id = ANY(participants));

Как писалось выше такой запрос использует индексы (https://stackoverflow.com/questions/4058731/can-postgresql-index-array-columns).

У вас есть опыт с работой с индексами. Как вам кажется, индексирование массивов приведёт к желаемому повышению производительности?



>> Можно я оптимизацию БД оставлю на последнее?
>
>Можно. Но я просто хотел подчеркнуть, что мессенджеры это высоконагруженные штуки и там приходится об этом думать. И о шардинге, если ты хочешь, чтобы проект расширялся. Потому мы сначала проектируем нормализованную схему, а потом смотрим на типичные запросы и оптимизируем схему для них.
>
>Более того, часто под мессенджеры даже пишутся специализированные хранилища.
Я это прекрасно понимаю..

>Насчет reply - а недостаточно тут просто сделать поле в сообщении "replyToUuid" со ссылкой на исходное сообщение? Без вложений.
Судя по API телеграма там используется как раз такой подход ( https://core.telegram.org/bots/api#message ), но мне никогда не было понятно почему только можно ответить на одно сообщение, мне иногда хочется ответить на несколько сразу, да и выглядит это как прикрепление к сообщению.

>Так, схема выглядит хорошо. Позже стоит продумать операции, которые будут выполняться (их явно будет больше, например, может понадобиться постраничное получение участников огромного чата). И как их оптимизировать.
Опять же с массивами это тоже делается очень просто

SELECT participants[offset:limit] FROM Conference WHERE id = conference.id;


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


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

https://protonmail.com/support/knowledge-base/how-is-the-private-key-stored/

Я собираюсь повторить эту практику.

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


>> Я думаю, что пока подходы к авторизации методов WAMP не изучены, следует писать сырой код на if'ах, а подход с роутингом взять на заметку и держать в уме. А как думаете вы?
>
>Я, увы, так хорошо авторизацию в WAMP не знаю и сейчас не могу подсказать.
Рано или поздно мы разберёмся какая архитектура будет лучше.

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



Ответы: >>1397655 >>1398326
Аноним 2019/05/11 13:16:23  №1397655 25
>>1397648
Илюша,ты когда трезвый,такой добрый! кек
Аноним 2019/05/12 11:52:50  №1398326 26
>>1397648

> Я теперь понял, что импорты должны выполнятся там где выполняется этот запрос - это работает.

Общий принцип такой, что ты импортируешь только то, что как-то используешь именно в этом файле (вызываешь, создаешь объекты этого класса итд). Если не используешь - то не импортируешь.

> К сожалению, мне не удалось найти какой паттерн наследования использует psql

Эти паттерны наследования реализуются не на уровне Postgres, а на уровне ORM. И там в документации к SQLAlchemy перечислено несколько вариантов на выбор.

> Я косвенно предположил, что это именно он

Это Concrete TI, так как при STI таблица одна, а при Class TI в text_message были бы только те поля, которые отсутствуют в message.

> Использование UNION приводит к нагрузке?

Это надо смотреть EXPLAIN-ом и делать тесты, но скорее всего, да. Представь, что ты хочешь получить последние 10 сообщений: самый быстрый способ сделать это - это пройтись по индексу (где сообщения отсортированы по времени) и взять первые 10 записей из единственной общей таблицы. В случае с UNION нам надо брать по несколько сообщений из каждой таблицы и как-то объединять списки. Это в случае, если СУБД будет работать максимально оптимально, а это не факт.

Но вообще, выбор схемы наследования зависит от того, какие мы запросы делаем и какие у нас данные.

> Отношения между сущностями тяжело совершить с помощью встроенных инструментов и приходится делать метод для совершения запроса в ручную

Вообще, в документации есть похожий пример и там используются дополнительные параметры (foreign_keys): https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#creating-custom-foreign-conditions

Может, в этом проблема?

Ведь ORM надо знать, как связывать объекты между собой. Допустим, у тебя есть объект класса A, и он связан отношением с объектом класса B. Если ты создаешь несколько объектов класса B, добавишь их в класс A, и попробуешь сбросить данные в БД, то ORM должен понять: как сохранить в БД связь между объектами?

Думаю, явное указание foreign_keys и remote_side как раз говорит ORM, что надо взять id из одного поля объекта и прописать его в поле другого объекта.

Но я могу ошибаться, так как лишь бегло пролистал документацию.

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

Если ты используешь наследование, то Text_Message - это наследник Message, в соответствие с принципом Лисков наследник можно использовать вместо предка, и когда ты запрашиваешь список Message, то в нем будут и объекты Text_Message. Если ты это не хочешь, то тебе надо указать дополнительное условие (например: искать только Text_Message).

> Сейчас я сделал авторизацию действий засчет ролей и даже для сервера не нужно применять динамическую авторизацию

Только не забудь, что надо использовать принцип "белого списка" и все, что не указано явно - запрещается.

> Лучше сделать это на системном языке и чтобы он был кроссплатформенный

Вообще, "системное программирование" - это написание всяких модулей ядра, драйверов на языках вроде Си. А так, пиши на чем удобнее - на Питоне, JS или bash.

> А если партнёров будет много (публичная конференция)?

Часто в больших публичных конференциях список участников не показывают, пока ты не нажмешь какую-то кнопку. Потому, тут стоит учесть только такие варианты:

- получить полный список для маленькой конференции
- получить (по запросу пользователя) полный список для средней конференции
- если у тебя есть большие конференции (> 1000 - 5000 - 10000 польз.), то там может потребоваться получать список постранично

> не будет ли и здесь плохо работать запрос с джоинами на больших нагрузках?

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

> // неизвестно какой пользователь сначала "создал" конференцию,

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

> Как вам кажется, индексирование массивов приведёт к желаемому повышению производительности?

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

SELECT WHERE a = 1 AND b = 2

Такой запрос при использовании UNIQUE INDEX (a, b) будет искать в индексе пару (1; 2) и будет оптимальным. Индекс выглядит так:

(a, b -> id)

Он отсортирован по возрастанию пар значений a; b.

А как выглядит индекс в твоем случае? Скорее всего, как отсортированный список (participant -> id). И как в нем найти соответствующие условию записи? Ищем по participant = sender, получаем большой список id, ищем по participant = receiver, получаем большой список id, ищем в 2 списках одинаковые id.

Твой запрос эквивалентен такому:

SELECT a.id FROM table AS a, table AS b WHERE a.participant = :sender AND b.participant = :receiver AND a.id = b.id

То есть, попробуй этот индекс нарисовать и придумать, как вообще в теории в нем найти нужную запись, и ты возможно увидишь, какие запросы будут работать быстро, а какие - в принципе не могут работать быстро.

Потому я и советовал поступать так:

- сделать нормализованную, не оптимальную схему
- сделать список запросов, которые будут выполняться
- оптимизировать схему под них

> но мне никогда не было понятно почему только можно ответить на одно сообщение,

Потому что это упрощает пользовательский интерфейс, наверно. Но твой подход тоже имеет право на жизнь. Кто знает, может это будет как раз преимуществом в сравнении с другими мессенджерами.

> Опять же с массивами это тоже делается очень просто

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

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

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

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

Это просто рассуждения на тему того, что тут можно сделать в теории.

Аноним 2019/05/12 13:29:03  №1398391 27
ОПчик, ответь, пожалуйста, нормальная ли архетиктура в >>1394909
Аноним 2019/05/18 10:41:34  №1402423 28
>>1402419
Хочу узнать, нормальное ли у меня ООП в >>1394909
Аноним 2019/05/19 03:51:23  №1402834 29
>>1402686

Вообще, проще оставить все как есть, так как установка делается не так часто. Но если тебе хочется to go deeper ....

В composer на события можно повесить вызов функции или статического метода: https://getcomposer.org/doc/articles/scripts.md#package-events

При этом он получит объект события. Из него как-то можно попробовать выковырять название пакета: https://getcomposer.org/doc/articles/scripts.md#event-classes

Подробности придется искать в коде композера или в Гугле.

Также, хочу заметить, что твои скрипт не кросс-платформенны и работают только под Linux/Mac. Правильнее было бы повесить кроссплатформенный PHP-скрипт.

>>1394909

Ты как-то что-то усложнил. Давай спроектируем все с нуля:

Задача: дать возможность при ошибке поменять реквест и отправить запрос заново. Значит, нужна функция. Она может получать объект ошибки (не ErrorException, который соответствует PHP-ошибке, а например, HttpException) и объект Request. Она может вернуть либо null - не повторять запрос, либо вернуть тот же или модифицированный Request для повтора запроса, либо выбросить какое-то новое исключение:

function handleError(HttpException $e, Request $r): ?Response { ... }

Реализация по умолчанию, естественно, будет возвращать null.

Но тут есть подвох. Что, если мы хотим, например, в ответ на ошибку 503 сделать паузу в 5 секунд и сделать до 3 повторов запроса? Как нам хранить счетчик запросов? В данном случае - хранить его негде (если мы только не предусмотрели это в базовом классе).

Потому можно попробовать другой подход. Делаем переопределяемым сам метод отправки запроса - sendRequest(Request $req): Response. В наследнике просто оборачиваем его:

function sendRequest(...): ...
{
// любой код
parent::sendRequest()
// любой код
}

Это получается более универсальным подходом, как мне кажется.

А еще более универсально будет обернуть выполнение запроса без наследования, обернув вызов метода get().

Также, можно посмотреть на архитектуру питоновской библиотеки urllib, где есть плагины (openers), но она наверно сложновата для твоего случая: https://docs.python.org/3/library/urllib.request.html#module-urllib.request

У тебя в коде ошибки:

- указан слишком общий класс для отлова исключений
- нет реализации по умолчанию

Вообще, подход с оборачиванием проще, чем с обработчиком ошибок, как мне кажется.
Ответы: >>1402868
Аноним 2019/05/19 07:21:38  №1402868 30
>>1402834
> скрипт не кросс-платформенны и работают только под Linux/Mac.
Я не автор того скрипта, но не понял зачем давать такой совет. Под виндой бекенд обычно не разрабатывают, во многих компаниях наоборот распространена практика пересаживания всех на мак/убунту, чтобы проекты можно было быстрее поднимать по инструкции. Никогда не видел, чтобы в проекте было несколько параллельных инструкций для разных ОС. Зато часто видел древние проекты, которые не то что на винде запустить нельзя, их нужно запускать только под определённой версией PHP и только под апачем, так как на его rewrite правилах и includ'ах завязан весь код.
Ответы: >>1402920 >>1405044
Аноним 2019/05/19 09:35:10  №1402920 31
>>1402868
> бекенд обычно не разрабатывают
Net Core
Аноним 2019/05/22 23:52:06  №1405044 32
>>1403996

Ты ищешь сложное решение вместо простого. В случае использования элемента display: block и задания ему ширины, маргины вычисляются автоматически. Достаточно задать левый маргин, ширину и поставить auto для правого маргина.

Как плюс, это работает вообще во всех браузерах, в отличие от calc(), который пришел только с CSS3 и внедрен с ~2013 года: https://caniuse.com/#feat=calc

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

В CSS полезно знать, как работают значения по умолчанию, чтобы меньше писать кода и меньше менять при правках.

>>1403678

Вообще, я бы советовал просто прочитать цикл статей "создаем приложение на фреймворке X". Вот, например, например, первый попавшийся туториал по Symfony: https://auth0.com/blog/symfony-tutorial-building-a-blog-part-1/

А так, пожалуйста, сложное облачное хранилище файлов nextcloud: https://github.com/nextcloud/server

CMS Интернет-магазина Magento: https://github.com/magento/magento2

>>1402868

Я довольно долгое время под виндой работал, PHP и Апач там вполне работают.

На винду поставить несколько версий PHP проще простого - скачиваем zip-архивы и распаковываем в разные папки. А под линукс? Либо заморачиваться со сторонними репозиториями, которые перезапишут тебе libssl, либо использовать работающие с переменныем успехом скрипты вроде phpenv.
170 - 590 Аноним 2019/06/13 04:11:39  №1415600 33
>>1394765

> require_once '....error_handler.php';
> Что это за многоточие и что это подключаем тут?

Многоточие - это путь к файлу, если он в какой-то папке. Файл содержит обработчик ошибок.

> initErrorHandler();
> И эта функция что делает??

Ее надо написать самому, это функция из error_handler.php. Она, как следует из названия, задает обработчик ошибок.

> А bootstrap это толи модель, толи просто инициализатор в MVC.

Это файл, который подготавливает код: например, задает автозагрузчик, создает нужные объекты (или описывает из в DI контейнере). Если он тебе не нужен, то можно его не использовать.

> Так вот в тех классах исключения писать не надо? Мне не нужно в классе, который реализует паттерн TableDataGateway использовать отлов ошибок/исключение/try/catch?

Да, не надо.

> Подключение к БД в каком месте идет? В TableDataGateway или в index?

Можно создавать объект PDO в bootstrap.php, можно в index. Это не очень важно, где именно. Объект PDO создается там же, где и другие нужные для работы кода объекты.

> Просто если в index, то что мне делать в конструкторе public function __construct(PDO $pdo) { ... } класса TableDataGateway? Взять данные из базы данных в массив?

Это DI. Прочитай урок по DI: https://github.com/codedokode/pasta/blob/master/arch/di.md

> Это я тоже не понял. Это шаблоны?

Это Front Controlller, погугли.

> Ты подразумевал, что она где то описана?

В мануале.

Если есть еще какие-то вопросы, уточняй.