Это копия, сохраненная 7 сентября 2017 года.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.
>Также, у тебя в коде SQL инъекция, нельзя вставлять данные прямо в запрос
Так норм?
$escaped = pg_escape_string($data);
$query = "INSERT INTO searches (data) VALUES ('{$escaped}') RETURNING id";
php.net/manual/ru/book.pdo.php
Проценты же можно было бы не считать самому же, а в переменную $percent ввести сам процент и посчитать типа как $creditBalance*$percent/100 же.
А можно ли как-то передавать аргумент по умолчанию напрямую?
Вот например print_r(rateBank(1.02, 0, 7777));
Как можно запихнуть туда 7777 третьим параметром, не передавая 0 вторым?
Ты можешь передавать в функцию JSON в качестве единственного параметра, а уже внутри функции разбирать JSON на массив и проверять, существует ли пара ключ => значение в массиве. Всегда так делаю.
Тоже можно.
>Наркоман
Ваше мнение очень важно для нас. Как думаешь, что будет читабельнее?
function givePassport($name, $surname, $lastname, $age, $city, $region, $country, $birth)
или
function givePassport($data)
?
Ты пытаешься съехать с темы, школяр. Вопрос был конкретно о том, как передать в функцию третий параметр, не передавая второй. Я ответил, что можно передать JSON. Либо, как подметил анон, массивом. А ты полез меня оскорблять зачем-то. В принципе, не удивительно. В последнее время неадекватных детей на Дваче всё больше. Спасибо,
Абу!
Прекращайте.
>>1033966
Как я выше написал, вместо JSON уместнее использовать массив, чтобы избежать лишних преобразований.
Массив, правда, имеет тот недостаток, что никак не документируется, какие в нем должны быть поля и что они содержат (для этого придется анализировать код функции). Если массив используется только внутри функции, в нем немного полей и функция маленькая, это допустимо.
Но в больших, реальных приложениях, где много кода и этот массив передается потом в другие функции, весь код изучать нереалистично. Потому в таких случаях (массив передается в разные функции и используется в большом объеме кода) лучше будет отказаться от массива и сделать объект - тогда можно будет открыть файл с классом и увидеть, какие у него есть поля, прочитать комментарии к ним. Более того, в классе поля можно сделать закрытыми и сделать проверки, что им присвоены корректные значения.
Потому, если в примере ниже данные о пользователе используются много где, лучше будет сделать объект Пользователь и передавать его.
Да, он куда-то пропал и не перекатывал тред, из-за чего он ушел в бамплимит и тот тред теперь трудно найти.
Поэтому я сделал перекат за него.
Он занятой человек и работает много похоже, уже давно не сидит каждый день в тредах. Я вообще удивляюсь как он до сих пор спустя 4-5 лет умудряется заходить и стараться каждому нуфагу дать ответы вываливая большущие такие посты.
>>1034140
Он сам перекатит когда всем там ответит, нуфаг сука. Тут и по 1000 постов треды были и ничего, наоборот хорошо когда он утоплен, в нем нет словестного поноса по 200 постов в день.
>Он сам перекатит когда всем там ответит, нуфаг сука
Почему бы ему отвечать на старые вопросы там, а новые пусть будут скапливаться здесь? Часто в тред люди заходят с напрямую /pr, особенно впервые.
>в нем нет словестного поноса по 200 постов в день
Наоборот же хорошо, может на вопросы будут отвечать и другие люди. Иначе тред какой-то полумертвый получается.
Понял. В таком случае при eval значения всех суперглобальных переменных вроде $_GET и $_POST будут утеряны? Так же как и аргументы командной строки...
На случай, если из моего предыдущего сообщения не очень понятны мои мотивы: я хочу, чтобы зафейлившийся assert выдавал подробную информацию о переменных, участвовавших в условии assert'а. Например:
$a = 10;
$b = 25;
Custom\assert($a >= $b); // должно вывалиться исключение с сообщением "Expected ($a >= $b), $a is 10, $b is 25".
Тут преимущества, что assert'ы выглядят естественно, не нужны эти бесконечные assertCountEquals/assertGreaterThanOrEqual. Мне кажется, что такие assert'ы - изначально путь неправильный.
Чтобы знать как сортировочку написать, там про графы, деревья почитать.
Кормена?
А тебе ведь не нужен eval (который сам по себе не очень хорошая идея). При вызове функции ты уже получаешь результат вычисления выражения как аргумент.
eval не меняет суперглобальные переменные или аргументы командной строки.
Насчет того, хорошая ли у тебя идея, не знаю.
Можно в Питоне (Keyword Arguments), в PHP нельзя. Тут https://wiki.php.net/rfc/named_params что-то пытались реализовать, но сейчас всё глухо.
>>1034443
Кормен для новичка не подойдёт, ты бы хоть отзывы почитал (не на этом сайте). Гораздо доступнее учебник: http://aliev.me/runestone/
>>1034483
Без исполнения кода я смогу только фразу "Expected ($a >= $b)" вывести, а хотелось бы включить ещё и значения переменных $a и $b.
eval плохой, когда туда суют пользовательский ввод.
Я сам понимаю, что подводных камней огромное количество и написать такую штуку будет непросто.
На гитхаб было бы просто идеально. Особенно если разметить как следует, с заголовками, с картиночками.
Лучше всего сделать шапку как и было раньше, постом, чтобы не надо было ходить не по каким ссылкам.
сгинь, безработный студент
Нафиг не нужна, тебе же раз 5 сказали в обоих тредах, что по ссылкам никто ходить не будет. Шапка в ОП-посте придаёт треду ламповость, а ты всё портишь, прекращай.
Да там годные только для совсем совсем ньюфагов разьве что.
Это какие же? Игру написать, или сеть? По бигдате что-то высчитать? Когда говорят о чем-то крутом, я себе представляю сразу актуальные нерешенные задачи. Или программирование - это пердолиться с тем что уже решено 20 лет назад френкенштейня новый велосипед, под предлогом того что это развивает тебя и без БАЗЫ НИКУДА И НИКАК?
А что в бигдате сложного то? Вообще никогда этого не понимал. Вся сложность в том, что нужно щепетильно оптимизировать, чтобы сократить время перелопачивания бигдаты. Ну и знать буквально 2 приёма которые сокращают время выполнения обхода на 22%. Хотя я почти с дивана говорю, я только 4 терабайта лопатил. Может в больших масштабах всё сложнее?
Скоро дипломный экз в специалисте, а я уже нихера не помню, так как работа связана совершенно с другим.
(Ну и доступный, так как смотрю на foreach и нихуища не понимаю)
Ну сколько воды льёт Борисов в уши, я бы лучше PHP The Right Way использовал и почитал бы эпизодически Котерова или подобное. А видеокурсы это долго и неэффективно, когда во второй и более раз.
http://ideone.com/F3qKIr
Если бы это было так легко сделать, я бы сюда не писал, верно?
>нужно показать таблицу умножение от 1 до 9. Не получается перенести строчку множителей вместе с произведением.
Эх, я сам когда-то таким был, на держи нормальную версию таблицы умножения http://ideone.com/9MobUt
>Нужно было в начало ставить?
Сам подумай. Спецсимвол разбивает строку ровно в том месте, где ты его поставил.
>в чём различие между \r и \n?
Дела дней давно минувших. Изначально, \r возврат каретки - это переход в начало строки, без перехода на новую строку, а \nперевод строки - переход на новую строку без постановки курсора в начало строки.
Сейчас разницы нет, и то, и то переведет в начало следующей строки.
cпасибо за пояснение
спасибо! Лично мне удобно зафоркать к себе в профиль определенный гайд и следовать ему. Все равно гитхаб часто открыт у меня. Это удобнее, чем метаться между тредами фронт-энда и пхп. Добавил шапку в гитхабе и на двачи не заходишь
С \r и \n интересная история. \r по задумке переводит курсор в начало строки, а \n - на следующую строку. В принтерах это использовалось (и до сих пор работает) для печати одних символов поверх других, например подчеркивания поверх буквы.
В тестовых файлах раньше Маки использовали \r для обозначения конца строки, а линуксы/юниксы - \n. Когда делали ДОС, для совместимости решили там обозначать конец строки как \r\n, и это перекочевало в Windows (например, блокнот исопльзует такой формат). Маки давно уже перешли на \n, так что теперь осталось только 2 варианта - либо \n, либо \r\n. IDE обычно позволяют выбирать вид конца строки, рекомендация PSR рекомендует использовать \n.
\r в консоли переводит курсор в начале строки, что позволяет обновлять информацию, перепечатывя ту же строку заново. Например, для вывода бегущей шкалы прогресса.
> Когда делали ДОС, для совместимости решили там обозначать конец строки как \r\n
Было джва, получилось три, как обычно в таких случаях. Кстати, раз ты такой умный, то может знаешь, что такое вертикальная табуляция (\v)? Я сейчас читаю один любопытный документ (ISO/IEC 14977 ENBF), там встречается описание этого символа. Что такое горизонтальная табуляция - понятно, а что такое вертикальная? И что такое форм фид (\f) заодно?
т.е. EBNF, но не суть
Символ вертикальной табуляции(\v) переводит курсор на следующую позицию вертикальной табуляции, и вывод текста продолжается с этой позиции.
Он указывает число строк, которые должны быть пропущены на странице перед началом печати очередной порции данных.
Этот символ влияет только на печать документов. Он не влияет на вывод текста на экран.
мимокрест
В принтере form feed вызывает переход на новую страницу: https://en.wikipedia.org/wiki/Page_break
Про табы история тут: https://en.wikipedia.org/wiki/Tab_key - исторически в принтерах позиции табуляции задавались механически и горизонтальный/вертикальный таб перемещал печатающую головку к следующей позиции. Использовалось для печати данных в формах.
Ну и про другие управляющие символы тут:
https://en.wikipedia.org/wiki/Control_character
https://en.wikipedia.org/wiki/C0_and_C1_control_codes
Подавляющее большинство из них сегодня не используется.
Стоит помнить, что эти управляющие коды берут свое начало из принтеров, телетайпов (когда-то у компьютеров не было мониторов, вместо них использовали готовые клавиатуру и принтер от телетайпа, чтобы не изобретать их с нуля) и может быть еще каких-то более древних систем передачи данных по проводам. Вот например, сейчас мы говорим "консоль" про окно командной строки. Но когда-то консолью назывались принтер и клавиатура, которые образовывали терминал и были подсоединены к большому компьютеру-мейнфрейму.
Или другой пример: кодировка ASCII - принята аж в 1963 году, опять же не только для компьютеров, которые тогда только появлялись, а вообще для передачи данных по проводам ( https://en.wikipedia.org/wiki/ASCII ).
Интересно, а по 3d принтерам тоже всякого понапридумывают?
>Что там за хуита на пастебине? Ещё и с анимевасяносайтом.
Вам нужно проследовать обратно в /b. Вы похоже заблудились.
>Что там за хуита на пастебине?
Вот новая шапка на гитхабе
https://github.com/phpguron/php_thead/wiki
>с анимевасяносайтом
Вполне себе годный сайт с уроками.
Скрытая пропаганда руби
в wrk фронтенд тред
Можно, веб-разработка же
Существует ли аналог Джавы Раш для пхп?
Нет ли такого же по 7-му рнр?
Если нет онлайн, где бы спиратить тестовый тест?
Есть несколько приложений на ведроиде, но они дико бесят рекламой, выскакивающей прямо поверх обучения.
Ну по факту, её обновления мне будут и не нужны, так как я её всё равно изменю
Ни в коем случае не меняйте ничего в сторонних библиотеках. Причины:
- после того, как ты что-то изменишь, нельзя легко обновить библиотеку
- трудно будет понять, что именно изменено и где
- нельзя установить библиотеку через менеджер пакетов вроде composer
А что тебе мешает сделать изменения средствами, предусмотренными фреймворком? Если ты не в силах разобраться, передай задачу тому, кто может.
>А что тебе мешает сделать изменения средствами, предусмотренными фреймворком? Если ты не в силах разобраться, передай задачу тому, кто может
При чем тут не в силах разобраться, разобраться можно с чем угодно. Я вот и спрашиваю - нормальный ли вариант взять за основу стандартный модуль RBAC и сделать отдельный модуль изменив то, что мне нужно
Абсолютно нормально.
Необходимо изучить фреймворк yii. но каких-то хороших уроков или мануалов найти не могу. а для работы требуется его знание. А я по своей глупости сам в нем разобраться не могу.
Знаешь ли что по этой теме и с чего начать?
(студент, знаю ооп, html, php и тд на базовом уровне, ибо лентяй да и соответствующей практики не было)
теория.
мне б понять на практике.
там был пример создания их блога, но на 3 пункте при создании выходила ошибка, решить которую я не смог(гугл не помог)
http://www.yiiframework.com/doc/blog/ ?
ну а чем не практика? ты хочешь туториал, который гарантировано исключит любые твои же ошибки? как это?
http://www.yiiframework.com/doc/blog/1.1/ru/prototype.auth
вот на этой странице идет настройка Аутентификации. Выполнив все что здесь написано, решил проверить. захожу на сайт. пытаюсь войти. он выдает ошибку, что пароль неверен. Как я понял, он захэшировал пароль в БД, но не хэширует пароль проверяемый с тем, что в БД. Как происходит сравнение паролей не понимаю (да тупой и там на сайте буквально все расписано, но я даун.)
значит пойду переучивать все с начала самого, и воспользуюсь гайдом с шапки. чувствую себя днищем
Есть сервак-файлопомойка, на него я заливаю картиночки, выдает прямую ссылку на них. Так вот, втентакле никак не хочет распознавать картинку с ссылки (т.е. скопипастил линк в сообщение, обычно картинка прикрепляется автоматически, а тут нихуя). В чем может быть проблема?
<head>
<meta property="og:image" content="https://pp.vk.me/c629531/v629531034/3172e/xEBYyER1WE4.jpg" />
<link rel="image_src" href="https://pp.vk.me/c629531/v629531034/3172e/xEBYyER1WE4.jpg">
</head>
MIME type?
Спасибо за совет анон, а то Кернигана половину прошел.
Бамп...
нужно написать курсач по php. Но не знаю какую тему выбрать. ничего придумать не могу. Нужно что-то интересное
С джуном всё ясно, там всегда стандартный набор и похожие задачи.
А на мидлов всегда всё по разному, не понятно...
Способы ускорения выполнения программ на РНР
блог - самое банальное
если знаешь про long polling, то сделай лобби для онлайн игры. типа, чтобы всё как в вк без обновления
if ( Locale::getDefault() == 'ru-RU' ) {
exit('Произошла инфляция, валюта вклада обесценилась, пенсии отменены, вы проиграли');
}
спасибо помог, прям как оп завещал
Нужно отказаться от идеи прибавлять фиксированную сумму. Нужно хранить в переменной текущую сумму на счету и увеличивать ее на 10% от текущего значения на каждом шаге.
Вот тут написано как увеличить число на N% https://otvet.mail.ru/question/24786316
Как то вот так пытался: $order = $amount1; где order это чекбокс, а amount1 это инпут.
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox" class="form" name="order">
</span>
<input type="text" class="form-control" name="amount1">
</div><!-- /input-group -->
Так же пытался вводить в value(чекбокса) пхп код содержащий переменную инпута(тоесть с name инпута) и всё равно ничего, уже все перепробовал и никак.
Опишу даже суть идеи, чтобы было прям вот понятно, что я хочу сделать. Вообщем есть сайт-магазин с мелким оптом. Товар 1 вида но разных цветов поэтому делать корзину ну я считаю незачем. На нем вообщем фотки с цветами и подписан id цвета и внизу есть чекбокс и инпут, в котором по задумке клиент должен вводить количество товара. Далее когда клиент выставит чекбоксы на нужных товарах, а так же выставит количество он доходит до формы оформления и всё это отправляется в базу sql.
Миграции и дампы - вещи разные. Миграции описывают изменения структуры таблиц во времени, дампы содержат ещё и данные в этих таблицах. Если в миграциях происходит наполнение таблиц данными (видел такое), то автор неправильно использует миграции.
> Наверняка же есть простой метод
Да, mysqldump
>>1037486
Будет особенно полезно, если реализуешь хотя бы простые структуры данных на Си (связанные списки, стек, очереди, деревья).
>>1037002
В PHP вообще нет указателей, а указателей на указатели - подавно. Указатель - это переменная, содержащая информацию о том, где в памяти размещён какой-то объект.
>>1037669
А смысл готовиться? Рассказывай о интересных проектах, в которых участвовал, приведи профиль на GitHub'е в порядок. Мидлов не просят сортировки на листочке писать. Да и толковых джунов тоже, если с ними есть о чём говорить.
мимо-джун-прошедший-много-собесов
>Указатель - это переменная, содержащая информацию о том, где в памяти размещён какой-то объект.
Но ведь любая переменная фактически ссылается на какой-то участок памяти, в котором расположен объект. Ну, в питоне так, по крайней мере, в пхп тоже, вроде.
зачем вообще обрабатывать этот чекбокс на сервере, если покупатель ввёл количество товара нужного цвета?
потому что я вот так решил и теперь не понимаю, как это сделать(ну я вроде понимаю но чет нихуя не выходит совершенно)
>А смысл готовиться? Рассказывай о интересных проектах, в которых участвовал, приведи профиль на GitHub'е в порядок. Мидлов не просят сортировки на листочке писать. Да и толковых джунов тоже, если с ними есть о чём говорить.
>мимо-джун-прошедший-много-собесов
А вот нет, просят написать какой нибудь запрос, спроектировать структуру проекта и много вопросов по паттернам всяким, олимпиадные задачки попадаются, вопросы по CS.
Для джунов я паттерн собеседований понял, а вот мидлов/суньёров нет.
К тому же, у меня например сейчас считай пустой github, так как моей конторе это не интересно, а свободного времени на реализацию чего-то что можно показать нет.
> нене, конкретику давай, в чём плюс?
Отобразить список залогиненных на сайте пользователей со всей инфой. Гомогенный подход. Для начала тебе пойдет.
Повсюду в Laravel используются. Только вот в трейтах мало хорошего - это по сути автоматизированный копипаст, который неудобно юнит-тестировать: http://blog.ircmaxell.com/2011/07/are-traits-new-eval.html
Пример - Laravel Dusk - пакет для написания браузерных тестов, весь функционал которого построен на трейтах, вот один из них: https://github.com/laravel/dusk/blob/1.0/src/Concerns/InteractsWithElements.php
По коду видно, что трейту нужен какой-то resolver (непонятно какой именно класс), какой-то driver и ещё используется метод waitUsing, который тоже непонятно откуда будет браться. Если не читать весь код трейта, то непонятно какие нужны объекты и методы, чтобы переиспользовать этот трейт. Подход с DI гораздо логичнее, проще, не нарушает инкапсуляцию, не порождает сильную связанность.
>>1038689
То, что ты описал - это база, которую как раз у джунов спрашивают, ведь у них мало практического опыта и нужно поспрашивать теорию. Ещё это объёмные вопросы, с которыми ты не разберёшься за неделю до собеседования, поэтому и "готовиться" смысла особого нет, разве что повторить то, что уже быть может подзабыл. В нашей конторе на мидлов+ спрашивают преимущественно о интересных завершённых проектах.
> у меня например сейчас считай пустой github, так как моей конторе это не интересно
Ей и не должно быть интересно. Но если ты идёшь на мидла без своего кода, который можно показать, то тебя засмеют.
Банковский процент считается так:
A=B(1+%)^n
A, В, % тебе известны. Осталось найти n(кол-во лет )
https://laravel.com/docs/5.4/blade#including-sub-views
Суть в следующем:
1. Создаёшь отдельный blade-файл
2. Подключаешь его в нужных местах с аргументами, как-то так:
@include('macros/button.blade.php', [
'isAdmin' => $isAdmin,
...
])
>это база, которую как раз у джунов спрашивают,
У джунов спрашивают про принципы ООП и пузырьковую сортировку.
А не про фабрики, очереди и прочее, и уж точно не просят спроектировать модуль или проект.
Никто не поверит на слово про интересные проекты, всем нужны пруфы или в виде кода(хотя всё равно любят пихать тестовое задание на 5-6 дней работы!) или в виде решения каких то задач на собеседование, что бы посмотреть, как кандидат соображает.
В одной конторе мне дали онлайн тесты лол, там были вопросы в стиле "B наследует A, что выведет метод C". Дропнул их сразу сказав что дальше мне не интересно. И они искали именно Middle PHP программиста.
>Но если ты идёшь на мидла без своего кода, который можно показать, то тебя засмеют.
И когда ты предлагаешь мне пилить свои петпроджекты?
После работы, когда ты уже устал и особо нет сил, к тому же есть ещё и другие дела, или на выходных, когда всё же хочется отдохнуть?
Большинство гитхаб кода людей что я видел, написано ими в рабочее время, для нужд компании. Да и к тому же, как правило народ выкладывает не большие модули, мне кажется по ним сложно сделать вывод о компетентности человека.
Это не нормально, ищи ошибку. Если не установлен laravel-debugbar, то поставь, он показывает отрендереные вьюшки: https://github.com/barryvdh/laravel-debugbar
>>1038910
Спрашивали у меня в аутсорс конторах на джуна как раз про фабрики и очереди. Мне ещё сыграло на руку то, что мог назвать примеры применения тех или иных паттернов в коде Symfony и Laravel (в свободное время просто люблю почитывать исходный код фреймворков, чтобы лучше их понимать). Насчёт свободного времени - понимаю, но ты можешь посидеть недельку-две дома, запилить пару проектов на хорошо знакомых технологиях с тестами. Для толкового мидла это не сложно.
Есть сайт на yii2, есть модель (в которой есть только tableName(), rules() и attributeLabels() ) вот я захотел расширить эту модель, дописал новые свойства, делаю во вьюхе $form->field($model, 'newProp') и получаю ошибку.
Гугл кидает на перегрузку виртуальных атрибутов (но это ведь не то?)
>Включает в себя HTML- и CSS-шаблоны оформления для типографики, веб-форм, кнопок, меток, блоков навигации и прочих компонентов веб-интерфейса, включая JavaScript-расширения.
Лол блядь, что то типа Muse?
Все тот же анон
Это когда кричали, что верстка таблицами не семантична и делали квадратно-гнездовым методом на дивах. Конвейер, короче.
верстальщики выёбываются
они вообще ещё нужны? РНР же и сам нормальный шаблонизатор, по сути, с производительностью в 7 ветке тоже проблем нет. Нужны тогда эти твиги-хуиги?
Вкусовщина, я считаю PHP хорошо справляется.
[code]
function foo($variable, $array){
}
[/code]
Как можно поставить брейкопоинт на вход этой функции чтобы я мог в окне увидеть значения $variable, $array когда дело дойдет до нее? Чтобы в браузере открыть тестовый домен, а аргументы как-то увидеть не дописывая вручную дамп в файл в эту функцию. Все таки 2017 год уже
>В PHP вообще нет указателей, а указателей на указатели - подавно. Указатель - это переменная, содержащая информацию о том, где в памяти размещён какой-то объект.
"Reference" переводится, как "ссылка", и я о них в мануале читал, их не может не быть. Мануал я на самом деле прочитал, так что просто скажи: есть ли у ссылок более одного уровня.........
Так, стоп, в мануале же ничего не написано об этом.
а как ты предлагаешь делать адаптивные таблицы?
preg_replace( '/(бла-бла)(мяу-мяу)/u', function($2), $string);
То есть, я хочу заменить текст в исходной строке $string на хуйню, которая выцеплена второй подмаской регулярки, но при этом хочу обработать эту хуйню функцией. Пхп не разрешает обрабатывать хуйню функцией, что мне не нравится. Вопрос: как добиться аналогичного эффекта законными средствами? Спасибо.
На самом деле не заменить в исходной строке, а создать новую с заменой в нужном месте, извините за неточность
Цикл просто не запускается, хз почему.
Укажите на ошибку, пожалуйста, пробовал по-всякому вычислить ошибку:
дебагил с помощью echo, но цикл просто не запускается, видимо, ошибка где-то в условиях.
http://ideone.com/bnrFXe
https://secure.php.net/manual/ru/control-structures.for.php
>В начале каждой итерации оценивается выражение expr2. Если оно принимает значение TRUE, то цикл продолжается
В том, что я с помощью непосредственно регулярок указываю на то, к каким символам в строке применить функцию.
Блин, только что сам открыл другой источник, перечитал и осознал.
Соре за невнимательность, всё получилось!
Тогда я не знаю чем тебе помочь.
Ты можешь, конечно, сначала сохранить те символы в отдельную переменную, обработать функцией, а затем заново выполнить замену.
Впихнул preg_replace_callback(), вопрос снят
Какого блять начала
В принципе есть смысл лазить в БД лишний раз на каждой странице? Это же медленнее в несколько десятков раз.
> Капитализация компании Amazon в ходе торгов 26 июля впервые превысила $500 млрд, передает Financial Times.
Ну как тебе сказать.... Понятно, что капитализация - это абстрактная цифра, но погугли, сколько у них работает инженеров, и прикинь, сколько они времени затратили на разработку кода.
>>1039833
Это спорно и без тестов не проверить. Если у тебя не хайлоад, то вполне может оказаться, что в базу будет залезть как раз быстрее. Откуда у тебя уверенность, что "база медленная, а файлы быстрые"? Опыт использования нищехостинга?
>>1039394
preg_replace_callback. PHP правильно делает что запрещает флаг e, так как это ведет к уязвимостям.
>>1039253
Думаю, нету. Думаю, при попытке взять ссылку на ссылку ты получишь ссылку на конечный объект (попробуй протестировать сам).
В PHP ссылки - это не указатели из Си. В PHP по идее все значения хранятся в памяти, а переменная содержит указатель на эту область памяти. Когда ты делаешь ссылку, PHP просто создает вторую переменную, ссылающуюся на то же значение. Но сделать переменную, хранящую ссылки (по аналогии с Си, где есть переменная, хранящая указатель), нельзя. Такого типа данных в PHP нету.
>>1039125
Да, нужно установить и настроить расширение xdebug (это больно), а потом настроить IDE (или другой клиент xdebug) для взаимодействия с ним. И тогда можно отлаживать код, выполнять пошагово, ставить брейкпойнты и тд.
Написано подробно на главной твига https://twig.symfony.com/
>>1039074
Есть такие варианты:
- переопределить render($template, $data) в базовом классе, чтобы он туда добавлял нужные переменные
- сделать отдельную функицию renderWithAdditionalVariables(..) которая добавит нужные переменные
- создать отдельный объект Twig_Environment, в него добавить переменные через addGlobal, и этот объект твига уже передать в контроллер с помощью DI (ну или можно в контроллере его и создать)
- сделать отдельный хелпер/контроллер предоставляющий данные для вывода шапки/подвала/меню
Ну и может еще какие-то варианты.
>>1039034
Таблицы надо верстать таблицами, а раскладку делать в CSS. И по моему, таблицы можно через CSS застаивть отображаться как дивы, или нельзя?
>>1039018
Тупо использовать вещи не по назначению. Тег Table предназначен для разметки табличных данных, а не для чего-то другого.
>>1038994
Я думаю, нужен или не нужен - зависит от ситуации.
Написано подробно на главной твига https://twig.symfony.com/
>>1039074
Есть такие варианты:
- переопределить render($template, $data) в базовом классе, чтобы он туда добавлял нужные переменные
- сделать отдельную функицию renderWithAdditionalVariables(..) которая добавит нужные переменные
- создать отдельный объект Twig_Environment, в него добавить переменные через addGlobal, и этот объект твига уже передать в контроллер с помощью DI (ну или можно в контроллере его и создать)
- сделать отдельный хелпер/контроллер предоставляющий данные для вывода шапки/подвала/меню
Ну и может еще какие-то варианты.
>>1039034
Таблицы надо верстать таблицами, а раскладку делать в CSS. И по моему, таблицы можно через CSS застаивть отображаться как дивы, или нельзя?
>>1039018
Тупо использовать вещи не по назначению. Тег Table предназначен для разметки табличных данных, а не для чего-то другого.
>>1038994
Я думаю, нужен или не нужен - зависит от ситуации.
А в модели есть нужное поле? Вообще, надо наверно докуменатцию читать и изучать причины ошибки.
>>1038910
Я вообще не понимаю, в чем причина недовольства, если не нравится, можешь не проходить тестовые задания, никто ведь не заставляет. Наверняка есть другие варианты, например, рекомендации от уважаемых в отрасли людей, опыт работы в выдающихся проектах или еще что-нибудь.
> А вот нет, просят написать какой нибудь запрос, спроектировать структуру проекта и много вопросов по паттернам всяким, олимпиадные задачки попадаются, вопросы по CS.
И? Тебе всего лишь нужно ответить на них лучше, или хотя бы не хуже, других кандидатов и с нынешним уровнем кандидатов, мне кажеется, это вообще сложности не представляет для человека с опытом.
>>1038926
Толковый мидл наверно может рассказать о своем опыте работы, о своем вкладе, и может дополнительные задания и не понядобятся. Хотя не понимаю, что сложного, ответить на несколько вопросов.
>>1038841
Ага, у меня иногда ощущение, что трейты просто пытаются использовать ради того, чтобы они были. Хотя может какие-то случаи для их использования и есть.
> доступ к файлам быстрее работает, например.
пруфы есть?
>>1038785
Их еще в редисе можно хранить.
>>1038177
Почему же, если мы например миграцией добавляем таблицу стран, почему бы сразу и не заполнить ее, чтобы не надо было делать это вручную?
>>1038131
Тебе надо учиться. Это можно сделать яваскриптом, но смысла в этом нет, так как проще переделать твой обработчик формы.
>>1038063
Гуглить, искать статьи, примеры кода и решения
>>1037905
Решается примерно так:
- прибавляем проценты и комиссию к остатку долга (!не вычитаем ничего пока!)
- если остаток маленький, выплачиваем сколько осталось и уходим
- иначе платим 5000
«Платим» здесь значит уменьшаем долг и увеличиваем общую сумму выплаченного.
> доступ к файлам быстрее работает, например.
пруфы есть?
>>1038785
Их еще в редисе можно хранить.
>>1038177
Почему же, если мы например миграцией добавляем таблицу стран, почему бы сразу и не заполнить ее, чтобы не надо было делать это вручную?
>>1038131
Тебе надо учиться. Это можно сделать яваскриптом, но смысла в этом нет, так как проще переделать твой обработчик формы.
>>1038063
Гуглить, искать статьи, примеры кода и решения
>>1037905
Решается примерно так:
- прибавляем проценты и комиссию к остатку долга (!не вычитаем ничего пока!)
- если остаток маленький, выплачиваем сколько осталось и уходим
- иначе платим 5000
«Платим» здесь значит уменьшаем долг и увеличиваем общую сумму выплаченного.
> Без исполнения кода я смогу только фразу "Expected ($a >= $b)" вывести, а хотелось бы включить ещё и значения переменных $a и $b.
Ну ты ведь понимаешь, что тут придется парсить код в AST и как-то вытягивать значения переменных. В C# вроде так можно, там передают анонимную функцию и ее тело разбирают на выражения.
Потому в phpunit используют $this->assertGreaterThen($a, $b);
Ну и еще есть другие варианты (как в JS) вроде Assert($a)->isGreaterThan($b);
assert($a, '>', $b) - не подходит?
> Тут преимущества, что assert'ы выглядят естественно, не нужны эти бесконечные assertCountEquals/assertGreaterThanOrEqual. Мне кажется, что такие assert'ы - изначально путь неправильный.
Ну они по крайней мере работают.
В твоем подходе проблема в том что надо получить переменные из скопа родительской функции, и я не уверен, что это вообще возможно сделать (хотя надо посмотреть функции - может что-то и есть, в текущей функции можно получить переменные через get_defined_vars)
>И? Тебе всего лишь нужно ответить на них
Я не жалуюсь, я изначально спрашивал про паттерн собеседований, а потом рассказывал отличие джунских и мидловых собеседований, которые я видел.
Но тестовое задание больше чем на один вечер это перебор.
Я ведь не в одну контору в этот момент собеседуюсь, если каждый даст задание на 16 часов работы, я ни одно не успею, особенно если я не уволился ещё с текущей работы(это вроде нормально).
>Толковый мидл наверно может рассказать о своем опыте работы, о своем вкладе
Нужно поднять скилл красноречия, а то я обычно говорю мол "Чем не занимался, всё говно какое то", хотя это неверно.
>Ага, у меня иногда ощущение, что трейты просто пытаются использовать ради того, чтобы они были. Хотя может какие-то случаи для их использования и есть.
Однажды трейты очень помогли при рефакторинге очень плохо написанного проекта, нужно было внедрять новый функционал на ходу не ломая старый и в той иерархи это было возможно лишь трейтами.
>Почему же, если мы например миграцией добавляем таблицу стран
Если это константные значения, то не нужно их хранить в базе.
Если нет, то они могут быть не нужны в другом проекте.
Пиздец, как я криво всё расписал, но вроде ясно.
http://ideone.com/V8DrXU
Хм, да и то думаю, что местные ветераны посмеются над этим.
Как-то начал задрачивать phalcon и настрочил 5 тыс. строк кода поверх фреймворка, чтобы получилась CMS на phalcon+mongodb. В итоге запутался в блядских наркоманских депенденси инжекшенах, а также понял, что дебажить это соколиное говно проктически невозможно, т.к. нет php-исходников фреймворка, и не понятно, что за нахуй там в нём происходит. Так и забил.
Я создаю свой контроллер, потом свою модель, в модели будут использоваться методы slim. В index.php я создаю объект слима $app = new Slim.
Мне этот $app в $container засунуть и тащить его через всё приложение в аргументах что бы я мог им в модели воспользоваться? FrontConroller>FileDownloadConroller>FileDownloadModel
Я явно делаю всё не так.
Я читал доки, но многое не понятно, собственно что касается DI и Middleware.
>FrontConroller>FileDownloadConroller>
Что-то явно не так, зачем контроллер дёргает контроллер?
>Мне этот $app в $container засунуть и тащить его через всё приложение в аргументах что бы я мог им в модели воспользоваться?
А зачем тебе $app в твоей модели изначально?
>Что-то явно не так, зачем контроллер дёргает контроллер?
Эм, это паттерн вообще такой. Есть главный контроллер, который определяет какому не главному контроллеру передать запрос.
>>1040549
>А зачем тебе $app в твоей модели изначально?
Загрузка файлов осуществляется с использованием возможностей фреймворка.
>Эм, это паттерн вообще такой. Есть главный контроллер, который определяет какому не главному контроллеру передать запрос.
Не вижу ни единой причины тебе использовать его. В твоём случае Slim - фронт контроллер.
>Загрузка файлов осуществляется с использованием возможностей фреймворка.
https://www.slimframework.com/docs/cookbook/uploading-files.html
Документация говорит, что то делается в одну строчку
$uploadedFiles = $request->getUploadedFiles();
Т.е. тебе нужен лишь request, а никак не $app. Твои классы не должны зависиоть от $app.
http://ideone.com/yG2PB6
>Виза нужна?
Две недели без визы можно
>Сколько стоит в месяц сычевальня с таким видом из окна?
Как на пике 2к$ за микростудию в месяц
Если искать не в Гонконге, а в Коулуне - то можно от 1к$.
Дорого.
Мда, echo было внутри цикла, я понял.
Ну то есть обычный require сразу отобразит содержимое файла представления, а если нужно взять шаблон в строку, чтобы передать куда-то дальше (в объект Response например)?
Покопался в исходниках слима, там используют буфферизацию типа
ob_start();
include $template;
$output = ob_get_clean();
return Response($output);
Это общепринятый подход? Я имею ввиду, когда не использую фреймворк типа слима, а только библиотеку HttpFoundation.
Хуя.
Если slim - frontcontroller, то мне делать толстый index.php, в котором описывать какому контроллеру отправлять запрос? Все getы и postы обрабатывать в нем?
Смотрю и ссусь, там же так высоко, и как люди работают только.
Был же сайт с уроками, куда делся?
Ну да. Микрофреймворк, у тебя же не расрастется там проект до тысяч рестов. Да ещё и слой контроллеров достаточно тонкий обычно, ты же там будешь сервисы дёргать, а вся логика в них.
В интернете много примеров шаблонов проектирования, но не объясняется, в чем преимущество их использования.
$factory = new FirstFactory();
$firstProduct = $factory->getProduct(); //в getProduct просто return new FirstProduct
$factory = new SecondFactory();
$secondProduct = $factory->getProduct(); // return new SecondProduct
Что мешает написать new Product прямо, не прибегая к созданию фабрики и вызова ее методов?
Может это сделано, чтобы эта часть кода была универсальной, не требовала правок, ну то есть изменения можно было вносить внутри фабрики, не меняя основной код?
Можно реальный пример, где видны преимущества использования?
Вот еще дурацкий пример
https://habrahabr.ru/sandbox/19322/
$obj = Car_Factory::factory('Big_Car');
$obj -> buildEngine();
$obj -> attachWheels();
$obj -> testing();
Что мешает в первой строчке сделать $obj = new BigCar; ?
Я использую фабрику в том случае, когда мне требуется создавать много объектов, требующих одинаковой настройки. В этом случае я могу один раз при создании фабрики задать ее параметры и она будет отдавать мне уже правильно настроенные объекты. Я не могу привести пример из мира php, но в JDK можно увидеть пример использования, например, в DocumentBuilderFactory.
>Вот еще дурацкий пример
По ссылке не ходил. Если в этом весь пример, то он действительно дурацкий.
Фабрики нужны для ленивого создания/получения объектов (возможно, с приватным конструктором). Например, ты обращаешься к фабрике:
$doc = Factory::getDocument();
Сам документ может не существовать до этого вызова. А после вызова существует. Повторный вызов Factory::getDocument() возвращает уже имеющийся объект. Кроме того, фабрика, например, может возвращать объект разных классов одним и тем же методом в зависимости от своих настроек. А ещё используются для IoC, которая, как и DI, ломает к хуям всю инкпасуляцию, поэтому не нужна.
Короче, фабрики не нужны.
>Повторный вызов Factory::getDocument() возвращает уже имеющийся объект
Тогда это уже Provider, а не фабрика. Фабрика подразумевает как раз создание нового экземпляра на каждый вызов, кеширование в ее обязанности не входит.
> в том случае, когда мне требуется создавать много объектов, требующих одинаковой настройки
Цикл?
> Повторный вызов Factory::getDocument() возвращает уже имеющийся объект
>>1041343
> Provider
Ты напутал с singleton.
Вообще, призываю новичков критически относиться к ответом ИТТ,
некоторые просто провайдят дремучие заблуждения.
По поводу фабрик, держи пример с описанием: http://ideone.com/h6Kvos
Ну и при чем тут цикл? Мне же не обязательно в одном и том же месте потребуются эти 100500 экземпляров. В одном методе один, в другом методе один, где тут цикл-то?
Ну, ок. Хотя на практике такое называется фабрикой. Например, в джумле:
https://api.joomla.org/cms-3/classes/JFactory.html
Они там в ней собрали говно со всего фреймворка и пользуются этой фабрикой вместо глобалов.
>Ты напутал с singleton.
Я-то не напутал, это он напутал. Фабрика производит на каждый вызов новый экземпляр. Провайдер отдает необходимый результат в ответ на запрос с возможностью кеширования. Синглтон - нечто 100% в единственном экземпляре с ленивой инициализацией.
Ну хорошо, в джумле какой-то Раджеш Кутапали решил, что неплохо было бы назвать это фабрикой. А еще где?
Ну я не пойму, здесь паттерны обсуждают или... вэйт. Я же в пхп-треде. Ладно, хуй с вами.
Приведи контр-пример тогда, а не требуй дополнительных подтверждений, которых тебе всегда будет мало, и для которых ты найдёшь оправдание в стиле Раджеш Кутапали.
По поводу твоего примера, он тоже ни о чем не говорит, зачем писать
$r = $ShapeFactory->getShape('R');
$s = $ShapeFactory->getShape('S');
$c = $ShapeFactory->getShape('C');
Если можно
$r = new RectangleShape();
$s = new SquareShape();
$c = new CircleShape();
Единственный весомый аргумент, который привели выше, это сложная инициализация объекта, ну то есть когда кроме
$obj = new Obj();
для каждого экземпляра нужна рутина типа
$obj->setProp1();
$obj->setProp2();
$obj->doSmth3();
// 100500 строк кода
Хотя почему бы не перенести эту рутину в конструктор?
При чем тут контр-пример? Речь о терминологии идет. Терминология паттернов описана в книжке от gang of four, предлагаю тебе в нее заглянуть. Сам, кстати, заглянул специально чтобы ткнуть носом, но, как ни странно, не нашел там вообще такого паттерна, как просто фабрика. Тем не менее, если взять абстрактную фабрику, то в ней явно речь идет о том, что каждый вызов порождает новый объект.
>>1041357
>Хотя почему бы не перенести эту рутину в конструктор?
1. Нехуй в конструкторе делать что-то помимо создания объекта.
2. Настраивать, как правило, можно по разному, не будешь же ты 20 аргументов передавать в к-тор на все случаи жизни.
3. В стороннюю либу ты вообще залезть не сможешь.
> можно
> почему бы
Тебя никто не заставляет, можно так и ещё 1001 способом.
Паттерны -- это не законы, которые надо выполнять, а некоторые выработанные шаблоны.
Зачем -- затем, что иногда так удобно. Например, в другой части программы ты не знаешь и не хочешь знать типы фигур, а работаешь с одним объектом -- их фабрикой.
> он тоже ни о чем не говорит
Ничего не говорит тебе, тут же не пример виноват.
> весомый аргумент
как будто тебя тут уговаривают, блять,
ну пожалуйста, используй паттерны, а то 40 лет программирования до этого уйдут в пустую
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html
Такой пример тебя устроит?
>>1041357
Потому что твой клиентский код может не знать о том, инстанс какого конкретного класса ему создавать и ты просто физически не можешь написать new. А фабрика знать может и вабрику тебе могут подложить извне.
>>1041360
>1. Нехуй в конструкторе делать что-то помимо создания объекта.
Если после создания твой объект непригоден к использованию, то у тебя хуевый конструктор.
>2. Настраивать, как правило, можно по разному, не будешь же ты 20 аргументов передавать в к-тор на все случаи жизни.
Не буду, для этого придумали Builder.
> говножаба вместо языка программирования
К тому же твой пример работает против тебя же:
> Return an instance, which may be shared or independent
>для каждого экземпляра нужна рутина типа
>$obj->setProp1();
>$obj->setProp2();
>$obj->doSmth3();
это другой паттерн, http://ideone.com/IrT4LL
>Единственный весомый аргумент
короче, хватит высирать своё детский сад сюда, учи мат.часть, ссука, потом вякай тут про аргументы
> говножаба вместо языка программирования
Пехапе тебе не язык, жаба тебе не язык. Ты из какого треда сюда повыебываться пришёл?
>К тому же твой пример работает против тебя же
Если бы ты хоть что-то написал с использованием Spring, ты бы знал, что большая часть компонентов имеет singleton scope. А так то да: ты там и своих скоупов наклепать можешь, вперёд. От этого фабрика быть фабрикой не перестанет и в банальный сборщик объекта по имени не превратится.
ОП, ты подтверждаешь? Idex будет выполнять роль фронт-контроллера?
> isSingleton(String name)
> Is this bean a shared singleton? That is, will getBean(java.lang.String) always return the same instance?
Но эта фабрика возвращает шаред синглтоны точно так же как Джумла, ссылку на апи которой я тебе давал выше. Что ты хочешь мне доказать?
Так я с тобой и согласен. Я писал тому парню, которому джумловские фабрики - не фабрики.
Вообще, "фабрика" - это функция или объект, создающий другие объекты.
Смысл в том, что иногда ты хочешь поменять код создания объекта, но этот код находится в сторонней библиотеке и поменять ты его не можешь. Тогда ты просишь автора библиотеки запилить там фабрику (а может он ее даже уже запилил) вместо создания объекта напрямую. После чего получаешь возможность влиять на процесс создания объектов:
class MyFActory { ... }
$myFacrtory = new MyFactory;
$otherObject = new OtherObject;
$otherObject->setFactory($myFactory);
// PROFIT
Также фабрика может использоваться, когда просто new не годится. Ну например, в зависимости от условий нужно создавать разные объекты с разными параметрами. Тогда код можно вынести в фабрику.
Главное только слишком не увлекаться этими паттернами, а то получится как в статье https://habrahabr.ru/post/153225/
Тут вот https://en.wikipedia.org/wiki/Abstract_factory_pattern приведен хороший пример: фабрика в GUI фреймворке, которая создает разные элементы GUI в зависимости от платформы. также, там в начале в Overview указано, какие проблемы решает паттерн.
Заметь, что есть еще паттерн Factory Method https://en.wikipedia.org/wiki/Factory_method_pattern
Также, можно поискать реальные случаи использования Factory, например в Симфони, таким запросом: https://www.google.ru/search?q=site:api.symfony.com+factory&btnG=Поиск&newwindow=1&gbv=1 (понять впрочем будет довольно сложно)
ну и конечно примеры с "машинами", или с кошками, которые наследуются от Животного, они сильно оторваны от реальных примеров кода. На мой взгляд, авторам лучше использовать более реалистичные примеры классов.
ну не все ж такие ебланы
Ява как раз язык, код на котором можно использовать для иллюстрации разных паттернов.
Что касается утверждения "фабрика обязана всегда создавать новый объект (а не возвращать ранее созданный)", тут я не уверен, что это должно быть строго так.
Если у тебя буква не задана в коде, а приходит откуда-то, то заменить $r = $ShapeFactory->getShape('R'); на $r = new RectangleShape(); не получится.
Обоссаные фрилансеры охуели и заряжают неадекватные ценники, поэтому обращаюсь к сосачу.
Деньги готовы платить. Пишите мне на мыло, обсудим morkV;)okovkaANUSgmailaAJPUNCTUMchuFom
>кто за еду возьмется сайт делать?
>Деньги готовы платить.
Чё-т взаимоисключающие параметры.
Олсо, если уж российские фрилансеры для тебя дорого, то я даже не знаю.
>обменник валют
>нет денег на прогера
>нет денег на российского фрилансера
Тебе что, 12 лет и мамка денег на сайт не даёт?
В Слиме контроллеры - это функции которые ты описываешь вместе с роутами ($app->get(роут, контроллер)). По задумке, Слим предназначен для маленьких приложений, где контроллеров мало и они маленькие и прекрасно влезают в один файл.
Однако, можно реализовать и контроллеры в виде отдельных классов. Обрати внимание на этот пункт в мануале: https://www.slimframework.com/docs/objects/router.html#container-resolution
> в модели будут использоваться методы slim.
Тут что-то не так. Это точно модель, а не контроллер? Какие такие методы Слима тебе нужны в модели?
> Мне этот $app в $container засунуть и тащить его через всё приложение в аргументах что бы я мог им в модели воспользоваться?
Нет, ты должен передавать в модель только то, что ей нужно, а не весь контейнер с сервисами и настройками. Ты читал мой урок по DI?
> но многое не понятно, собственно что касается DI
читал мой урок по DI? https://github.com/codedokode/pasta/blob/master/arch/di.md
> и Middleware
Ох, я ведь где-то писал про него. Идея в том, что middleware - это штука, которая выполняется до контроллера (так что она может что-то сделать с запросом) и после (так, что она может что-то сделать с ответом), ну а также она может например вообще не вызывать контроллер и сгенерировать ответ сама. При этом middleware еще оборачивают друг друга, так что запрос проходит через первый middleware, он что-то с ним делает, передает второму, тот передает контроллеру, а ответ передается в обратном порядке.
Они обычно используются для таких целей:
- логгирование
- отладка (например, дописать в конец страницы все выполненные во время ее генерации SQL запросы)
- кеширование страницы целиком
- сжатие страницы gzip (если клиент поддерживает это)
- фильтрация запросов/ограничение доступа (например ограничение числа запросов с 1 IP за секунду или требование решить капчу перед доступом к сайту)
- иногда для защиты от CSRF (у меня есть урок про CSRF)
- модификация ответа (например: найти все ссылки и что-нибудь к ним дописать)
- шифрование кук: перед обработкой запроса пришедшие куки расшифровываются, а после генерации ответа отправляемые куки зашифровываются, браузер видит зашифрованные куки, а код видит куки в открытом виде. То есть мы добавляем шифрование прозрачно для кода, не требуя его переписывания (правда для того, кто разбирается в коде, это может оказаться сюрпризом)
То есть они работают на уровне протокола HTTP, с запросами, ответами, заголовками. Вот например список middleware в фреймворке Джанго: https://docs.djangoproject.com/en/1.11/ref/middleware/
Попробуй например сделать middleware, которое дает доступ к сайту только после решения капчи.
> Загрузка файлов осуществляется с использованием возможностей фреймворка.
Нужно отделить модель и логику сохранения файла от самого Слима.
В Слиме контроллеры - это функции которые ты описываешь вместе с роутами ($app->get(роут, контроллер)). По задумке, Слим предназначен для маленьких приложений, где контроллеров мало и они маленькие и прекрасно влезают в один файл.
Однако, можно реализовать и контроллеры в виде отдельных классов. Обрати внимание на этот пункт в мануале: https://www.slimframework.com/docs/objects/router.html#container-resolution
> в модели будут использоваться методы slim.
Тут что-то не так. Это точно модель, а не контроллер? Какие такие методы Слима тебе нужны в модели?
> Мне этот $app в $container засунуть и тащить его через всё приложение в аргументах что бы я мог им в модели воспользоваться?
Нет, ты должен передавать в модель только то, что ей нужно, а не весь контейнер с сервисами и настройками. Ты читал мой урок по DI?
> но многое не понятно, собственно что касается DI
читал мой урок по DI? https://github.com/codedokode/pasta/blob/master/arch/di.md
> и Middleware
Ох, я ведь где-то писал про него. Идея в том, что middleware - это штука, которая выполняется до контроллера (так что она может что-то сделать с запросом) и после (так, что она может что-то сделать с ответом), ну а также она может например вообще не вызывать контроллер и сгенерировать ответ сама. При этом middleware еще оборачивают друг друга, так что запрос проходит через первый middleware, он что-то с ним делает, передает второму, тот передает контроллеру, а ответ передается в обратном порядке.
Они обычно используются для таких целей:
- логгирование
- отладка (например, дописать в конец страницы все выполненные во время ее генерации SQL запросы)
- кеширование страницы целиком
- сжатие страницы gzip (если клиент поддерживает это)
- фильтрация запросов/ограничение доступа (например ограничение числа запросов с 1 IP за секунду или требование решить капчу перед доступом к сайту)
- иногда для защиты от CSRF (у меня есть урок про CSRF)
- модификация ответа (например: найти все ссылки и что-нибудь к ним дописать)
- шифрование кук: перед обработкой запроса пришедшие куки расшифровываются, а после генерации ответа отправляемые куки зашифровываются, браузер видит зашифрованные куки, а код видит куки в открытом виде. То есть мы добавляем шифрование прозрачно для кода, не требуя его переписывания (правда для того, кто разбирается в коде, это может оказаться сюрпризом)
То есть они работают на уровне протокола HTTP, с запросами, ответами, заголовками. Вот например список middleware в фреймворке Джанго: https://docs.djangoproject.com/en/1.11/ref/middleware/
Попробуй например сделать middleware, которое дает доступ к сайту только после решения капчи.
> Загрузка файлов осуществляется с использованием возможностей фреймворка.
Нужно отделить модель и логику сохранения файла от самого Слима.
Да, если ты не используешь шаблонизатор, только может стоит еще обработку исключений добавить (отменять буферизацию при вылете исключения). Это можно сделать с помощью catch или finally.
Если шаблонизатор, то там обычно есть метод для получения шаблона в виде строки.
За такими пейзажами ехать далеко не надо. Вид почти как в Кудрово, ну может только дома этажей на 5-6 повыше и улица чуть пошире.
Да.
Ну так-то функция поиска яндекс/google тоже на одной странице умещается. Да и дизайна там немного, можно готовый взять.
Олсо, в цене проекта нужно учитывать не только время работы, а ещё и разбор шизы заказчика, отладку, первичные поддержку и обучение, etc. -- иначе ничего хорошего не выйдет.
Ответ на твой вопрос будет сильно различаться, в зависимости от того, в каком сообществе - заказчиков или фрилансеров - спрашивать.
кокойнить грабер котировок, который их пиздит, схороняет в бд, потом показывает на сраничке. нехуевый такой сайтик.
Вряд ли. Сейчас работаю с проектом, который походу писался во времена PHP 4 (в коде var'ы, идентификатор сессии передаётся через GET-параметр, постоянно натыкаюсь на вот такие шедевры: https://pastebin.com/KhEix3UE ) и как-то кайфа мало.
>>1041472
Разве там просто граббер и не нужно работать с API платёжек для обмена электронных валют?
>>1041066
Доки + исходники. Книги нужно по фундаментальным вещам читать.
>>1041350
> Провайдер отдает необходимый результат в ответ на запрос с возможностью кеширования.
Бред какой-то, есть ссылка на GoF?
>>1041450
> четырехглазые, кто за еду
С таким отношением проследуй вон из треда.
>проектом, который походу писался во времена PHP 4 (в коде var'ы, идентификатор сессии передаётся через GET-параметр, постоянно натыкаюсь на вот такие шедевры: https://pastebin.com/KhEix3UE ) и как-то кайфа мало.
Я начну с чего-нибудь опенсурсного на гитхабе плюс пет прожект какой-нибудь. а жс, верстка не особо доставляют. проще говоря мне нравится больше логику сайта делать, нежели рисунки анимировать.
>То есть, тебе стоило бы для начала хотя бы немного изучить теорию про gpg
Я пытался это сделать, то есть я пробежался по википедии, затем зашел в "Введение" библиотеки, в котором нашел ссылку на проект GnuPG, и в котором была ссылка на этот мануал https://www.gnupg.org/documentation/manuals/gnupg/ , в котором ничего не понятно. Мне почему-то не пришло в голову просто загуглить "gpg manual".
Меня одновременно поражает как легко вы сломали эту библиотеку и и глубоко расстраивает что я не смог сделать это самостоятельно. Я даже не знаю почему мне это сразу не далось. Даже если бы я загуглил "gpg manual" я бы не обратил внимания на вторую ссылку т.к. посчитал бы что это мануал о том как обращаться именно с консольной версией.
Невозможность находить ответ в таких неочевидных местах, это делает мне больно.
>> Только почему-то я не вижу методов генерации ключей.
>Это можно сделать утилитой командной строки gpg. Вообще, в самой сишной библиотеке есть функция генерации ключа, но в расширении она не доступна.
Значит нужно будет сгенировать ключ с помощью командной строки... это интересно т.к. открывает простор к ещё большим возможностям. Я ещё не смотрел на php с такой стороны.
Исходя из манаула https://www.gnupg.org/gph/en/manual.html#AEN26
При выполнении команды генерации ключа, происходит диалог в котором устанавливаются опции. Чтобы передать все опции программно нужно несколько раз выполнить exec(...)?
Например:
exec("gpg --gen-key");
exec("1"); //DSA and ElGamal
exec("2048"); //keysize
exec("0"); //key does not expire
exec($id); //user ID
exec($password); //passphrase
$privateKey = exec("gpg --armor --export-secret-key {$id}");
$publicKey = exec("gpg --armor --export {$id}");
Я попытался проверить, и в ответ получил пустую строку. Думаю, это потому что нет доступа для пользователя www-data. Загуглив как дать доступ на определённые команды (linux ubuntu) получил результаты как давать разрешения на папку/файлы и как дать доступ к sudo. Загуглив "linux gpg program directory" получил результаты о том как зашифровывать директории. ??? Попробовал поискать эту директорию на диске, получить несколько папок. Для не опытного linux пользователя мне сложно что-то придумать.
Так сложно ещё никогда не было. Я делаю что-то не так?
>You must provide a user ID in addition to the key parameters. The user ID is used to associate the key being created with a real person.
>
>You need a User-ID to identify your key; the software constructs the user id
>from Real Name, Comment and Email Address in this form:
> "Heinrich Heine (Der Dichter) <heinric|-/hhANUSduessej'!ldorfPUNCTUMd>}Ke>"
>
>Real name:
Что за глупость? Может мне ещё адрес проживания указать?
К тому же, мне не дали указать "Real name" цифрами, но это ничего т.к. можно указать имейл вместо этого. Нужно ещё указать какой-то comment (зачем это нужно?).
Ещё нужно сгенерировать рандомное количество байт делая всякие рандомные вещи (нажимать рандомные клавиши, водить мышкой, удалять что-нибудь с диска), что мне не представляется как сделать это программно.
Я подумал, что если сделать это через консоль так сложно, то может быть получиться найти библиотеку, которая может сгенерировать ключи. Но с этим тоже не так всё просто, единственную библиотеку которая удалось найти это http://pear.php.net/package/Crypt_GPG/ , но она заявляет что не поддерживает генерацию ключей, хотя такой класс есть:
http://pear.php.net/package/Crypt_GPG/docs/latest/Crypt_GPG/Crypt_GPG_KeyGenerator.html
http://pear.php.net/manual/en/package.encryption.crypt-gpg.generate-key.php
>Crypt_GPG does not yet support generating GnuPG keys. Generating a GnuPG key for use with Crypt_GPG is much the same as generating any other GnuPG key on a system.
Как вы думаете, стоит ли воспользоваться этой библиотекой или же есть способ сгенерировать ключи через консоль?
>> Почему ключи должны храниться где-то на диске?
>Потому что так задумано, это программа для одного пользователя, с помощью которой он подписывает и шифрует файлы и переписку.
Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
>Вообще, opengpg
Я тоже путаю OpenPGP и GnuPG
OpenPGP, это стандарт не использующий запатентованные алгоритмы
А GnuPG, это его реализация под лицензией GNU General Public License
>Плюс, я тут подумал, может это позволит как-то интегрироваться с GPG, то есть, например, обмениваться сообщениями с теми, кто использует обычную почту с шифрованием через gpg.
Да, я думал что можно ещё использовать мессенджер как почтовый ящик, но это будет уже совсем другая задача. То что вы не только помогаете мне изучать программирование, но и как бы ещё курируете мои проекты, предлагая идеи которые совпадают с моими... это очень мотивирует.
>То есть, тебе стоило бы для начала хотя бы немного изучить теорию про gpg
Я пытался это сделать, то есть я пробежался по википедии, затем зашел в "Введение" библиотеки, в котором нашел ссылку на проект GnuPG, и в котором была ссылка на этот мануал https://www.gnupg.org/documentation/manuals/gnupg/ , в котором ничего не понятно. Мне почему-то не пришло в голову просто загуглить "gpg manual".
Меня одновременно поражает как легко вы сломали эту библиотеку и и глубоко расстраивает что я не смог сделать это самостоятельно. Я даже не знаю почему мне это сразу не далось. Даже если бы я загуглил "gpg manual" я бы не обратил внимания на вторую ссылку т.к. посчитал бы что это мануал о том как обращаться именно с консольной версией.
Невозможность находить ответ в таких неочевидных местах, это делает мне больно.
>> Только почему-то я не вижу методов генерации ключей.
>Это можно сделать утилитой командной строки gpg. Вообще, в самой сишной библиотеке есть функция генерации ключа, но в расширении она не доступна.
Значит нужно будет сгенировать ключ с помощью командной строки... это интересно т.к. открывает простор к ещё большим возможностям. Я ещё не смотрел на php с такой стороны.
Исходя из манаула https://www.gnupg.org/gph/en/manual.html#AEN26
При выполнении команды генерации ключа, происходит диалог в котором устанавливаются опции. Чтобы передать все опции программно нужно несколько раз выполнить exec(...)?
Например:
exec("gpg --gen-key");
exec("1"); //DSA and ElGamal
exec("2048"); //keysize
exec("0"); //key does not expire
exec($id); //user ID
exec($password); //passphrase
$privateKey = exec("gpg --armor --export-secret-key {$id}");
$publicKey = exec("gpg --armor --export {$id}");
Я попытался проверить, и в ответ получил пустую строку. Думаю, это потому что нет доступа для пользователя www-data. Загуглив как дать доступ на определённые команды (linux ubuntu) получил результаты как давать разрешения на папку/файлы и как дать доступ к sudo. Загуглив "linux gpg program directory" получил результаты о том как зашифровывать директории. ??? Попробовал поискать эту директорию на диске, получить несколько папок. Для не опытного linux пользователя мне сложно что-то придумать.
Так сложно ещё никогда не было. Я делаю что-то не так?
>You must provide a user ID in addition to the key parameters. The user ID is used to associate the key being created with a real person.
>
>You need a User-ID to identify your key; the software constructs the user id
>from Real Name, Comment and Email Address in this form:
> "Heinrich Heine (Der Dichter) <heinric|-/hhANUSduessej'!ldorfPUNCTUMd>}Ke>"
>
>Real name:
Что за глупость? Может мне ещё адрес проживания указать?
К тому же, мне не дали указать "Real name" цифрами, но это ничего т.к. можно указать имейл вместо этого. Нужно ещё указать какой-то comment (зачем это нужно?).
Ещё нужно сгенерировать рандомное количество байт делая всякие рандомные вещи (нажимать рандомные клавиши, водить мышкой, удалять что-нибудь с диска), что мне не представляется как сделать это программно.
Я подумал, что если сделать это через консоль так сложно, то может быть получиться найти библиотеку, которая может сгенерировать ключи. Но с этим тоже не так всё просто, единственную библиотеку которая удалось найти это http://pear.php.net/package/Crypt_GPG/ , но она заявляет что не поддерживает генерацию ключей, хотя такой класс есть:
http://pear.php.net/package/Crypt_GPG/docs/latest/Crypt_GPG/Crypt_GPG_KeyGenerator.html
http://pear.php.net/manual/en/package.encryption.crypt-gpg.generate-key.php
>Crypt_GPG does not yet support generating GnuPG keys. Generating a GnuPG key for use with Crypt_GPG is much the same as generating any other GnuPG key on a system.
Как вы думаете, стоит ли воспользоваться этой библиотекой или же есть способ сгенерировать ключи через консоль?
>> Почему ключи должны храниться где-то на диске?
>Потому что так задумано, это программа для одного пользователя, с помощью которой он подписывает и шифрует файлы и переписку.
Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
>Вообще, opengpg
Я тоже путаю OpenPGP и GnuPG
OpenPGP, это стандарт не использующий запатентованные алгоритмы
А GnuPG, это его реализация под лицензией GNU General Public License
>Плюс, я тут подумал, может это позволит как-то интегрироваться с GPG, то есть, например, обмениваться сообщениями с теми, кто использует обычную почту с шифрованием через gpg.
Да, я думал что можно ещё использовать мессенджер как почтовый ящик, но это будет уже совсем другая задача. То что вы не только помогаете мне изучать программирование, но и как бы ещё курируете мои проекты, предлагая идеи которые совпадают с моими... это очень мотивирует.
Я хотел использовать функцию rewind в коде, но не смог. По ссылке - минимальный код с ошибкой.
Какого чёрта ревайнду нужен аргумент?
Ещё раз, для скептиков:
>public void SplDoublyLinkedList::rewind ( void )
>PHP Warning: rewind() expects exactly 1 parameter
Ничего не смущает?
Давай по другому. Приведу аналогию чтобы было понятнее.
Ты создаёшь переменную, присваиваешь ей число 1. А потом пытаешься получить то, что было до 1?
Это какой-то толстый трлоллинг по моему. Я вот тоже не пойму что он в коде то хочет.
>>1041720
>>1041728
>>1041729
>>1041730
Вы совсем больные? Вы код на идеоне открыли? Я могу сюда запостить его, чтобы вам мышью не кликать лишний раз.
<?php
// your code goes here
$ks=new SplDoublyLinkedList();
$ks.rewind();
?>
И вывод:
PHP Warning: rewind() expects exactly 1 parameter, 0 given in /home/UNfUFN/prog.php on line 6
PHP Recoverable fatal error: Object of class SplDoublyLinkedList could not be converted to string in /home/UNfUFN/prog.php on line 6
На кой ляд ревайнду нужен аргумент, если в мануале написано, что аргументов нет?
Так ты же сам не обьяснил чего хотел то. А потом удивляешься что никто не смог помочь...
Я дал ссылку на код с ошибкой и дал ссылку на метод, который я хочу использовать.
А, ну тогда всё нормально.
>вы все пидорасы от вас никако-ко-ko-го толку, я сам в итоге разобрался.
Молодец.
>По привычке написал . для обращения к методу вместо -> , а в РНР это конкатенация строк, и никто из вас это не увидел.
А я сначала прихуел, а потом смотрю что не дает фатал эрора, и подумал что это новомодная плюха в пхп7.1 или типа того, что бы заменять Pidor::ebatsaVSraku на Pidor.ebatsaVSraku
>Однако, можно реализовать и контроллеры в виде отдельных классов. Обрати внимание на этот пункт в мануале: https://www.slimframework.com/docs/objects/router.html#container-resolution
Кажется я тупой. Как путь к классу прописывать? Почему в доках ни слова про путь? Я уже все возможные пути испробовал, даже этот файл раскидал по всем папкам, может блядь нашёл хотя бы корневую директорию откуда он начинает поиск, но и тут фейл. Полез в гитхаб, стал искать проекты на слиме, нашёл пример в котором использовался данный пример вызова контроллера, но ёбаный врот, там никакой логики в указании пути к контроллеру.
А всё, он использует пространства времён.
Сейчас конечно легко говорить, но тебе стоило бы обратить внимание на второе сообщение: PHP Recoverable fatal error: Object of class SplDoublyLinkedList could not be converted to string in /home/UNfUFN/prog.php on line 6
А так, rewind($fd) это функция изменения позиции чтения/записи в открытом файле.
>>1041724
>>1041732
Ваши комментарии пользы не несут и тут не нужны. Идите упражняться в остроумии в любой другой тред.
Ты смешиваешь разные проблемы. Поиск и загрузка файла с классом - это не задача Слима, он этим не занимается. Этим занимается автозагрузчик, почитай урок https://github.com/codedokode/pasta/blob/master/php/autoload.md
В твоем случае у тебя либо написанный тобой автозагрузчик, либо сгенерированный композером.
Что касается синтаксиса, там в мануале описано, что можно указать в качестве контроллера:
- имя сервиса в контейнере + метод. В этом случае ты можешь задать, как именно будет создаваться объект контроллера, определить его зависимости
- имя класса + метод (Слим создаст объект сам как описано в мануале, передав экземпляр $app в конструктор)
- callable (что это такое, описано в мануале PHP http://php.net/manual/ru/language.types.callable.php)
> Как путь к классу прописывать? Почему в доках ни слова про путь?
Потому что Слим не занимается загрузкой классов и ему неинтересно, где он находится. Он просто создает объект через new.
>
>- имя сервиса в контейнере + метод. В этом случае ты можешь задать, как именно будет создаваться объект контроллера, определить его зависимости
Всё думал как передать зависимости в контроллер.
Так правильно?
Имя плохое для сервиса так как похоже на имя класса и создает путаницу. Назови просто file_download_controller или FileDownloadController.
В конструкторе для view надо указать тайп-хинт.
>Имя плохое для сервиса так как похоже на имя класса и создает путаницу.
Он взял из примера в документации, там именно так и сделали.
Мануал https://httpd.apache.org/docs/2.4/programs/httpd.html
> -k install|config|uninstall
> Install Apache httpd as a Windows NT service; change startup options for the Apache httpd service; and uninstall the Apache httpd service.
Дополнительно https://httpd.apache.org/docs/2.4/platform/windows.html#winsvc
Чтобы Апач добавил себя в список сервисов (сервисы в Windows - это процессы, которые работают в фоновом режиме, могут стартовать при загрузке ОС: https://ru.wikipedia.org/wiki/Службы_Windows )
вроде разобрался, спасибо
Нахуя тебе бэк? Нахуя тебе пхп? Ничему новому ты не научишься, точно также будешь писать вебговнину, оторванную от сути программирования. Выбирай что-нибудь хардкорное, ту же няшечку, раст, или хотя бы го, и вперед, алгоритмы, хуитмы, интересно же.
мимо фронтендер
Чем тебе фронтенд не программирование? Или ты считаешь, что крудопараша на беке - это тру, а двигающиеся картинки на фронте - не тру?
Я вполне допускаю, что даже одностраничный сайт может влететь в копеечку. Если сложный дизайн + мобильный дизайн + дизайн для планшетов. Плюс внутренняя логика.
Если есть сомнения по поводу цены - это нормально. Сами разработчики зачастую ошибаются в сложности и в сроках. Пиши так - требуется разработчик, 500 рублей/час. И работай по часам, в итоге и ты знаешь за что платишь и он не в накладе.
Но опыту скажу, что люди, нанимающие подешевле, они потом это всё переделывают и платят двойную цену. Потому что говноделов на рынке процентов 90, это всякие колымящие студентики, говнофирмы и проч. А они всё делают на скорую руку, в стиле главное завалить, а потом запинаем. Для качества нужно время и деньги.
вебдев
В смысле крудопараша? Ты не понимаешь сути бекэнда и никогда не понимал. Нас никто не заставляет выбирать тот или иной фреймворк, также как и тебя не засталяют использовать ангуляр. На бекэнде есть куча вещей, которых никто не видит - парсинги XML файлов, всякие алгоритмы поиска, пагинации, различные абстракции и многое другое.
Сорян, я бекенд пишу. И всё, что ты описал, вкладывается в слово крудопараша. Парсинг xml ему программирование, а фронтенд нет, я хуею.
Фронтэнд вообще не нужен, через три года не будет никакого фронтэнда. Только HTML+CSS, джаваскрипт не нужен.
Лолшто. Какой апи, куда дёргать, что ты несешь, наркоман?
http://codepad.org/g7q1F03u
Всё с тобой, с наркоманом, ясно. Пездуй!
есть проблема: ответ не совпадает с необходимым, хотя за каждый год добавляется 10% ко вкладу.
У меня выходит 72 года против 49 в учебнике ЧЯДНТ?
Почему \n не работает на компьютере(sublime xampp)? Приходится использовать <br>, в онлайн редакторах работает нормально.
Про генератор так и не понял, верно или нет.
Шифровка.
http://ideone.com/IEUykS
> Про генератор так и не понял, верно или нет.
Не совсем понятно, что тебе нужно. Всё правильно же выводит.
Я не понимаю должно ли и если да, то как сделать чтобы при выводе номера соответствовали слогам по тому порядку, которому они записаны в массиве. Или там смысл просто в рандоме?
Как?
>Выпало число 2, слог дзу
Слог дзу никак не может быть под индексом два или где я сам себе мозги ебу?
Спасибо большое, запомнил теперь про 0. Значит верно.
спасибо, но проблема оказалась в другом. я вместо 10 тысяч на старте прописал 1
Есть, как минимум, десяток.
Лев толстой, но что-то я в начале говно какое-то сделал, а потом только подсказку посмотрел, сейчас буду пытаться сделать по-человечески.
https://ideone.com/WWErg2
Не понимаю подсказку в уроке, совсем тугой, может кто объяснить?
>В каждый элемент массива мы кладем массив вариантов слова или строки, из которого надо сделать выбор: [$word1, $word2, $word3, ["\n"], ...]
То есть, нужно из этих трех массивов $wordi сделать один, а потом как-то, видимо через foreach пройти и взять по рандомному слову. Но не выходит! через слияние получается чушь. http://ideone.com/7QemuM
Извиняюсь, засрал тред. Опять Лев Толстой.
https://ideone.com/0TX8fS
Но опять, как я понимаю это не тот способ, что был в подсказке.
Спасибо. Реально круто!
Идея такая: чтобы не писать руками однотипный код вида:
- выбрать случайно первое слово из первого массива
- выбрать случайно второе слово из второго массива
- ....
Мы вместо этого можем использовать цикл по массиву. То есть мы делаем массив такого вида:
$source = [
[массив вариантов первого слова],
[массив вариантов второго слова],
...
];
И обходим этот массив в цикле. На каждом шаге цикла мы берем массив вариантов N-го слова, выбираем из него одно слово и добавляем в стихотворение.
>>1042240
Давай ты свои негативные эмоции будешь где-нибудь в другом месте изливать?
Также, мне кажется не очень хорошая идея игнорировать часть ошибок с помощью error_reporting (легко убедиться, открыв мануал со списком видов ошибок). Я не понимаю, почему ты не хочешь выводить все ошибки? Не уверен в качестве кода?
Во-первых, операция + не совсем "склеивает" массивы, тебе нужен array_merge, посмотри мануал:
- http://php.net/manual/ru/function.array-merge.php
- http://php.net/manual/ru/language.operators.array.php
> Оператор + возвращает левый массив, к которому был присоединен правый массив. Для ключей, которые существуют в обоих массивах, будут использованы значения из левого массива, а соответствующие им элементы из правого массива будут проигнорированы.
Во-вторых, объединение массивов тут не годится. Ведь в этом случае все слова окажутся в одном массиве и будет непонятно, какие из них куда должны быть вставлены.
Тебе нужен массив из массивов (двухмерный массив): массив, содержащий в качестве элементов массивы из слов.
$source = [
['abc', 'def'],
['ghi', 'jkl'],
...
];
Тогда последний вариант с циклом - самый подходящий и является близким к верному? Как проснусь буду пробовать через merge. Такое слияние через + увидел у котерова и стал ковырять, а до merge буквально пару страниц не дочитал.
Цикл - это для тех, кто не любит копировать один и тот же код несколько раз, а хочет более красивое и универсальное решение. В принципе, можно и по-простому решить задачу, без циклов, если не хочется возиться.
>>1042030
\n работает, если открыть в браузере исходный код страницы (Ctrl + U) то там будет виден перенос строки. Просто по правилам языка разметки HTML перенос строки эквивалентен пробелу.
----
Чтобы переносы строк нормально работали и в браузере и в ideone (и в консоли), можно использовать для этого \n, а в начале программы поставить
header("Content-Type: text/plain; charset=utf-8");
Это заставит браузер воспринимать то, что выводит твоя программа, как обычный текст, а не HTML, и уважать переносы строк в нем (так как в языке HTML перенос строки равносилен пробелу).
Иначе перенос строки будет в исходном коде страницы (его можно увидеть нажав Ctrl + U), но на самой странице его не будет.
Расшифровка сделана неправильно: ты расшифровываешь не зашифрованный текст, а исходный. Надо расшифровывать содержимое $cipher
Также, если сделать это исправление, ты увидишь, что твой шифр не расшифровывает все буквы и часто возвращает неверный результат.
>>1042003
Все верно.
>>1041913
> ([ \-()])*
Здесь круглые скобки не нужны
Второй конечно лучше, но его можно еще упростить - блок с пробелами/скобками/минусами можно оставить один вместо двух.
Это последствия "свободного общения". Как только ты упрощаешь регистрацию и ослабляешь модерацию, на твоем ресурсе начинают скапливаться всякие странные личности. Это кстати и не только в интернете, это и в реальном мире так же происходит, если убирать правила и ограничения или делать маленькое наказание за них.
>>1041725
Что значит "этой функцией занимается JS"? Какой?
Псевдоклассы нужны для применения CSS правил к элементам в определенном состоянии (:hover - при наведении мыши) или в определенном положении (:first-child - первый элемент среди соседних).
Я не знаю точно, когда они появились, но в CSS2 они были, а он был придуман очень давно, лет 10 или 15 назад.
Какой сложный вопрос, даже не знаю как долго ты гуглил перед тем как спросить в треде. Ладно уж, помогу перспективному программисту.
Ну, я где-то что-то читал про pgp, плюс, если изучать разные проекты, то постепенно появляется опыт, который подсказывает, куда надо смотреть.
> Значит нужно будет сгенировать ключ с помощью командной строки... это интересно т.к. открывает простор к ещё большим возможностям. Я ещё не смотрел на php с такой стороны.
Вообще, это не так и хорошо, так как удобнее получать данные вызовом функции, чем запускать внешнюю программу, передавать ей данные, правильно их экрнировать, разбирать ее вывод, проверять код выхода, логгировать ошибки.
> При выполнении команды генерации ключа, происходит диалог в котором устанавливаются опции. Чтобы передать все опции программно нужно несколько раз выполнить exec(...)?
Нет. Каждый новый вызов exec запускает новый процесс. Чтобы передать что-то на вход команде, нужно передать ей данные на стандартный ввод (stdin).
Для начала, стоит разобраться, как работает ввод-вывод в linux. Когда программа хочет прочитать какие-то данные из файла или записать в файл, она открывает файл на запись или чтение (в PHP это делается функцией fopen) с помощью обращения к ядру linux. В случае успеха ядро создает "поток ввода-вывода" (абстракция, в которую можно писать или из которой можно читать данные) и возвращает файловый дескриптор этого потока ввода-вывода - целое число, которое является его идентификатором. Дескрипторы начинаются с нуля, то есть самый первый открытый процессом поток получит дескриптор 0, второй - дескрипитор 1 и так далее. Далее, программа делает чтение или запись вызовом функций fwrite/fread, передавая в них файловый дескриптор. Когда программа закончила операции с файлом, она закрывает (уничтожает) поток вызовом fclose().
В линуксе можно "открыть" на чтение/запись не только обычные файлы, но и так называемые "файлы устройств" (вроде /dev/console, /dev/sda1 или /dev/dsp). Для программы они выглядят как файлы, но чтение/запись в них приводит к передаче данных определенному драйверу устройства в ядре. Ну например, /dev/console соответствует, как следует название, консоли, и чтение/запись с этого устройства приводит к выводу текста в консоль или его вводу с клавиатуры. /dev/sda1 позволяет читать (или перезаписать) содерждимое жесткого диска посекторно, а /dev/dsp раньше позволял передавать данные драйверу звуковой карты и вопроизводить/записывать звук (сейчас это не работает). Есть еще и другие устройства, если интересно:
- https://ru.wikipedia.org/wiki/Специальный_файл_устройства
- http://rus-linux.net/lib.php?name=/MyLDP/proc/proc.htm
- http://gentoo.theserverside.ru/book/secrets_of_dev.html
Кроме /dev, есть еще специальные псевдофайлы в /proc, которые при чтении возвращают информацию о запущенных процессах и настройках ядра.
Так вот, даже если программа не открыла ни один файл явно, предполагается, что ей при запуске родитель передаст 3 "стандартных" дескриптора: 0, 1, 2 - стандартный ввод (stdin), стандартный вывод (stdout) и поток для вывода ошибок/отладочных сообщений (stderr). Когда ты запускаешь что-то в консоли, то эти потоки соответствуют либо устройству /dev/ttyN (в случае запуска в реальной консоли) либо /dev/pts/N (эмулятору терминала в случае запуска в GUI терминале). Соответственно, программе не надо думать, откуда ей вводить и куда выводить данные по умолчанию - она использует стндартные потоки.
Ну например, echo в PHP выводит данные в поток 1 (stdout).
В Линукс при создании нового процесса он наследует список открытых файлов (дескрипторов) от родителя, потому какие-то специальные усилия прилагать для передачи этих дескрипторов не требуется. Если ты используешь GUI терминал, то при запуске программа-терминал создает виртуальную консоль (/dev/ptyN), открывает ее на чтение/запись и запускает оболочку (bash например), передавая ей полученные дескрипторы. Ну и соответственно, все, что запускается из bash, наследует эти стандартные дескрипторы, если ты только в команде не задашь перенаправление ввода-вывода.
Увидеть эти потоки можно, набрав команду ls /proc/self/fd -l . ls - это команда вывода списка файлов, однако здесь мы обращаемся к специальной файловой системе proc, которая предоставляет информацию о процессах в виде файлов, в данном случае /proc/self/fd - это псевдокаталог, который содержит "файлы", каждый из которых соответствует открытому файловому дескриптору текущего процесса (процесса ls). Вот что выводится у меня:
0 -> /dev/pts/1
1 -> /dev/pts/1
2 -> /dev/pts/1
3 -> /proc/8200/fd
Мы видим, что потоки 0-2 привязаны к псевдотерминалу, и есть еще поток 3, это видимо поток, открытый программой ls для каких-то своих целей (наверно, это поток для чтения списка файлов в каталоге - в линукс чтение списка файлов в каталоге тоже похоже по сути на чтение файла).
Соответственно, если ты хочешь, чтобы программа читала данные не с клавиатуры, ты должен перенаправить stdin, передав в качестве него дескриптор какого-то файла, например, так: command < file.txt. Тогда она будет читать данные из файла, а не с клавиатуры: http://xgu.ru/wiki/Стандартные_потоки_ввода/вывода
Вот пример команд, которые заставляют bash запустить программу ls с перенаправлением стандартных потоков:
touch /tmp/1.txt
ls /proc/self/fd -l < /tmp/1.txt 2> /tmp/2.txt
lr-x------ 0 -> /tmp/1.txt
lrwx------ 1 -> /dev/pts/1
l-wx------ 2 -> /tmp/2.txt
lr-x------ 3 -> /proc/8206/fd
Думаю, тут все понятно (кстати, по разрешениям можно увидеть, в каком режиме - на чтение, запись или на то и другое - открыт поток).
То есть ты мог бы использовать перенаправление, чтобы программа читала данные из файла. Но зачем создавать файл? В Линуксе есть более интересная штука под названием "пайп", точнее, даже 2 штуки:
- просто пайп (pipe), или "анонимный пайп", который создается в памяти
- именованный пайп (named pipe) или FIFO, который создается на диске и нам не очень интересен сейчас
Пайп при создании дает 2 дескриптора: в первый можно писать, из второго можно читать (то, что записано в первый). Родительский поток создает пайп, а затем запускает дочерний процесс, передав ему дескриптор чтения из пайпа в качестве stdin. И таким образом, дочерний процесс будет читать то, что запишет в пайп родитель. Более того, пайпы можно использовать и в обратную сторону - чтобы дочерний процесс в них писал, а родитель бы читал.
Пайп создает bash, когда ты используешь символ | . Например, команда
echo "Yes" | cat
Создаст пайп и 2 дочерних процесса (echo и cat). Она передаст один конец пайпа в качестве stdout для echo, а другой - в качестве stdin для cat.
В PHP пайп создать вручную нельзя. Но там есть 2 интересных функции:
- http://php.net/manual/ru/function.popen.php
- http://php.net/manual/ru/function.proc-open.php
Которые позволяют создать дочерний процесс, связанный через пайп(ы) с родителем. proc_open более гибкая - она позволяет явно указать, какие дескрипторы передать дочернему процессу. Используя ее, ты можешь создать процесс, stdin/stderr/stdout которого соединены пайпами с родителем, и ты получаешь полный контроль над вводимыми/выводимыми данными.
Ну, я где-то что-то читал про pgp, плюс, если изучать разные проекты, то постепенно появляется опыт, который подсказывает, куда надо смотреть.
> Значит нужно будет сгенировать ключ с помощью командной строки... это интересно т.к. открывает простор к ещё большим возможностям. Я ещё не смотрел на php с такой стороны.
Вообще, это не так и хорошо, так как удобнее получать данные вызовом функции, чем запускать внешнюю программу, передавать ей данные, правильно их экрнировать, разбирать ее вывод, проверять код выхода, логгировать ошибки.
> При выполнении команды генерации ключа, происходит диалог в котором устанавливаются опции. Чтобы передать все опции программно нужно несколько раз выполнить exec(...)?
Нет. Каждый новый вызов exec запускает новый процесс. Чтобы передать что-то на вход команде, нужно передать ей данные на стандартный ввод (stdin).
Для начала, стоит разобраться, как работает ввод-вывод в linux. Когда программа хочет прочитать какие-то данные из файла или записать в файл, она открывает файл на запись или чтение (в PHP это делается функцией fopen) с помощью обращения к ядру linux. В случае успеха ядро создает "поток ввода-вывода" (абстракция, в которую можно писать или из которой можно читать данные) и возвращает файловый дескриптор этого потока ввода-вывода - целое число, которое является его идентификатором. Дескрипторы начинаются с нуля, то есть самый первый открытый процессом поток получит дескриптор 0, второй - дескрипитор 1 и так далее. Далее, программа делает чтение или запись вызовом функций fwrite/fread, передавая в них файловый дескриптор. Когда программа закончила операции с файлом, она закрывает (уничтожает) поток вызовом fclose().
В линуксе можно "открыть" на чтение/запись не только обычные файлы, но и так называемые "файлы устройств" (вроде /dev/console, /dev/sda1 или /dev/dsp). Для программы они выглядят как файлы, но чтение/запись в них приводит к передаче данных определенному драйверу устройства в ядре. Ну например, /dev/console соответствует, как следует название, консоли, и чтение/запись с этого устройства приводит к выводу текста в консоль или его вводу с клавиатуры. /dev/sda1 позволяет читать (или перезаписать) содерждимое жесткого диска посекторно, а /dev/dsp раньше позволял передавать данные драйверу звуковой карты и вопроизводить/записывать звук (сейчас это не работает). Есть еще и другие устройства, если интересно:
- https://ru.wikipedia.org/wiki/Специальный_файл_устройства
- http://rus-linux.net/lib.php?name=/MyLDP/proc/proc.htm
- http://gentoo.theserverside.ru/book/secrets_of_dev.html
Кроме /dev, есть еще специальные псевдофайлы в /proc, которые при чтении возвращают информацию о запущенных процессах и настройках ядра.
Так вот, даже если программа не открыла ни один файл явно, предполагается, что ей при запуске родитель передаст 3 "стандартных" дескриптора: 0, 1, 2 - стандартный ввод (stdin), стандартный вывод (stdout) и поток для вывода ошибок/отладочных сообщений (stderr). Когда ты запускаешь что-то в консоли, то эти потоки соответствуют либо устройству /dev/ttyN (в случае запуска в реальной консоли) либо /dev/pts/N (эмулятору терминала в случае запуска в GUI терминале). Соответственно, программе не надо думать, откуда ей вводить и куда выводить данные по умолчанию - она использует стндартные потоки.
Ну например, echo в PHP выводит данные в поток 1 (stdout).
В Линукс при создании нового процесса он наследует список открытых файлов (дескрипторов) от родителя, потому какие-то специальные усилия прилагать для передачи этих дескрипторов не требуется. Если ты используешь GUI терминал, то при запуске программа-терминал создает виртуальную консоль (/dev/ptyN), открывает ее на чтение/запись и запускает оболочку (bash например), передавая ей полученные дескрипторы. Ну и соответственно, все, что запускается из bash, наследует эти стандартные дескрипторы, если ты только в команде не задашь перенаправление ввода-вывода.
Увидеть эти потоки можно, набрав команду ls /proc/self/fd -l . ls - это команда вывода списка файлов, однако здесь мы обращаемся к специальной файловой системе proc, которая предоставляет информацию о процессах в виде файлов, в данном случае /proc/self/fd - это псевдокаталог, который содержит "файлы", каждый из которых соответствует открытому файловому дескриптору текущего процесса (процесса ls). Вот что выводится у меня:
0 -> /dev/pts/1
1 -> /dev/pts/1
2 -> /dev/pts/1
3 -> /proc/8200/fd
Мы видим, что потоки 0-2 привязаны к псевдотерминалу, и есть еще поток 3, это видимо поток, открытый программой ls для каких-то своих целей (наверно, это поток для чтения списка файлов в каталоге - в линукс чтение списка файлов в каталоге тоже похоже по сути на чтение файла).
Соответственно, если ты хочешь, чтобы программа читала данные не с клавиатуры, ты должен перенаправить stdin, передав в качестве него дескриптор какого-то файла, например, так: command < file.txt. Тогда она будет читать данные из файла, а не с клавиатуры: http://xgu.ru/wiki/Стандартные_потоки_ввода/вывода
Вот пример команд, которые заставляют bash запустить программу ls с перенаправлением стандартных потоков:
touch /tmp/1.txt
ls /proc/self/fd -l < /tmp/1.txt 2> /tmp/2.txt
lr-x------ 0 -> /tmp/1.txt
lrwx------ 1 -> /dev/pts/1
l-wx------ 2 -> /tmp/2.txt
lr-x------ 3 -> /proc/8206/fd
Думаю, тут все понятно (кстати, по разрешениям можно увидеть, в каком режиме - на чтение, запись или на то и другое - открыт поток).
То есть ты мог бы использовать перенаправление, чтобы программа читала данные из файла. Но зачем создавать файл? В Линуксе есть более интересная штука под названием "пайп", точнее, даже 2 штуки:
- просто пайп (pipe), или "анонимный пайп", который создается в памяти
- именованный пайп (named pipe) или FIFO, который создается на диске и нам не очень интересен сейчас
Пайп при создании дает 2 дескриптора: в первый можно писать, из второго можно читать (то, что записано в первый). Родительский поток создает пайп, а затем запускает дочерний процесс, передав ему дескриптор чтения из пайпа в качестве stdin. И таким образом, дочерний процесс будет читать то, что запишет в пайп родитель. Более того, пайпы можно использовать и в обратную сторону - чтобы дочерний процесс в них писал, а родитель бы читал.
Пайп создает bash, когда ты используешь символ | . Например, команда
echo "Yes" | cat
Создаст пайп и 2 дочерних процесса (echo и cat). Она передаст один конец пайпа в качестве stdout для echo, а другой - в качестве stdin для cat.
В PHP пайп создать вручную нельзя. Но там есть 2 интересных функции:
- http://php.net/manual/ru/function.popen.php
- http://php.net/manual/ru/function.proc-open.php
Которые позволяют создать дочерний процесс, связанный через пайп(ы) с родителем. proc_open более гибкая - она позволяет явно указать, какие дескрипторы передать дочернему процессу. Используя ее, ты можешь создать процесс, stdin/stderr/stdout которого соединены пайпами с родителем, и ты получаешь полный контроль над вводимыми/выводимыми данными.
Правда, работать с этими функциями трудно. Ведь чтение/запись идет асинхронно и тебе надо следить за 3 пайпами, при любой ошибке твоя программа заблокируется, перестанет читать/писать в пайпы, и дочерний процесс так же заблокируется в ожидании готовности пайпа. Я когда-то писал такое, и там придется потратить время на отладку. Лучше использовать обертки вроде Symfony Process, которые берут эти сложности на себя и предоставляют простой интерфейс (впрочем, после опыта с ручным написанием я и в нем нашел пару косяков - например, в некоторых случаях передача данных через пайп идет медленно).
Но тут стоит остановиться и посмотреть, а нет ли блоее простого варианта? Может быть, gnupg просто позволяет указать параметры ключа в опциях командной строки? Гугление дает ответ:
- https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html
- https://gist.github.com/woods/8970150
Вот как-то так можно действовать. Опять же, тут может быть лучше вместо создания файла передавать параметры ключа через stdin, чтобы не забивать диск временными файлами.
Я тут конечно много букв написал, но это в любом случае важно знать, если ты хочешь запускать процессы. Советую поэксперименировать с запуском простых команд linux через Symfony Process, и перехватом их ввода/вывода.
exec() у тебя использован неудачно - ты даже не проверяешь код выхода (exit code) программы. А это надо делать, чтобы отслеживать ошибки при запуске/выполнении дочернего процесса.
В твоем примере ты не перенаправляешь ввод-вывод и процесс gpg унаследует потоки от php. При запуске в консоли он будет пытаться читать данные с консоли, при запуске внутри веб-сервера - читать из того, что сервер поставил себе как stdin.
Вторая команда exec('1') попытается запустить команду '1', это вызовет ошибку, которую ты скорее всего не увидишь, так как не отслеживаешь код выхода и поток ошибок дочернего процесса (разве что в логе ошибок ты увидишь что-то вроде sh: 1: command not found, так как PHP перенаправляет stderr программы в лог ошибок).
> Я попытался проверить, и в ответ получил пустую строку.
Тебе нужно правильно обрабатывать ошибки, чтобы видеть сообщения о них, а не гадать.
> Думаю, это потому что нет доступа для пользователя www-data.
Это вряд ли, так как gpg создает файлы в домашнем каталоге пользователя, который обычно доступен ему на запись. Хотя, кто знает, где у www-data домашний каталог (нужно посмотреть в /etc/passwd).
> Так сложно ещё никогда не было.
Это из-за не очень сильного знания линукса.
> Что за глупость? Может мне ещё адрес проживания указать?
Я думаю, что реальное имя указывать не требуется, ник или числовой id вполне сойдет. Как и email.
> Нужно ещё указать какой-то comment (зачем это нужно?).
Это ведь для почты делалось, чтобы можно было указать имя, ник и адрес email. comment - это, я думаю, ник.
> Ещё нужно сгенерировать рандомное количество байт делая всякие рандомные вещи (нажимать рандомные клавиши, водить мышкой, удалять что-нибудь с диска), что мне не представляется как сделать это программно.
По идее случайные данные должны браться из /dev/random, возможно их там мало и они закончились? тут бы помог аппаратный генератор случайных чисел, но он наверно дорого стоит. В новых серверных процессорах intel такой есть встроенный.
Вот тут вроде похожий случай описан: https://serverfault.com/questions/691120/how-to-generate-gpg-key-without-user-interaction
Там советуют запустить какой-то демон как решение проблемы (в идеале было бы хорошо проверять наличие аппаратного RNG, но наверно это уже проблема настройки сервера, и приложение не должно этим заниматься).
Можно конечно битность ключа понизить. Или посмотреть, может есть какие-то настройки, управляющие сбором случайных данных в ядре линукса.
- https://habrahabr.ru/company/mailru/blog/273147/
- https://www.linux.org.ru/forum/talks/12608246
- https://wiki.archlinux.org/index.php/Random_number_generation
- https://www.8host.com/blog/ispolzovanie-prostogo-demona-entropii-haveged/
> Как вы думаете, стоит ли воспользоваться этой библиотекой или же есть способ сгенерировать ключи через консоль?
По-хорошему, конечно, это должно делаться функцией. Но видимо у них библиотека заточена на сценарий, когда ты управляешь ключаим вручную, а программно только шифруешь/подписываешь данные ключами из хранилища.
Можно конечно посмотреть на алгоритм работы gnupg и попробовать повторить то же на openssl - там ведь есть и генерация ключей, и шифрование, по моему? Можно подсмотреть алгоритмы шифрования из телеграма или signal, или еще какого-то мессенджера.
Главная проблема c созданием своей библиотеки - где гарантия, что ты не допустишь ошибку, которая сделает все шифрование уязвимым? Проекты вроде GnuPG проверяются и анализируются, в отличие от твоего самодельного кода. А в криптографии очень легко допустить ошибку. Если ты занимаешься криптографией, хорошо бы знать хотя бы общие принципы, а также известные атаки, чтобы не оставить уязвимость. Ну или хотя бы использовать проверенный, надежный код.
Плюс, gpg может работать на клиенте, что является дополнительным плюсом.
Я тут погуглил, нашел еще это:
- https://paragonie.com/book/pecl-libsodium
- https://github.com/jedisct1/libsodium-php
Я правда с этой библиотекой не сталкивался и ничего про нее не знаю.
Мне конечно еще интересно, как другие мессенджеры решают проблему - не знаю, сможешь ли ты в них разобраться, ибо у того же signal документация и спецификации довольно хардкорные для чтения.
Вот еще вопросы на SO:
- https://stackoverflow.com/questions/9262109/simplest-two-way-encryption-using-php
И вот еще, может тут есть что-то полезное: https://www.owasp.org/index.php/Guide_to_Cryptography
> Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
Не знаю, можно протестировать, если что, можно удалять оттуда ключ после генерации. Но конечно ключи в Бд хранить... кто-то получит доступ к БД и получит всю переписку.
Правда, работать с этими функциями трудно. Ведь чтение/запись идет асинхронно и тебе надо следить за 3 пайпами, при любой ошибке твоя программа заблокируется, перестанет читать/писать в пайпы, и дочерний процесс так же заблокируется в ожидании готовности пайпа. Я когда-то писал такое, и там придется потратить время на отладку. Лучше использовать обертки вроде Symfony Process, которые берут эти сложности на себя и предоставляют простой интерфейс (впрочем, после опыта с ручным написанием я и в нем нашел пару косяков - например, в некоторых случаях передача данных через пайп идет медленно).
Но тут стоит остановиться и посмотреть, а нет ли блоее простого варианта? Может быть, gnupg просто позволяет указать параметры ключа в опциях командной строки? Гугление дает ответ:
- https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html
- https://gist.github.com/woods/8970150
Вот как-то так можно действовать. Опять же, тут может быть лучше вместо создания файла передавать параметры ключа через stdin, чтобы не забивать диск временными файлами.
Я тут конечно много букв написал, но это в любом случае важно знать, если ты хочешь запускать процессы. Советую поэксперименировать с запуском простых команд linux через Symfony Process, и перехватом их ввода/вывода.
exec() у тебя использован неудачно - ты даже не проверяешь код выхода (exit code) программы. А это надо делать, чтобы отслеживать ошибки при запуске/выполнении дочернего процесса.
В твоем примере ты не перенаправляешь ввод-вывод и процесс gpg унаследует потоки от php. При запуске в консоли он будет пытаться читать данные с консоли, при запуске внутри веб-сервера - читать из того, что сервер поставил себе как stdin.
Вторая команда exec('1') попытается запустить команду '1', это вызовет ошибку, которую ты скорее всего не увидишь, так как не отслеживаешь код выхода и поток ошибок дочернего процесса (разве что в логе ошибок ты увидишь что-то вроде sh: 1: command not found, так как PHP перенаправляет stderr программы в лог ошибок).
> Я попытался проверить, и в ответ получил пустую строку.
Тебе нужно правильно обрабатывать ошибки, чтобы видеть сообщения о них, а не гадать.
> Думаю, это потому что нет доступа для пользователя www-data.
Это вряд ли, так как gpg создает файлы в домашнем каталоге пользователя, который обычно доступен ему на запись. Хотя, кто знает, где у www-data домашний каталог (нужно посмотреть в /etc/passwd).
> Так сложно ещё никогда не было.
Это из-за не очень сильного знания линукса.
> Что за глупость? Может мне ещё адрес проживания указать?
Я думаю, что реальное имя указывать не требуется, ник или числовой id вполне сойдет. Как и email.
> Нужно ещё указать какой-то comment (зачем это нужно?).
Это ведь для почты делалось, чтобы можно было указать имя, ник и адрес email. comment - это, я думаю, ник.
> Ещё нужно сгенерировать рандомное количество байт делая всякие рандомные вещи (нажимать рандомные клавиши, водить мышкой, удалять что-нибудь с диска), что мне не представляется как сделать это программно.
По идее случайные данные должны браться из /dev/random, возможно их там мало и они закончились? тут бы помог аппаратный генератор случайных чисел, но он наверно дорого стоит. В новых серверных процессорах intel такой есть встроенный.
Вот тут вроде похожий случай описан: https://serverfault.com/questions/691120/how-to-generate-gpg-key-without-user-interaction
Там советуют запустить какой-то демон как решение проблемы (в идеале было бы хорошо проверять наличие аппаратного RNG, но наверно это уже проблема настройки сервера, и приложение не должно этим заниматься).
Можно конечно битность ключа понизить. Или посмотреть, может есть какие-то настройки, управляющие сбором случайных данных в ядре линукса.
- https://habrahabr.ru/company/mailru/blog/273147/
- https://www.linux.org.ru/forum/talks/12608246
- https://wiki.archlinux.org/index.php/Random_number_generation
- https://www.8host.com/blog/ispolzovanie-prostogo-demona-entropii-haveged/
> Как вы думаете, стоит ли воспользоваться этой библиотекой или же есть способ сгенерировать ключи через консоль?
По-хорошему, конечно, это должно делаться функцией. Но видимо у них библиотека заточена на сценарий, когда ты управляешь ключаим вручную, а программно только шифруешь/подписываешь данные ключами из хранилища.
Можно конечно посмотреть на алгоритм работы gnupg и попробовать повторить то же на openssl - там ведь есть и генерация ключей, и шифрование, по моему? Можно подсмотреть алгоритмы шифрования из телеграма или signal, или еще какого-то мессенджера.
Главная проблема c созданием своей библиотеки - где гарантия, что ты не допустишь ошибку, которая сделает все шифрование уязвимым? Проекты вроде GnuPG проверяются и анализируются, в отличие от твоего самодельного кода. А в криптографии очень легко допустить ошибку. Если ты занимаешься криптографией, хорошо бы знать хотя бы общие принципы, а также известные атаки, чтобы не оставить уязвимость. Ну или хотя бы использовать проверенный, надежный код.
Плюс, gpg может работать на клиенте, что является дополнительным плюсом.
Я тут погуглил, нашел еще это:
- https://paragonie.com/book/pecl-libsodium
- https://github.com/jedisct1/libsodium-php
Я правда с этой библиотекой не сталкивался и ничего про нее не знаю.
Мне конечно еще интересно, как другие мессенджеры решают проблему - не знаю, сможешь ли ты в них разобраться, ибо у того же signal документация и спецификации довольно хардкорные для чтения.
Вот еще вопросы на SO:
- https://stackoverflow.com/questions/9262109/simplest-two-way-encryption-using-php
И вот еще, может тут есть что-то полезное: https://www.owasp.org/index.php/Guide_to_Cryptography
> Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
Не знаю, можно протестировать, если что, можно удалять оттуда ключ после генерации. Но конечно ключи в Бд хранить... кто-то получит доступ к БД и получит всю переписку.
Я бы написал как-то так (предположим, что у нас единственный пользователь для простоты). Это первый кривой вариант без моделей, сохранения данных локально и прочего.
Начнем с API. Самое простое, что мне пришло в голову - сделать 2 метода: один вызвается в начале, чтобы получить начальный список сообщений, второй дергается по таймеру, чтобы получать новые сообщения, которые мы добавляем в список. При этом в ответе на запрос содержится дата/время, которые надо передать в следующем запросе.
class Api {
// Получить последние N сообщений
function loadLastMessages(limit) { ... } // -> Promise<MessageBatch>
// Получить новые сообщения после since через API
function loadNewMessages(since) { ... } // -> Promise<MessageBatch>
}
// Контроллер - пишем код прямо в него, кто нам запретит
class SomeCtrl {
init() {
api.loadLastMessages(SOME_LIMIT).then(
function (batch) {
that.renderBatch(batch); // вывести полученные сообщения
that.queueRefresh(batch.lastUpdate)
},
this.handleLoadError(...).bind(this)
);
}
queueRefresh(lastUpdate) {
setTimeout(function () { that.loadNewMessages(lastUpdate); }, SOME_TIMEOUT);
}
loadNewMessages(lastUpdate) {
api.loadNewMessages(since).then(
function (batch) {
that.renderBatch(batch);
that.queueRefresh(batch.lastUpdate)
},
this.handleUpdateError(...).bind(this)
);
}
}
При ошибке для начала - можно показывать иконку ошибки, и делать повторный запрос с увеличивающимися интервалами времени. Вообще, тут стоит еще добавить индикацию, чтобы пользователь видел в каком состоянии система, а не смотрел на белый экран и гадал, это сообщения не загрузились или их там и нет.
Заметь, что в моем коде все действия последовательны, выполняются по очереди - мы не запускаем обновление сообщений, пока не получим начальный список. Отсутствие параллельных запросов позволяет избежать проблем с тем, что ответ на второй параллельный запрос может придти раньше, чем на первый.
Теперь попробуем запилить то же, но с моделью. Мы перенесем получение и обновление данных в модель, и используем паттерн observer (взял первое, что пришло в голову):
class MessageListModel {
construct () {
// Событие получения новых сообщений
this.updateEvent = new EventEmitter;
// Событие изменения статуса связи (успешная/идут ошибки)
// Используется для вывода иконок и уведомлений о проблемах
// с соединением с сервером.
this.statusChangeEvent = new EventEmitter;
// Пдробная информация об ошибках со связью
this.errorEvent = new EventEmitter;
// Время последнего обновления
this.lastUpdate = null;
},
// Подписка на события обновления списка сообщений
// обновления будут приходить только после загрузки
// начальных сообщений
subscribeToUpdates(callback) { // -> eventId
this._startUpdateLoop();
return this.updateEvent.subscribe(callback);
},
// отписка
unsubscribeFromUpdates(eventId) {
this.updateEvent.unsubscribe(eventId);
this._stopUpdateLoopIfNoSubscribersExist(); // надеюсь суть понятна из названия
},
// Получить последние N сообщений
getLastMessages(limit) { // -> Promise<MessageBatch>, никогда не реджектится
var result = new Promise(function (resolve, reject) {
this._loadLastMessages(limit, resolve);
});
return result;
},
// Отправляет запрос на получение сообщений
_loadLastMessages(limit, resolver) {
api.getLastMessages(limit).then(
function (batch) {
this.setConnectionStatus(true);
this.lastUpdate = batch.updateTime;
resolve(batch);
},
function (error) {
this.errorEvent.emit(error);
this.setConnectionStatus(false);
setTimeout(function () {
that._loadLastMessages(limit, resolver);
}, RETRY_TIMEOUT);
}
);
},
// Запускает процесс обновления данных
_startUpdateLoop() {
if (loop is started) {
return;
}
this._queueUpdate();
},
_stopUpdateLoopIfNoSubscribersExist() {
...
},
_cancelUpdateTimeout() {
....
},
_queueUpdate() {
this.timeoutId = setTimeout(
function () { that._loadMoreMessages(); },
SOME_TIMEOUT
);
},
_loadMoreMessages() {
if (!this.lastUpdate) {
// Мы еще не получили начальный набор сообщений
this._queueUpdate();
return;
}
api.loadNewMessages(this.lastUpdate).then(
function (batch) {
this._setConnectionStatus(true);
this.updateEvent.emit(batch, false);
this.lastUpdate = batch.updateTime;
this._queueUpdate();
},
function (error) {
this.errorEvent.emit(error);
this._setConnectionStatus(false);
this._queueUpdate();
}
)
}
}
Тут я вижу недостаточек: мы храним время последнего обновления в поле this.lastUpdate, и это затрудняет понимание кода и проверку правильности. Так как трудно понять, в какой момент там хранится какое значение. Можно ли уже его использовать или нет? Из-за этого мы ставим костыль с проверкой его на не-пустоту в _loadMoreMessages(). То есть везде, где оно используется, мы в теории должны лепить такую проверку, иначе есть риск допустить ошибку.
Возможно, ту можно было бы вместо обычной переменной сделать промис, this.initialUpdateTimePromise, который бы резолвился после получения начальных сообщений. И опираться на этот промис. И может быть, код станет логичней. А может и нет. Оставим изучение этого вопроса как упражнение читателю.
Вообще, конечно, лучше передавать переменные через аргументы функций, так будет меньше проблем (но в данном примере у меня это не получилось, и я ввел поле).
Теперь, вынеся работу с данными в модель, мы можем реализовывать разные архитектуры. Мы можем в контроллере подписываться на события этой модели и обрабатыать их, а можем - передать модель во вью и там подписываться на события. В общем, делать как нам удобно. Главное что у нас теперь есть модель, которая представляет собой самообновляющийся список сообщений одного пользователя. Как и положено в MVC (с активной моделью, которая сама сообщает об изменениях).
Кода правда стало чуть больше, но хорошая архитектура не дается бесплатно.
Вот пример использования модели в контроллере. При инициализации мы просто пописываемся на интересующие нас события:
class SomeCtrl {
init() {
this.renderConnectionStatus(STATUS_LOADING);
// Обрабатывать ошибки не требуется, это делает модель
this.msgListModel.getLastMessages(SOME_LIMIT).then(function (batch) {
this.renderInitialMessages(batch);
});
this.msgListModel.onConnectionStatusChange(function (newStatus) {
this.renderConnectionStatus(newStatus);
});
this.msgListModel.subscribeToUpdates(function (batch) {
this.renderNewMessages(batch);
this.deleteOldMessages();
});
}
}
Желательно также во view предусмотреть ограничение числа сообщений и удалять старые по мере добавления новых, чтобы DOM и потребление памяти не росли неограниченно при долгой работе (чем больше узлов DOM, тем медленнее все будет работать).
Я бы написал как-то так (предположим, что у нас единственный пользователь для простоты). Это первый кривой вариант без моделей, сохранения данных локально и прочего.
Начнем с API. Самое простое, что мне пришло в голову - сделать 2 метода: один вызвается в начале, чтобы получить начальный список сообщений, второй дергается по таймеру, чтобы получать новые сообщения, которые мы добавляем в список. При этом в ответе на запрос содержится дата/время, которые надо передать в следующем запросе.
class Api {
// Получить последние N сообщений
function loadLastMessages(limit) { ... } // -> Promise<MessageBatch>
// Получить новые сообщения после since через API
function loadNewMessages(since) { ... } // -> Promise<MessageBatch>
}
// Контроллер - пишем код прямо в него, кто нам запретит
class SomeCtrl {
init() {
api.loadLastMessages(SOME_LIMIT).then(
function (batch) {
that.renderBatch(batch); // вывести полученные сообщения
that.queueRefresh(batch.lastUpdate)
},
this.handleLoadError(...).bind(this)
);
}
queueRefresh(lastUpdate) {
setTimeout(function () { that.loadNewMessages(lastUpdate); }, SOME_TIMEOUT);
}
loadNewMessages(lastUpdate) {
api.loadNewMessages(since).then(
function (batch) {
that.renderBatch(batch);
that.queueRefresh(batch.lastUpdate)
},
this.handleUpdateError(...).bind(this)
);
}
}
При ошибке для начала - можно показывать иконку ошибки, и делать повторный запрос с увеличивающимися интервалами времени. Вообще, тут стоит еще добавить индикацию, чтобы пользователь видел в каком состоянии система, а не смотрел на белый экран и гадал, это сообщения не загрузились или их там и нет.
Заметь, что в моем коде все действия последовательны, выполняются по очереди - мы не запускаем обновление сообщений, пока не получим начальный список. Отсутствие параллельных запросов позволяет избежать проблем с тем, что ответ на второй параллельный запрос может придти раньше, чем на первый.
Теперь попробуем запилить то же, но с моделью. Мы перенесем получение и обновление данных в модель, и используем паттерн observer (взял первое, что пришло в голову):
class MessageListModel {
construct () {
// Событие получения новых сообщений
this.updateEvent = new EventEmitter;
// Событие изменения статуса связи (успешная/идут ошибки)
// Используется для вывода иконок и уведомлений о проблемах
// с соединением с сервером.
this.statusChangeEvent = new EventEmitter;
// Пдробная информация об ошибках со связью
this.errorEvent = new EventEmitter;
// Время последнего обновления
this.lastUpdate = null;
},
// Подписка на события обновления списка сообщений
// обновления будут приходить только после загрузки
// начальных сообщений
subscribeToUpdates(callback) { // -> eventId
this._startUpdateLoop();
return this.updateEvent.subscribe(callback);
},
// отписка
unsubscribeFromUpdates(eventId) {
this.updateEvent.unsubscribe(eventId);
this._stopUpdateLoopIfNoSubscribersExist(); // надеюсь суть понятна из названия
},
// Получить последние N сообщений
getLastMessages(limit) { // -> Promise<MessageBatch>, никогда не реджектится
var result = new Promise(function (resolve, reject) {
this._loadLastMessages(limit, resolve);
});
return result;
},
// Отправляет запрос на получение сообщений
_loadLastMessages(limit, resolver) {
api.getLastMessages(limit).then(
function (batch) {
this.setConnectionStatus(true);
this.lastUpdate = batch.updateTime;
resolve(batch);
},
function (error) {
this.errorEvent.emit(error);
this.setConnectionStatus(false);
setTimeout(function () {
that._loadLastMessages(limit, resolver);
}, RETRY_TIMEOUT);
}
);
},
// Запускает процесс обновления данных
_startUpdateLoop() {
if (loop is started) {
return;
}
this._queueUpdate();
},
_stopUpdateLoopIfNoSubscribersExist() {
...
},
_cancelUpdateTimeout() {
....
},
_queueUpdate() {
this.timeoutId = setTimeout(
function () { that._loadMoreMessages(); },
SOME_TIMEOUT
);
},
_loadMoreMessages() {
if (!this.lastUpdate) {
// Мы еще не получили начальный набор сообщений
this._queueUpdate();
return;
}
api.loadNewMessages(this.lastUpdate).then(
function (batch) {
this._setConnectionStatus(true);
this.updateEvent.emit(batch, false);
this.lastUpdate = batch.updateTime;
this._queueUpdate();
},
function (error) {
this.errorEvent.emit(error);
this._setConnectionStatus(false);
this._queueUpdate();
}
)
}
}
Тут я вижу недостаточек: мы храним время последнего обновления в поле this.lastUpdate, и это затрудняет понимание кода и проверку правильности. Так как трудно понять, в какой момент там хранится какое значение. Можно ли уже его использовать или нет? Из-за этого мы ставим костыль с проверкой его на не-пустоту в _loadMoreMessages(). То есть везде, где оно используется, мы в теории должны лепить такую проверку, иначе есть риск допустить ошибку.
Возможно, ту можно было бы вместо обычной переменной сделать промис, this.initialUpdateTimePromise, который бы резолвился после получения начальных сообщений. И опираться на этот промис. И может быть, код станет логичней. А может и нет. Оставим изучение этого вопроса как упражнение читателю.
Вообще, конечно, лучше передавать переменные через аргументы функций, так будет меньше проблем (но в данном примере у меня это не получилось, и я ввел поле).
Теперь, вынеся работу с данными в модель, мы можем реализовывать разные архитектуры. Мы можем в контроллере подписываться на события этой модели и обрабатыать их, а можем - передать модель во вью и там подписываться на события. В общем, делать как нам удобно. Главное что у нас теперь есть модель, которая представляет собой самообновляющийся список сообщений одного пользователя. Как и положено в MVC (с активной моделью, которая сама сообщает об изменениях).
Кода правда стало чуть больше, но хорошая архитектура не дается бесплатно.
Вот пример использования модели в контроллере. При инициализации мы просто пописываемся на интересующие нас события:
class SomeCtrl {
init() {
this.renderConnectionStatus(STATUS_LOADING);
// Обрабатывать ошибки не требуется, это делает модель
this.msgListModel.getLastMessages(SOME_LIMIT).then(function (batch) {
this.renderInitialMessages(batch);
});
this.msgListModel.onConnectionStatusChange(function (newStatus) {
this.renderConnectionStatus(newStatus);
});
this.msgListModel.subscribeToUpdates(function (batch) {
this.renderNewMessages(batch);
this.deleteOldMessages();
});
}
}
Желательно также во view предусмотреть ограничение числа сообщений и удалять старые по мере добавления новых, чтобы DOM и потребление памяти не росли неограниченно при долгой работе (чем больше узлов DOM, тем медленнее все будет работать).
Вот попробуй разобраться с этими примерами. Их нужно понять полностью, если ты не понимаешь промисы, то изучай статью про написание класса промисов с нуля, если что-то непонятно - задавай вопросы, но пока это не разберешь, дальше двигаться наверно не получится.
Вообще, код довольно топорный, так что тут можно подумать, как его можно улучшить. Ну например, есть такая штука как "реактивное программирование" (FRP). Не пригодится ли оно тут?
- https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 (я что-то такое вроде читал когда-то, но ничего не помню)
- http://reactivex.io/intro.html
- http://reactivex.io/rxjs/
Там в основе лежит такая штука, как Observable. Это абстрактный поток событий, которые происходят асинхронно (то есть мы не можем получить этот поток целиком, но мы можем подписаться на него и нас будут вызывать при приходе события). Пока это напоминает обычный паттерн Observer. Но в реактивном программировании с Observable можно делать различные операции и преобразования, порождая новые Observables.
Более того, вроде есть библиотеки и шаблонизаторы, заточенные на работу с Observables.
Соответственно, наш список сообщений может быть смоделирован с помощью observable - первое событие в нем это начальный список сообщений, а последующие события - это обновления. Мы можем представить его как список блоков сообщений (Observable<MessageBatch>), или список отдельных сообщений (Observable<Message>). Что лучше, я не знаю.
Такая абстракция позволяет нам написать функцию, которая возвращает поток существующих и будущих сообщений и сделать интерфейс модели предельно минималистичным:
// Святые угодники, вы только посмотрите на этот интерфейс
// и сравните с первой версией кода!
class MessageListModel {
getMessagesStream(initalCount) { .. } // -> Observable(Message)
getConnectionStatusStream() { ...} // -> Observable(NetworkStatus)
}
class MessageListCtrl {
init(msgListModel) {
msgListModel.getMessagesStream(INITIAL_COUNT).subscribe(function (message) {
this.appendMessage(message);
// можно this.view.appendMessage, это не принципиально
});
msgListModel.getConnectionStatusStream().subscribe(function (status) {
this.displayNetworkStatus(status);
});
}
}
Это по сути то же самое, что и код выше, просто добавлена лишняя абстракция. Главный момент тут - это MVC. Напомню еще раз, что ошибки надо обрабатывать, индикаторы ожидания пользователю показывать.
Вот тут кстати есть интересный момент. Мы передаем initialCount, значит если мы сделаем 2 вызова:
var stream1 = model.getMessagesStream(10);
var stream2 = model.getMessagesStream(20);
то мы получим 2 независимых потока сообщений. Вопрос: можно ли при этом избежать двойных запросов к API на получение новых сообщений и вместо этого делать один запрос для обоих потоков? Я думаю, что можно, оставим реализацию этого как упражнение читателю.
В общем, разбирайся. Ну и может кому-то еще будет интересно почитать, повысим уровень образованности нашего треда в сравнении с другими.
Тут конечно еще многое можно доработать. Ну например, тут совсем нет хранения сообщений локально, ну ты сначала с этим разберись, а потом посмотрим. Я вообще такие вещи не писал раньше и все это на ходу придумал, так что тут могут быть какие-то косяки.
Там еще не очень удачно сделано, что повторный запрос при ошибке сделан в самой модели. Такие вещи часто будут встречаться в приложении, и это надо как-то абстрагировать и выносить отдельно. Также, нет смысла делать в каждой модели свой getConnectionStatusStream() - лучше сделать какой-то один объект, через который проходят все запросы и который отслеживает состояние сети.
Насчет твоего кода - он подозрительно маленький, мне кажется, там что-то упущено, например, я там не вижу таймера для обновления сообщений. Попробуй его сравнить с моим примером.
Код наверно съедет, потому вот ссылка на нормальный отформатированный пост с подсветкой: https://gist.github.com/codedokode/6dfde1f0ec3d895bc940b67e7919cc29
Вот попробуй разобраться с этими примерами. Их нужно понять полностью, если ты не понимаешь промисы, то изучай статью про написание класса промисов с нуля, если что-то непонятно - задавай вопросы, но пока это не разберешь, дальше двигаться наверно не получится.
Вообще, код довольно топорный, так что тут можно подумать, как его можно улучшить. Ну например, есть такая штука как "реактивное программирование" (FRP). Не пригодится ли оно тут?
- https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 (я что-то такое вроде читал когда-то, но ничего не помню)
- http://reactivex.io/intro.html
- http://reactivex.io/rxjs/
Там в основе лежит такая штука, как Observable. Это абстрактный поток событий, которые происходят асинхронно (то есть мы не можем получить этот поток целиком, но мы можем подписаться на него и нас будут вызывать при приходе события). Пока это напоминает обычный паттерн Observer. Но в реактивном программировании с Observable можно делать различные операции и преобразования, порождая новые Observables.
Более того, вроде есть библиотеки и шаблонизаторы, заточенные на работу с Observables.
Соответственно, наш список сообщений может быть смоделирован с помощью observable - первое событие в нем это начальный список сообщений, а последующие события - это обновления. Мы можем представить его как список блоков сообщений (Observable<MessageBatch>), или список отдельных сообщений (Observable<Message>). Что лучше, я не знаю.
Такая абстракция позволяет нам написать функцию, которая возвращает поток существующих и будущих сообщений и сделать интерфейс модели предельно минималистичным:
// Святые угодники, вы только посмотрите на этот интерфейс
// и сравните с первой версией кода!
class MessageListModel {
getMessagesStream(initalCount) { .. } // -> Observable(Message)
getConnectionStatusStream() { ...} // -> Observable(NetworkStatus)
}
class MessageListCtrl {
init(msgListModel) {
msgListModel.getMessagesStream(INITIAL_COUNT).subscribe(function (message) {
this.appendMessage(message);
// можно this.view.appendMessage, это не принципиально
});
msgListModel.getConnectionStatusStream().subscribe(function (status) {
this.displayNetworkStatus(status);
});
}
}
Это по сути то же самое, что и код выше, просто добавлена лишняя абстракция. Главный момент тут - это MVC. Напомню еще раз, что ошибки надо обрабатывать, индикаторы ожидания пользователю показывать.
Вот тут кстати есть интересный момент. Мы передаем initialCount, значит если мы сделаем 2 вызова:
var stream1 = model.getMessagesStream(10);
var stream2 = model.getMessagesStream(20);
то мы получим 2 независимых потока сообщений. Вопрос: можно ли при этом избежать двойных запросов к API на получение новых сообщений и вместо этого делать один запрос для обоих потоков? Я думаю, что можно, оставим реализацию этого как упражнение читателю.
В общем, разбирайся. Ну и может кому-то еще будет интересно почитать, повысим уровень образованности нашего треда в сравнении с другими.
Тут конечно еще многое можно доработать. Ну например, тут совсем нет хранения сообщений локально, ну ты сначала с этим разберись, а потом посмотрим. Я вообще такие вещи не писал раньше и все это на ходу придумал, так что тут могут быть какие-то косяки.
Там еще не очень удачно сделано, что повторный запрос при ошибке сделан в самой модели. Такие вещи часто будут встречаться в приложении, и это надо как-то абстрагировать и выносить отдельно. Также, нет смысла делать в каждой модели свой getConnectionStatusStream() - лучше сделать какой-то один объект, через который проходят все запросы и который отслеживает состояние сети.
Насчет твоего кода - он подозрительно маленький, мне кажется, там что-то упущено, например, я там не вижу таймера для обновления сообщений. Попробуй его сравнить с моим примером.
Код наверно съедет, потому вот ссылка на нормальный отформатированный пост с подсветкой: https://gist.github.com/codedokode/6dfde1f0ec3d895bc940b67e7919cc29
Можно наверно, но ты будешь получать байты, а не буквы. Так как в utf-8 буква занимает от 1 до 6 байт, а foreach про это не знает. Смысла нет.
Да нельзя блядь, теоретики хуевы. 1 минуту сука потратить на проверку вида: http://ideone.com/ji1SOx или гуглежку http://php.net/manual/ru/control-structures.foreach.php
Хотя знаешь, твой ответ достоин вопроса, норм высер.
<!-- Поле MAX_FILE_SIZE должно быть указано до поля загрузки файла -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Название элемента input определяет имя в массиве $_FILES -->
Отправить этот файл: <input name="userfile" type="file" />
<input type="submit" value="Send File" />
</form>
Поясните как отправить файл на сервер БЕЗ нажатия на кнопку "отправить", а после выбора файла на компе и нажатия кнопки "открыть".
Все примеры что гуглятся идут именно с такой допотопной системной и сабмитом "отправить".
Разбить строку на массив символов (точнее, unicode codepoints) еще можно так
preg_split('//u', $string, null, PREG_SPLLIT_NO_EMPTY);
// соответствует промежутками между символами и разбивает строку на символы
NO_EMPTY убирает 2 пустых элемента, получающихся в начале и конце массива
Помогите с проблемой.
Кароч есть скрипт для CLI. Изначально был сделан для работы в связке с AJAX. AJAX продолжал вызывать скрипт с разными параметрами, пока не было встречено определенное условие. Затем поставили задачу переделать под работу с CRON. CRON подходит в качестве инициатора, но не связующего звена, т.к. может выполняться не чаще раза в минуту, а скрипт должен вызывать сам себя, как только текущая операция завершена.
Сделал следующее:
передал register_shutdown_function коллбэк, в котором делаю дисконнект с БД и затем использую команду passthru, чтобы начать следующую итерацию.
В итоге столкнулся с проблемой "открыто слишком много файлов". Скрипт выполняет под рутом и где-то на 50-ой итерации php крашится, т.к. рут превышает ulimit использования файлов.
Подозреваю, что это вызвано тем, что изначальный скрипт никогда толком не завершается и ждет результата выполнения passthru. По сути, получается рекурсия, которая сжирает ресурсы.
Вопрос: как решить задачу связывания вызовов скрипта и при этом высвобождать ресурсы?
>>1042469
В общем, на freenode ##php порекомендовали в passthru передавать в следующем виде
>nohup php thescript.php >/dev/null 2>&1 &
Таким образом вывод от работы скрипта уходит "в никуда", а сам скрипт запускается в фоновом режиме. инициатор получает пустой вывод от работы вызванной команды и благополучно закрывается.
Не сработает это. У него скрипт тупо превысит лимит выполнения и тупо крашнется или если быть упоротым и пытаться бесконечно выполнять скрипт отключив лимиты в конфигах, то всё равно похоже дело не взлетит. Идеально конечно же дергать пхп аяксом, и получая ответ о завершении дергать опять и так до бесконечности.
Вот нагуглил тему, советую прочитать. https://php.ru/forum/threads/beskonechnyj-skript-kak-sdelat.29629/page-2 Там автор как-то замутил что бы два скрипта дергали сами себя по кругу, мб нашему товарищу это подойдет.
В итоге полагаю что проблема засерания памяти и краша из-за превышения таймаута исчезает.
>Думает что с каждой версией пхп кто-то будет переписывать какие-то фундаментальные вещи.
Ебать ору с дауна.
Дебил? Дебил.
Про расшифровку очень сложно, он проходит по символу например "1185" и дешифрует как "11_8_5, вместо "1_18_5". Как жить то? Логика не ясна
http://ideone.com/a74KGq
Тесты показали, что не падает. Благополучно работает. Вывод в файл сделал. Туда же можно отладочную инфу.
Это все кривые костыли. Во-первых, зачем register_shutdown, когда можно просто вызывать код в цикле? Какое-то переусложнение, не говоря о том, что такое использование resgister_shutdown вообще не предусмотрено.
Если тебе надо выполнять задачи в фоне, то нужен демон вроде gearman https://ruhighload.com/index.php/2010/07/09/gearman-и-php-асинхронные-задачи/
Он вроде даже позволяет отслеживать прогресс выполнения фоновой задачи.
Ну и конечно, надо проверить, что там задачи не теряются даже при ошибках, падениях скрипта и тд.
>Логика не ясна
Ну ты сам сделал такой шифр который можно обратно понимать двусмысленно.
у тебя are превращается в 1185 который можно дешифровать в итоге двумя способами.
Судя по твоему примеру компилятор делает замены в словах с конца, при этом ищет наиболее длинное совпадение.
То есть он видит 1185
и сначала видит что есть 5, для неё есть замена в твоем шифре - e
идет дальше, смотрит есть ли для комбинации из 5 и предудущего символа замена - для 85, такой не находит. Значит решает что 5 - это e
Ок го некст - 8 это h
Но после этого он опять идет проверять дальше, и смотрит что есть замена не просто для 8, а для 18, а вот для 118 уже нету и стало быть решено что 18 меняем на r
Ну и остается только 1, которую никуда не деть кроме как заменить на a
Вот малость подшаманил с твоим кодом, и смотри что получается: http://ideone.com/0q49n2
Всё так и работает. Ищет совпадения начиная с конца а не с начала. В общем у тебя ошибка не в коде, а в логике шифра, что ты допустил двусмысленность.
Исполнение в цикле предполагает серьезное переписывание скрипта.
Gearman тоже рекомендовали. Потом гляну. Спс.
>API из CSS или HTML
А че нельзя чтоле? По сути есть HTML5.2, CSS3, SVG и JS На-ху-й не-ну-жен ибо половину функций уже вхуярили в HTML (В том числе и ваше ебаное API), страшно подумать что в будущем сделают.
Хотя в HTML и современный CSS3 вписывают упрощенки всякие.
Просто замечаю, что при вёрстке с psd-макетов, значения в пикселях из макета зачем-то переводят в em, хотя смысла в этом получается нет и только тратится время.
Родитель тут не при чем. Ты просто выражаешь свои намерения.
Если ты хочешь, чтобы длина была N пикселей - ты пишешь N px. Например, если у тебя в дизайне задумано, что ширина области для картинки 400px - ты пишешь 400px.
Если длина привязана к размеру текста, ты пишешь N em.
Ну например, если ты хочешь, чтобы у заголовка был отступ снизу, строго равный высоте текста заголовка, ты пишешь 1em.
А если ты например хочешь, чтобы ширина блока была равна половине ширины родителя, ты пишешь 50%.
Плюс em в том, что если завтра ты решишь увеличить размер шрифта на сайте, заданные в em размеры элементов изменятся пропорционально. Ну и при чтении видно, что размер элемента привязан к размеру символов.
То есть выбор величины определяется логикой, по которой рассчитывается размер элемента, привязан он к размеру шрифта, еще чему-то или нет. На практике по моему это довольно редко нужно (если не согласны, приведите мне примеры, где это нужно).
А вообще, в верстке очень много неграмотных людей, которые ничего не понимают, а просто бездумно копируют то, что написано в какой-нибудь статье на сайте для фронтендщиков. Когда я смотрю чей-то чужой CSS/JS код, мне хочется разбить лицо рукой. Понабирали вкатывальщиков.
Сделал классическую "считалочку": http://ideone.com/RxqmIo
Чё там происходит: массив с позициями участниками перебирается циклом с двумя счётчиками - один отсчитывает количество слогов, когда они заканчиваются, маркирует позицию крестиком; второй непосредственно "проходится" по массиву и переводит отсчёт на начало строки, когда его значение превышает длину массива. (типа по кругу ёпт...)
В какой-то момент я подумал, что можно задать рекуррентное соотношение, доказать его и тупо вставлять переменные в формулу, но я слишком тупой для этого. Короче, там можно было проще сделать или нет?
Да я заметил, что нельзя, просто это гнойно пиздец. Всё равно строки индексируются, могли бы и сделать.
Сабмитить js-ом
Я вот думаю, это привычка с xml или просто нежелание учиться семантике? Кому тогда драфты хуеву тучу пишут на консорциуме?
БЛЯТЬ КАК Я НЕНАВИЖУ ВИНДУ ЭТА ЕБЛЯ С БУБНОМ ПРИ УСТАНОВКЕ КАЖДОГО ЭКСТЕНШЕНА.
НАДО БУДЕТ ФЛЕШКУ КУПИТЬ И ЗАПИСАТЬ НА НЕГО ОБРАЗ УБУНТЫ СИЛ НЕТ
ребят, подскажите пожалуйста, а как вот на PHP cURL сделать вот такое:
curl -F file=@/path/to/file.txt "http://localhost:5001/ipfs/api/v0/add?recursive=false"
как вот именно -F (--form) бахнуть? Не могу понять что тут получается.
это же POST запрос вроде бы получается такой
Индексируются там байты. Обращаться к элементу строки как к элементу массива можно только когда там однобайтная кодировка и каждый байт совпадает с 1 символом поэтому прокатывает такое:
$a = 'asdfe'
echo $a[3];
//f
На таком ты уже пролетаешь:
$a = 'йцукенг'
echo $a[3];
//нихуя
Уже есть
Лол, какая привычка для типикал вкатывальщика
Ответ на старый вопрос про чат.
> С точки зрения дизайна интерфейса, критично ли будет то, что даже если все сообщения получены, то они всё равно будут прижиматься вниз, оставляя пустое пространство в верху? Я не вижу в этом ничего плохого, но кому-то пустое пространство может показаться не красивым.
Если честно, не очень красиво и выглядит как баг (что там должны быть сообщения, но они не отрисовались).
По твоему CSS - я плохо понимаю логику.
> position: absolute;
> top: 70%;
Почему именно 70%? Не проще ли просто bottom: 0?
Ты ведь вроде делал мои задачи на HTML, значит, ты наверно сможешь решить проблему. Давай начнем с простой задачи (реально простая):
- есть див известной высоты (например, 300px или 100% от высоты страницы)
- в него вложено содержимое неизвестной высоты
- если оно маленькое, оно должно прижиматься к низу контейнера
- если содержимого много, то должна появляться прокрутка
Затем задачу можно усложнить:
- имеется див-контейнер определенной высоты (например, 300px или 100% страницы)
- в нем внизу есть область фиксированной высоты (для отправки сообщений), например 200px (еще более сложная версия: область, высота которой не задана, а определяется содержимым)
- в оставшейся области должны выводиться сообщения как в предыдущей задаче
Я в том сообщении ( https://2ch.hk/pr/res/1000416.html#1007389 (М) ) предложил возможные подходы, нужно просто их перебрать и посмотреть, реально их тут применить или нет. Если вообще никак, то придется использовать вычисление высоты через яваскрипт (с пересчетом при ресайзе окна), но конечно средствами CSS решить эту задачу было бы гораздо лучше (но в реальных задачах иногда приходится прибегать и к яваскрипту).
Также, мне не нравится вот это:
> resize: none;
Зачем пользователя лишать удобства? Это из той же серии, когда запрещают изменение масштаба двумя пальцами на мобильных устройствах (на хабре например), раздражает, когда хочешь увеличить в статье картинку, а нельзя.
Кстати, для прокрутки списка сообщений в конец можно еще использовать интересный метод scrollIntoView: https://developer.mozilla.org/ru/docs/Web/API/Element/scrollIntoView (там написано, что это экспериментальный метод, но это относится только к опциям, без опций код работает даже в ИЕ6. то есть реально везде).
Ответ на старый вопрос про чат.
> С точки зрения дизайна интерфейса, критично ли будет то, что даже если все сообщения получены, то они всё равно будут прижиматься вниз, оставляя пустое пространство в верху? Я не вижу в этом ничего плохого, но кому-то пустое пространство может показаться не красивым.
Если честно, не очень красиво и выглядит как баг (что там должны быть сообщения, но они не отрисовались).
По твоему CSS - я плохо понимаю логику.
> position: absolute;
> top: 70%;
Почему именно 70%? Не проще ли просто bottom: 0?
Ты ведь вроде делал мои задачи на HTML, значит, ты наверно сможешь решить проблему. Давай начнем с простой задачи (реально простая):
- есть див известной высоты (например, 300px или 100% от высоты страницы)
- в него вложено содержимое неизвестной высоты
- если оно маленькое, оно должно прижиматься к низу контейнера
- если содержимого много, то должна появляться прокрутка
Затем задачу можно усложнить:
- имеется див-контейнер определенной высоты (например, 300px или 100% страницы)
- в нем внизу есть область фиксированной высоты (для отправки сообщений), например 200px (еще более сложная версия: область, высота которой не задана, а определяется содержимым)
- в оставшейся области должны выводиться сообщения как в предыдущей задаче
Я в том сообщении ( https://2ch.hk/pr/res/1000416.html#1007389 (М) ) предложил возможные подходы, нужно просто их перебрать и посмотреть, реально их тут применить или нет. Если вообще никак, то придется использовать вычисление высоты через яваскрипт (с пересчетом при ресайзе окна), но конечно средствами CSS решить эту задачу было бы гораздо лучше (но в реальных задачах иногда приходится прибегать и к яваскрипту).
Также, мне не нравится вот это:
> resize: none;
Зачем пользователя лишать удобства? Это из той же серии, когда запрещают изменение масштаба двумя пальцами на мобильных устройствах (на хабре например), раздражает, когда хочешь увеличить в статье картинку, а нельзя.
Кстати, для прокрутки списка сообщений в конец можно еще использовать интересный метод scrollIntoView: https://developer.mozilla.org/ru/docs/Web/API/Element/scrollIntoView (там написано, что это экспериментальный метод, но это относится только к опциям, без опций код работает даже в ИЕ6. то есть реально везде).
>>1025530
> В файлообменнике сущность файл должна агрегировать массив сущностей комментариев? Если да, то класс FileGateway должен агрегировать CommentGateway?
Хороший вопрос.
Логика понятна: если у файла есть комментарии, то логично бы иметь массив комментариев внутри объекта-файла. Но если попытаться воплотить это в реальности, то возникнет проблема: мы теперь должны при загрузке файла загружать все комментарии к нему, даже если они нам не нужны. Хочешь вывести 10 последних файлов - загрузи 1000 комментариев к ним. Очень неэффективно.
То есть наша "идеальная" объектная модель никак не учитывает тот факт, что данные находятся где-то далеко, в базе данных, и их получение не бесплатно. Вот какие есть решения:
1) отказаться от этой идеи и не хранить массив комментариев в файле. Тогда, конечно, нам придется передавать их отдельной переменной, но это небольшая плата за решение проблемы.
2) использовать "ленивую загрузку" и паттерн прокси: помещать в файл не массив комментариев, а прокси-объект, который представляет список комментариев к файлу, но загружает их только при первом обращении к нему:
function getFileById($id) {
...
$data = $pdo->fetch();
$file = new File;
$file->id = $data['id'];
$file->name = $data['name'];
$file->size = $data['size'];
$file->comments = new CommentsCollectionProxy($file->id, $commentsGateway);
return $file;
}
При этом получение комментариев будет выглядеть так:
// метод getAll вызывает обращение к БД и загрузку комментов в прокси-объект
$comments = $file->comments->getAll();
// Возвращает ранее сохраненный масив комментариев
$comments = $file->comments->getAll();
а еще можно делать например так:
// делает запрос SELECT COUNT(...) и сохраняет результат внутри объекта
$count = $file->comments->getCount();
Разумеется, вместо публичного поля мы можем хранить прокси в приватном поле, а в объекте File сделать метод getComments() который будет возвращать комментарии.
Вообще "прокси" называют объект, который подменяет другой объект, перехватывая все вызовы к нему. В нашем случае CommentsCollectionProxy является чем-то вроде прокси для массива комментариев (а не для объекта).
Надеюсь, что код класса прокси ты способен написать и сам.
"ленивую загрузку" можно использовать и в других случаях, например, когда у нас есть "тяжелое" поле, содержащее большой объем данных (текст статьи), и мы бы хотели не загружать его, пока оно не понадобится.
Кстати, если бы ты использовал библиотеку Doctrine, то она сама генерирует прокси-классы для ленивой загрузки (а также генерирует репозитории, чтобы не надо было писать руками TableDataGateway). Когда у тебя десятки сущностей, реализовывать для всех их ленивую загрузку вручную будет долго и утомительно.
> Если да, то класс FileGateway должен агрегировать CommentGateway?
Я бы не назвал это "агрегацией", но да, FileGateway может содержать ссылку на CommentGateway. Агрегация - это отношение вида "целое - часть", но CommentGateway не является частью FileGateway, он просто содержит ссылку на этот объект.
>>1025530
> В файлообменнике сущность файл должна агрегировать массив сущностей комментариев? Если да, то класс FileGateway должен агрегировать CommentGateway?
Хороший вопрос.
Логика понятна: если у файла есть комментарии, то логично бы иметь массив комментариев внутри объекта-файла. Но если попытаться воплотить это в реальности, то возникнет проблема: мы теперь должны при загрузке файла загружать все комментарии к нему, даже если они нам не нужны. Хочешь вывести 10 последних файлов - загрузи 1000 комментариев к ним. Очень неэффективно.
То есть наша "идеальная" объектная модель никак не учитывает тот факт, что данные находятся где-то далеко, в базе данных, и их получение не бесплатно. Вот какие есть решения:
1) отказаться от этой идеи и не хранить массив комментариев в файле. Тогда, конечно, нам придется передавать их отдельной переменной, но это небольшая плата за решение проблемы.
2) использовать "ленивую загрузку" и паттерн прокси: помещать в файл не массив комментариев, а прокси-объект, который представляет список комментариев к файлу, но загружает их только при первом обращении к нему:
function getFileById($id) {
...
$data = $pdo->fetch();
$file = new File;
$file->id = $data['id'];
$file->name = $data['name'];
$file->size = $data['size'];
$file->comments = new CommentsCollectionProxy($file->id, $commentsGateway);
return $file;
}
При этом получение комментариев будет выглядеть так:
// метод getAll вызывает обращение к БД и загрузку комментов в прокси-объект
$comments = $file->comments->getAll();
// Возвращает ранее сохраненный масив комментариев
$comments = $file->comments->getAll();
а еще можно делать например так:
// делает запрос SELECT COUNT(...) и сохраняет результат внутри объекта
$count = $file->comments->getCount();
Разумеется, вместо публичного поля мы можем хранить прокси в приватном поле, а в объекте File сделать метод getComments() который будет возвращать комментарии.
Вообще "прокси" называют объект, который подменяет другой объект, перехватывая все вызовы к нему. В нашем случае CommentsCollectionProxy является чем-то вроде прокси для массива комментариев (а не для объекта).
Надеюсь, что код класса прокси ты способен написать и сам.
"ленивую загрузку" можно использовать и в других случаях, например, когда у нас есть "тяжелое" поле, содержащее большой объем данных (текст статьи), и мы бы хотели не загружать его, пока оно не понадобится.
Кстати, если бы ты использовал библиотеку Doctrine, то она сама генерирует прокси-классы для ленивой загрузки (а также генерирует репозитории, чтобы не надо было писать руками TableDataGateway). Когда у тебя десятки сущностей, реализовывать для всех их ленивую загрузку вручную будет долго и утомительно.
> Если да, то класс FileGateway должен агрегировать CommentGateway?
Я бы не назвал это "агрегацией", но да, FileGateway может содержать ссылку на CommentGateway. Агрегация - это отношение вида "целое - часть", но CommentGateway не является частью FileGateway, он просто содержит ссылку на этот объект.
echo random_int(0, 1) ? -1 : 1;
Почему тернарный оператор возвращает -1 или 1 независимо от того, выпал 0 или 1??
Что блять?
это что-то из учебника, чтобы ты понял приведение типов:
0 == false
1 == true
1 и -1 возвращает не новое слово, которое ты только что выучил, а echo.
и не возвращает, а выводит, но не суть
http://php.net/manual/ru/class.v8js.php
Я имею ввиду грядущую WebAssembly;
Кто-нибудь из гуру, скомпилируйте PHP под выполнение там, когда время будет?
Хочу сделать следующее:
Чтобы было меню статичное и чтобы в этом меню я мог выбрать, например, калькулятор или что-нибудь проще, типа перевода из одной системы единиц в другую, ну вы понели.
Как это делать? Ajax, фреймы?
фреймы уже не модно (но можно),
без них -- ajax, либо, если у тебя немного инфы, можешь прямо на одной странице по клику на пункт меню показывать с помощью js+css нужный блок и скрывать ненужные остальные.
Должен остаться только один ©
я имею ввиду, правильно спозиционируешь в css
олсо, при использовании display:none такие блоки вообще не влияют на остальные
Помогла смена дефолтной директории временного хранения файлов на свою (upload_tmp_dir в php.ini)
Проработал в одной компании 10 лет, разрабатывал мимоходом для них всякое ПО на PHP+Mysql особо навороченные системы которые (одну из которых) ВНЕЗАПНО сейчас стали использовать по всему миру (В 2016 внедрили в Индии, США, Нидерландах, Малайзии ну и в России) Потому что то, что писал я опять же ВНЕЗАПНО, не падает и тупит процентов на 90 меньше всего остального (я у мамы байтоёб). Соответственно сейчас настало время воровать трактор и уплывать на нём в Канаду и я заблаговременно готовлю резюме. А посему вопрос.
Как можно в Резюме упомянуть это ПО, не в даваясь в подробности, вообще можно о таком упоминать? И можно ли рассказывать о функционале ПО? Можно говорить какие технологии использовались? Я просто никогда до этого не прыгал по вакансиям, почти всю жизнь на одном месте и хотелось бы у более опытных людей узнать что к чему. Или лучше написать что так мол и так, разработал такую штуку, но коммерческая тайна не позволяет рассказывать подробности.
непонятно, жалуешься ты или хвастаешься, но вопрос скорее к юристам. Если всё так, чому не переведёшься в канадский офис своей конторы?
И с чего лучше начать - пхп или жс?
Могут трансфернуть через полтора года в Техас или ОАЭ, но я хочу именно в Канаду. А там у нас 1 проект и полтора айтишника.
Относительно проекта, я подписывал документы о неразглашении, но не знаю относится ли это именно к проекту. Ну и могу я тогда в резюме просто рассказать мол разработал такой вот проект но подробностей не скажу ибо коммерческая тайна. Это ведь не будет выглядеть странно?
>насколько он сейчас востребован?
Веб стек на 90% состоит из PHP.
>За что все так ненавидят php?
Рашкинский менталитет. Никто не хочет конкуренции. Не стоит слушать хейт. PHP - это всего-лишь инструмент. Если этим инструментом удобно работать - работай. Если бы PHP был бы хоть на 10% настолько плох как про него говорят, никто-бы его не использовал.
>И с чего лучше начать - пхп или жс?
Это разные как-бы вещи. Если ты во фронтенд, то жс, если бэкэнд, то PHP. Решать тебе.
В первую очередь за его долю в 80% рынка
Вкатывальщики уже в принципе не нужны,
а начинать надо не с языка, а с теории и общих основ. Ну может быть в PHP более понятное ООП и в последних версиях type hinting а-ля Java. Если JavaScript -- то хотя бы с TypeScript.
Ну и попроси трансфер в канадский офис, будешь сидеть с этими полуторами айтишниками?
Изучи, что ты там наподписывал, и проконсультируйся с юристом.
Спасибо анон.
>Вкатывальщики уже в принципе не нужны
Хочешь сказать, что вкатиться в профессию уже нельзя?
>а начинать надо не с языка, а с теории и общих основ.
О чем ты конкретно? Теория и основы рассказываются в учебниках по любому языку.
>>1044247
>Это разные как-бы вещи.
Я понимаю. Под "лучше" я имел в виду "больше вакансий, выше з/п, интереснее работать".
Нет, я не хочу "и рыбку съесть..", просто хочу представлять реальную картину.
Вакансии в основном идут сразу и на то и на то, редко кому-то нужны чисто JS или PHP.
потому что ты решил повыёбываться, очевидно %)
можно, конечно, но будет примерно как в начале нулевых, когда до плебса дошло, что выгодно быть юристом-экономистом, кекус
Осталось совсем чуток блин, все считает идеально правильно, как же я намучился с этой задачей
стрелка/меню/боковая панель
Это значит "сколько останется". Смысл в том, что левая колонка всегда имеет ширину 200px, а правая - сколько останется места за вычетом левой колонки и отступов. Получается 100% ширины окна - 230px.
ладно, спасибо. вроде бы разобрался..
Ага. В "Конкретной математике" приводилась в начале самом.
Бамп вопросу, где он?!
Смысл есть.
Пиксели - абсолютная величина. Что бы ты не делал, 16 пикселей так и останутся 16 пикселями. Задумал сделать шрифт покрупнее - придется вручную менять везде.
Em - относительная величина. Хочешь сделать шрифт покрупнее, просто ставишь допустим body { font-size: 17px; } и у тебя весь сайт преображается. Очень удобно.
> Задумал сделать шрифт покрупнее - придется вручную менять везде.
Вот тут не правильно, на мой взгляд. Почему мы должны менять размеры всех элементов пропорционально шрифту? Такого правила нет. Есть элементы, размеры которых пропорциональны размеру текста, и есть такие, которые не пропорциональны.
> Хочешь сделать шрифт покрупнее, просто ставишь допустим body { font-size: 17px; } и у тебя весь сайт преображается. Очень удобно.
А возможно, все разъедется и будет выглядеть неаккуратно.
Если ты хочешь призвать делать все в em, то это выглядит как нездоровый фанатизм. Вообще, я плохо себе представляю дизайн, в котором можно просто взять и поменять размер шрифта. Такое редко бывает.
>Почему мы должны менять размеры всех элементов пропорционально
Ну потому что.
Шрифт 19px с междустрочным интервалом 30px смотрится хорошо.
При увеличении шрифта до 32px междустрочный интервал нужно увеличивать до 45px (согласно golden ratio https://pearsonified.com/typography/). Иначе параграфы будут выглядеть "сплюснуто".
С em'ами нужно лишь изменить базовый размер. А с пикселями нужно менять в двух местах (font-size и line-height).
>А возможно, все разъедется и будет выглядеть неаккуратно.
У профессионального девелопера ничего не разъезжается. Он умно сочетает различные техники (пиксели, vw, проценты и em'ы). "Разъезжание" элементов типичная ошибка фронтэнд-нубов.
line-height можно (и обычно принято) указывать относительным, вроде 1.4. Я имел в виду другие вещи: отступы, поля, ширину колонок, размеры картинок.
Потому em должен использоваться только для тех элементов, которые привязаны к размеру шрифта в текущем элементе.
> У профессионального девелопера ничего не разъезжается. Он умно сочетает различные техники (пиксели, vw, проценты и em'ы). "Разъезжание" элементов типичная ошибка фронтэнд-нубов.
Это теоретические рассуждения или практический опыт? Разъедется, я не уверен, что я вообще когда-то видел сайт , который бы выдержал заметное изменение размера шрифта без ущерба. Если у тебя картинка размером 200px, она сама по себе до 220px не увеличится например. Наверно, в теории можно было бы продумать такую верстку, которая бы выдерживала изменение размера шрифта, но этим, я думаю, никто не занимается, включая профессионалов, из принципа YAGNI.
Также, если мне не изменяет память, ситуацию усложняет тот факт, что не для всех элементов HTML размер шрифта наследуется (я не уверен, возможно что это исправляется через font-size: inherit). Это относится к элементам форм, таблицам.
Бля, какой ты душный зануда, пиздос. Уже объяснил тебе концепт абсолютных и относительных значений. Em'ы повсюду используют в основном адепты сеток, чтобы добиться равномерности и пропорциональности столбцов/контента, такой контент лучше зумится на различных девайсах, лучше оптимизирован под ретину и проч.
Нельзя однозначно сказать, плохо это или хорошо, это просто такая техника. Как есть объектно-ориентированное программирование, а есть функциональное.
>не уверен, что я вообще когда-то видел сайт , который бы выдержал заметное изменение размера шрифта без ущерба
Так увидь. Ситуации когда нужно плясать от 18px базового на больших экранах, от 16px на средних и от 14px на планшетах не редки. Соответственно, нужно и размер контент менять.
Я не ОП, но у тебя же код обрывается на последней 294 строчке. Программа вообще не запускается из-за синтаксиса.
И я еще не понял, ideone внезапно перестал поддерживать функции вида mb_strlen()? Я так-то в phpstorm сейчас делаю, хотел свой вариант этой задачи выложить, а он ругается на отсутствие функции.
Вот вроде здесь нормально работает
http://sandbox.onlinephpfunctions.com/code/31702a84a5de8daae8b982621e907d9681bfd666
Как я сделал - класс для компании, класс для департамента, создал четыре объекта для каждого департамента, потом отдельно классы для каждой профессии. В каждом объекте департамента я сделал массив с аналогом штатного расписания, в котором перечислены ставки, а потом уже на основании этого массива я заполнил ставки, код получился довольно коротким, если не считать описания классов. Есть идея вообще отказаться от создания отдельных объектов департаментов, а сделать одну большую таблицу примерно как в условии задачи изображено.
А можно махнуть рукой и двинуться дальше :-)
Зачем ты ему отвечаешь? Там какой-то блядь кек а не код. Я глянул, нашел эрор за 5 сек, понял что там долбоеб, который даже не хочет ошибку прочитать и закрыл нахуй. Если бы там реальная какая-то проблема была в решении, то конечно же стоит отвечать и пытаться помочь, а так нахуй таких.
Да ладно тебе, не ругайся с утра.
Лучше объясните по хардкору, какие-то есть отличия в коде при отправке формы по get и post. В случае с get у меня все работает, массив $_GET нормально заполняется, меняю все на метод post и массив возвращается пустой. В мануале сказано мельком про Content-Type application/x-www-form-urlencoded или multipart/form-data. Или вообще не в этом дело?
Вот мой HTML, генерируется редактором, ничего не менял, добавил только форму из мануала:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Title</title>
</head>
<body>
<form action="_tmp.php" method="get">
Имя: <input type="text" name="username" /><br />
Email: <input type="text" name="email" /><br />
<input type="submit" name="submit" value="Отправь меня!" />
</form>
</body>
</html>
Вот скрипт:
<?php
var_dump($_GET);
?>
При замене на post массив пуст.
надеюсь ты method = "get" и $_GET оба на post меняешь?
Нет, ты по моему просто пересказываешь чье-то мнение, сам толком его не понимая. Я пытаюсь объяснить, где ты ошибаешься, но ты уперся и ничего не слышишь, и еще занудой обзываешься.
Сетки делаются обычно с использованием процентов и пикселей (можешь посмотреть код бутстрапа например). На зум и на адаптивность использование em или пикселей никак не влияет. Никакой концепции тут нет, есть случаи, где размер элемента привязан к размеру шрифта, и есть, где не привязан.
>>1044608
В адаптивной версии для маленьких экранов обычно верстка сильно меняется, и изменение размера шрифта тут второстепенная деталь.
>>1044756
Такие комментарии тут не нужны. Не засоряй тред.
>>1044750
И такие наверно тоже.
Бамп, все ещё в поисках хорошей литературы. Пока что прохожу курс из шапки и читаю мануал на php.net.
Данные, из POST формы передаются в теле HTTP запроса и помещаются в $_POST: http://php.net/manual/ru/language.variables.external.php
>>1044743
На ideone при обновлении php забыли mbstring. Я им писал несколько месяцев назад, можешь, если хочешь, тоже написать.
>>1044719
Наверно, хотя зависит от компании. И от того, как ты решишь задачу. За эту задачу с нуля браться точно не стоит, хотя бы студентов сначала надо сделать или аналогичную задачу.
Возможно, лучше было бы, если бы работник сам считал свою зарплату. Ведь у него есть вся необходимая информация для этого. То же касается потребления кофе, производства бумаги и тд.
В принципе, в департаменте это делать можно (то есть используем класс работника просто как хранилище данных, не умеющее само ничего считать), но я не вижу, какая в этом выгода. Например, у тебя нельзя для одного конкретного работника узнать его зарплату (а если бы в нем был метод расчета, то было бы можно). Мне кажется, что расчет в департаменте расчет стоит делать только если в работнике недостаточно информации для расчета и нужны данные департамента.
В классах профессий тоже есть недостаток. У тебя при наследовании надо добавить поля с параметрами профессии, но это никак не документировано и никак не проверяется. Обе эти проблемы можно решить, используя абстрактные методы. Они специально придуманы для таких случаев, когда класс-наследник должен заполнить "недописанные" места в базовом классе. Тогда будет сразу видно, что надо указать при наследовании, и PHP не позволит не указать эти параметры.
Также, этого пока в коде нет, но хочу предупредить, что нельзя в базовом классе обращаться к полям/методам, которые не описаны в текущем классе и появятся только в наследниках. Ну например, в твоем коде методы класса Employee не должны обращаться к полям вроде rate, так как это поле не описано в базовом классе. Так как есть вероятность, что кто-то унаследует класс, но не объявит поле, и будет ошибка.
Далее, поля пишутся до методов, а не после. Рекомендуемый порядок (для удобства чтения кода) такой:
- константы
- публичные поля
- непубличные поля
- конструктор(ы)
- публичные методы
- непубличные методы
Так как если мы хотим использовать класс, нас в первую очередь интересуют методы, которые он предоставляет (публичные).
Поле employeesTable нужно убрать. Это не свойство департамента, а лишь вспомогательное поле, которое используется у тебя для его заполнения. Оно никак не связано с задачами, которые выполняет департамент. Оно не требуется, мы можем заполнить и использовать департамент без него. То есть, если его убрать, Департамент не потеряет ни одной полезной функции. Значит, надо убрать.
Тут еще можно вспомнить принцип разделения ответственности - каждый класс должен выполнять свою задачу. На мой взгляд, задача Департамента - вести учет работников, но начальное наполнение департамента - это не его задача, этой задачей занимается внешний код и незачем ему свои временные данные сохранять в департамент.
Если объяснение выглядит не очень убедительно, можешь задать вопросы или написать возражения, так как сам по себе принцип разделения ответственности довольно важен в ООП и надо в нем разобраться.
Я бы советовал в классе Департамента сделать конструктор с аргументом $name, чтобы показать, что нельзя создать департамент без названия. Аналогично, в классе работника с помощью конструктора можно указать обязательные для его создания данные.
В случае со списком работников или списком департаментов, их удобнее не передавать через конструктор, а добавлять после создания через методы вроде addEmployee(). Ну и это позволит например нанимать новых работников в любой момент.
Также, надо закрыть поля $employees/$departments. Сейчас они открыты и с ними можно делать что угодно, потому лучше их закрыть и сделать методы вроде addEmployee(Employee $e), которые позволяют добавлять в поле только объекты определенного класса.
Вообще, я думаю, тут есть смысл сделать все поля закрытыми. И с помощью методов указать перечень допустимых действий с ними. Это называется инкапсуляция, подробнее:
-----
Инкапсуляция. У этого слова есть разные определения, в том числе такие что ничего не понять, потому объясню простыми словами.
Суть инкапсуляции в том, что класс скрывает (инкапслирует) в себе логику работы с данными и сами данные, а наружу выставляет методы. Пользователю этих методов не важно, как класс устроен внутри, как он хранит данные, ему достаточно вызвать нужный метод чтобы получить результат.
Это упрощает понимание кода: тебе не надо читать и разбирать код класса, достаточно прочитать название метода (и может быть комментарий к нему). Также, это упрощает изменение кода: если какое-то свойство имеет уровень private то доступ к нему возможен только из того же класса и тебе не надо бегать по всему коду и смотреть что там с этим свойством делается, тебе достаточно просмотреть один файл с этим классом.
Как плюс, мы можем поставить какие-то проверки в методах, и запретить установку неправильных значений свойств. Таким образом, снаружи записать неправльное значение в объект будет нельзя и автор класса может гарантировать его корректную работу в любой ситуации.
Инкапсуляция это хорошо. Так как весь код, который занимается одной задачей, оказывается заключен внутри одного класса. Противоположный случай это когда код (или знание о его внутреннем устройстве) вылезает из класса и размазывается по всей программе.
Если проводить аналогии, то можно представить кофе-машину. Ты нажимаешь кнопку (=вызываешь публичный метод) и получаешь кофе (=результат вызова этого метода), при этом ты не видишь что происходит внутри нее и тебе не надо в этом разбираться.
-----
Имена методов принято писать с маленькой буквы. Я не уверен, есть ли это требование в PSR, но вроде все крупные проекты вроде Симфони так делают.
Для расчета среднего и суммы по компании, наверно, стоит добавить методы в компанию.
Чтобы сделать код еще более понятным, около полей с объектами можно подписать их типы, используя синтаксис phpDoc:
/** @var Employee[] Список работников */
private $employees;
Ждем также версию с антикризисными мерами.
Возможно, лучше было бы, если бы работник сам считал свою зарплату. Ведь у него есть вся необходимая информация для этого. То же касается потребления кофе, производства бумаги и тд.
В принципе, в департаменте это делать можно (то есть используем класс работника просто как хранилище данных, не умеющее само ничего считать), но я не вижу, какая в этом выгода. Например, у тебя нельзя для одного конкретного работника узнать его зарплату (а если бы в нем был метод расчета, то было бы можно). Мне кажется, что расчет в департаменте расчет стоит делать только если в работнике недостаточно информации для расчета и нужны данные департамента.
В классах профессий тоже есть недостаток. У тебя при наследовании надо добавить поля с параметрами профессии, но это никак не документировано и никак не проверяется. Обе эти проблемы можно решить, используя абстрактные методы. Они специально придуманы для таких случаев, когда класс-наследник должен заполнить "недописанные" места в базовом классе. Тогда будет сразу видно, что надо указать при наследовании, и PHP не позволит не указать эти параметры.
Также, этого пока в коде нет, но хочу предупредить, что нельзя в базовом классе обращаться к полям/методам, которые не описаны в текущем классе и появятся только в наследниках. Ну например, в твоем коде методы класса Employee не должны обращаться к полям вроде rate, так как это поле не описано в базовом классе. Так как есть вероятность, что кто-то унаследует класс, но не объявит поле, и будет ошибка.
Далее, поля пишутся до методов, а не после. Рекомендуемый порядок (для удобства чтения кода) такой:
- константы
- публичные поля
- непубличные поля
- конструктор(ы)
- публичные методы
- непубличные методы
Так как если мы хотим использовать класс, нас в первую очередь интересуют методы, которые он предоставляет (публичные).
Поле employeesTable нужно убрать. Это не свойство департамента, а лишь вспомогательное поле, которое используется у тебя для его заполнения. Оно никак не связано с задачами, которые выполняет департамент. Оно не требуется, мы можем заполнить и использовать департамент без него. То есть, если его убрать, Департамент не потеряет ни одной полезной функции. Значит, надо убрать.
Тут еще можно вспомнить принцип разделения ответственности - каждый класс должен выполнять свою задачу. На мой взгляд, задача Департамента - вести учет работников, но начальное наполнение департамента - это не его задача, этой задачей занимается внешний код и незачем ему свои временные данные сохранять в департамент.
Если объяснение выглядит не очень убедительно, можешь задать вопросы или написать возражения, так как сам по себе принцип разделения ответственности довольно важен в ООП и надо в нем разобраться.
Я бы советовал в классе Департамента сделать конструктор с аргументом $name, чтобы показать, что нельзя создать департамент без названия. Аналогично, в классе работника с помощью конструктора можно указать обязательные для его создания данные.
В случае со списком работников или списком департаментов, их удобнее не передавать через конструктор, а добавлять после создания через методы вроде addEmployee(). Ну и это позволит например нанимать новых работников в любой момент.
Также, надо закрыть поля $employees/$departments. Сейчас они открыты и с ними можно делать что угодно, потому лучше их закрыть и сделать методы вроде addEmployee(Employee $e), которые позволяют добавлять в поле только объекты определенного класса.
Вообще, я думаю, тут есть смысл сделать все поля закрытыми. И с помощью методов указать перечень допустимых действий с ними. Это называется инкапсуляция, подробнее:
-----
Инкапсуляция. У этого слова есть разные определения, в том числе такие что ничего не понять, потому объясню простыми словами.
Суть инкапсуляции в том, что класс скрывает (инкапслирует) в себе логику работы с данными и сами данные, а наружу выставляет методы. Пользователю этих методов не важно, как класс устроен внутри, как он хранит данные, ему достаточно вызвать нужный метод чтобы получить результат.
Это упрощает понимание кода: тебе не надо читать и разбирать код класса, достаточно прочитать название метода (и может быть комментарий к нему). Также, это упрощает изменение кода: если какое-то свойство имеет уровень private то доступ к нему возможен только из того же класса и тебе не надо бегать по всему коду и смотреть что там с этим свойством делается, тебе достаточно просмотреть один файл с этим классом.
Как плюс, мы можем поставить какие-то проверки в методах, и запретить установку неправильных значений свойств. Таким образом, снаружи записать неправльное значение в объект будет нельзя и автор класса может гарантировать его корректную работу в любой ситуации.
Инкапсуляция это хорошо. Так как весь код, который занимается одной задачей, оказывается заключен внутри одного класса. Противоположный случай это когда код (или знание о его внутреннем устройстве) вылезает из класса и размазывается по всей программе.
Если проводить аналогии, то можно представить кофе-машину. Ты нажимаешь кнопку (=вызываешь публичный метод) и получаешь кофе (=результат вызова этого метода), при этом ты не видишь что происходит внутри нее и тебе не надо в этом разбираться.
-----
Имена методов принято писать с маленькой буквы. Я не уверен, есть ли это требование в PSR, но вроде все крупные проекты вроде Симфони так делают.
Для расчета среднего и суммы по компании, наверно, стоит добавить методы в компанию.
Чтобы сделать код еще более понятным, около полей с объектами можно подписать их типы, используя синтаксис phpDoc:
/** @var Employee[] Список работников */
private $employees;
Ждем также версию с антикризисными мерами.
Вопрос с GET и POST снят, на апаче все корректно работает, проблема, видимо, во встроенном веб-сервере phpstorm, не работает метод post, да и ладно, на данном этапе это не принципиально, буду делать все через get. Может так и задумано.
Написал на ideone, посмотрим, подождем.
В твоем коде синтаксическая ошибка, то есть где-то там скобки неправильно стоят или опечатка какая-нибудь.
Судя по тексту: PHP Parse error: syntax error, unexpected end of file, expecting '{' in /home/Mzm5qE/prog.php on line 294 причина в том, что у тебя в конце идет определение функции и оно обрывается на полуслове.
По коду:
> public $endStavka; // окончательная ставка, вычисляется через функцию EndStavka
Это поле не независимое, так как оно вычисляется через другие поля. Раз так, лучше его убрать и заменить на метод вычисления ставки. Иначе ты должен обновлять это поле при любых изменениях (ранг, базовая ставка, статус босса), это усложняет код и проще просто не хранить это поле вообще.
Код плохо отформатирован, используй phpformatter.com
> $endStavkaB
Название непонятное, что значит буква B непонятно.
В классах профессий тоже есть недостаток. У тебя при наследовании надо добавить поля с параметрами профессии (ставка, кофе и тд), но это никак не документировано и никак не проверяется. Обе эти проблемы можно решить, используя абстрактные методы. Они специально придуманы для таких случаев, когда класс-наследник должен заполнить "недописанные" места в базовом классе. Тогда будет сразу видно, что надо указать при наследовании, и PHP не позволит не указать эти параметры.
Еще, я думаю, что эти поля нужно убрать:
> public $engeenerA; //1 rang
> public $engeenerB; //2 rang
> public $engeenerC; //3 rang
Они используются для заполнения департамента в начале, но почему этот код заполнения должен быть в самом департаменте? Эти поля не нужны для его работы, он может легко работать без них, значит, их надо убрать. Вместо них в департаменте лучше сделать метод addWorker(Worker $w) и добавлять работников через него. Также, кроме метода приема на работу, можно сделать метод увольнения работника, он понадобится в антикризичных мерах.
То есть ты пытаешься возложить на департамент задачу его заполнения и делаешь его негибким, добавляешь в него кучу однотипного кода. В твоем коде нельзя например создать департамент с другим набором работников, так как их число жестко в нем заложено. Лучше сделать универсальный департамент, который изначально пуст и который можно заполнять как угодно.
Соответственно, классы вроде MarketDivision тогда вообще не понадобятся. Департаменты все одинаковы, они ничем не различаются и нет смысла для них делать разные классы. Тут можно спросить: а как же работники? В принципе, их тоже можно сделать одним классом, если хочется, но наверно с наследованием просто проще задавать им параметры.
Наследование нужно там, где у объектов разное поведение (то есть когда например у одной профессии зарплата считается одним методом, а у другой - другим).
Что касается кода заполнения департамента работниками - он сделан методом копипасты и никуда не годится. Вместо этого сделай по-другому: сделай массив с параметрами работников (сколько человек, какого ранга, профессия), обходи этот массив циклом, создавай на каждом шаге работников и добавляй в департамент.
Также, я тебе советую отказаться от публичных полей и исопьзовать закрытые (private/protected) поля, доступ к которым возможен только через методы. Это называется инапсуляция.
----
Инкапсуляция. У этого слова есть разные определения, в том числе такие что ничего не понять, потому объясню простыми словами.
Суть инкапсуляции в том, что класс скрывает (инкапслирует) в себе логику работы с данными и сами данные, а наружу выставляет методы. Пользователю этих методов не важно, как класс устроен внутри, как он хранит данные, ему достаточно вызвать нужный метод чтобы получить результат.
Это упрощает понимание кода: тебе не надо читать и разбирать код класса, достаточно прочитать название метода (и может быть комментарий к нему). Также, это упрощает изменение кода: если какое-то свойство имеет уровень private то доступ к нему возможен только из того же класса и тебе не надо бегать по всему коду и смотреть что там с этим свойством делается, тебе достаточно просмотреть один файл с этим классом.
Как плюс, мы можем поставить какие-то проверки в методах, и запретить установку неправильных значений свойств. Таким образом, снаружи записать неправльное значение в объект будет нельзя и автор класса может гарантировать его корректную работу в любой ситуации.
Инкапсуляция это хорошо. Так как весь код, который занимается одной задачей, оказывается заключен внутри одного класса. Противоположный случай это когда код (или знание о его внутреннем устройстве) вылезает из класса и размазывается по всей программе.
Если проводить аналогии, то можно представить кофе-машину. Ты нажимаешь кнопку (=вызываешь публичный метод) и получаешь кофе (=результат вызова этого метода), при этом ты не видишь что происходит внутри нее и тебе не надо в этом разбираться.
----
В твоем коде синтаксическая ошибка, то есть где-то там скобки неправильно стоят или опечатка какая-нибудь.
Судя по тексту: PHP Parse error: syntax error, unexpected end of file, expecting '{' in /home/Mzm5qE/prog.php on line 294 причина в том, что у тебя в конце идет определение функции и оно обрывается на полуслове.
По коду:
> public $endStavka; // окончательная ставка, вычисляется через функцию EndStavka
Это поле не независимое, так как оно вычисляется через другие поля. Раз так, лучше его убрать и заменить на метод вычисления ставки. Иначе ты должен обновлять это поле при любых изменениях (ранг, базовая ставка, статус босса), это усложняет код и проще просто не хранить это поле вообще.
Код плохо отформатирован, используй phpformatter.com
> $endStavkaB
Название непонятное, что значит буква B непонятно.
В классах профессий тоже есть недостаток. У тебя при наследовании надо добавить поля с параметрами профессии (ставка, кофе и тд), но это никак не документировано и никак не проверяется. Обе эти проблемы можно решить, используя абстрактные методы. Они специально придуманы для таких случаев, когда класс-наследник должен заполнить "недописанные" места в базовом классе. Тогда будет сразу видно, что надо указать при наследовании, и PHP не позволит не указать эти параметры.
Еще, я думаю, что эти поля нужно убрать:
> public $engeenerA; //1 rang
> public $engeenerB; //2 rang
> public $engeenerC; //3 rang
Они используются для заполнения департамента в начале, но почему этот код заполнения должен быть в самом департаменте? Эти поля не нужны для его работы, он может легко работать без них, значит, их надо убрать. Вместо них в департаменте лучше сделать метод addWorker(Worker $w) и добавлять работников через него. Также, кроме метода приема на работу, можно сделать метод увольнения работника, он понадобится в антикризичных мерах.
То есть ты пытаешься возложить на департамент задачу его заполнения и делаешь его негибким, добавляешь в него кучу однотипного кода. В твоем коде нельзя например создать департамент с другим набором работников, так как их число жестко в нем заложено. Лучше сделать универсальный департамент, который изначально пуст и который можно заполнять как угодно.
Соответственно, классы вроде MarketDivision тогда вообще не понадобятся. Департаменты все одинаковы, они ничем не различаются и нет смысла для них делать разные классы. Тут можно спросить: а как же работники? В принципе, их тоже можно сделать одним классом, если хочется, но наверно с наследованием просто проще задавать им параметры.
Наследование нужно там, где у объектов разное поведение (то есть когда например у одной профессии зарплата считается одним методом, а у другой - другим).
Что касается кода заполнения департамента работниками - он сделан методом копипасты и никуда не годится. Вместо этого сделай по-другому: сделай массив с параметрами работников (сколько человек, какого ранга, профессия), обходи этот массив циклом, создавай на каждом шаге работников и добавляй в департамент.
Также, я тебе советую отказаться от публичных полей и исопьзовать закрытые (private/protected) поля, доступ к которым возможен только через методы. Это называется инапсуляция.
----
Инкапсуляция. У этого слова есть разные определения, в том числе такие что ничего не понять, потому объясню простыми словами.
Суть инкапсуляции в том, что класс скрывает (инкапслирует) в себе логику работы с данными и сами данные, а наружу выставляет методы. Пользователю этих методов не важно, как класс устроен внутри, как он хранит данные, ему достаточно вызвать нужный метод чтобы получить результат.
Это упрощает понимание кода: тебе не надо читать и разбирать код класса, достаточно прочитать название метода (и может быть комментарий к нему). Также, это упрощает изменение кода: если какое-то свойство имеет уровень private то доступ к нему возможен только из того же класса и тебе не надо бегать по всему коду и смотреть что там с этим свойством делается, тебе достаточно просмотреть один файл с этим классом.
Как плюс, мы можем поставить какие-то проверки в методах, и запретить установку неправильных значений свойств. Таким образом, снаружи записать неправльное значение в объект будет нельзя и автор класса может гарантировать его корректную работу в любой ситуации.
Инкапсуляция это хорошо. Так как весь код, который занимается одной задачей, оказывается заключен внутри одного класса. Противоположный случай это когда код (или знание о его внутреннем устройстве) вылезает из класса и размазывается по всей программе.
Если проводить аналогии, то можно представить кофе-машину. Ты нажимаешь кнопку (=вызываешь публичный метод) и получаешь кофе (=результат вызова этого метода), при этом ты не видишь что происходит внутри нее и тебе не надо в этом разбираться.
----
Ты уверен, что у тебя в коде нет ошибки? Что-то не верится, что в phpstorm так все плохо.
>>1044351
И еще кое-что. Давай попробуем посмотреть на структуру твоего кода с высоты птичьего полета. Выпишем названия классов, публичных полей и методов в них:
abstract class Worker
- $rang;
- $dicrementcoffe;
- $stavka;
- $mainingDocument;
- $endStavka;
- $boss = false;
- __construct()
- getendStavka()
Что мы тут видим? Во-первых, неаккуратно написанные названия (stavka -> salary/rate, getendStavka -> getEndStavka). Если ты пишешь код прямо на ideone, то советую обзавестись редактором кода вроде Notepad++, Sublime, Eclipse PDT, Netbeans, PHPStorm (платный).
Во-вторых, видна непоследовательность. Для зарплаты у нас есть базовая ставка и метод вычисления конечной ставки. Но для кофе и документов этого нету. Хотя там тоже есть базовая и конечная величина.
Далее, видна избыточность. Для получения конечной ставки есть поле endStavka и метод getendStavka - что мы должны использовать?
Наконец, конструктор класса пустой. Это значит, что для создания объекта не требуется ничего указывать. Но разве имеет смысл создать работника, у которого не указан ранг? Нужно либо задать для ранга значение по умолчанию, либо требовать указать его через конструктор при создании.
То есть если бы я хотел воспользоваться твоим классом, я бы запутался. Как правильно его создавать? Как использовать?
Я тебе советую сначала попробовать спроектировать структуру класса, то есть просто написать, какие в нем должны быть публичные поля и методы, проверить, что все выглядит логично и аккуратно, а потом уже писать их код. Если ты не уверен, можешь запостить структуру класса в тред, я прокомментирую.
Для наглядности, давай я покажу пример правильно спроектированных классов. Допустим, есть гостиница, в ней есть номера. У каждого номера есть порядковый номер (число), вместимость (на сколько человек рассчитан) и цена за сутки. Номера могут бронироваться, на определенный диапазон дат. Нужно спроектировать классы, которые бы моделировали эту ситуацию.
Проектировать можно "сверху вниз" (начиная c гостиницы) или "снизу вверх" (начиная с номеров и броней), как удобнее. Я описываю только публичные поля и методы (так как к приватным все равно нельзя обратиться снаружи). Я сделаю такие классы:
// бронь
// При создании указываем диапазон дат и число гостей
class Reservation
- construct(DateTime $startDate, DateTime $endDate, $guestCount)
- getLength() // считает длительность в днях
- getGuestCount()
- getStartDate()
- getEndDate()
// номер
class Room
// указываем номер, вместимость, цену за сутки
- construct($number, $capacity, $pricePerNight)
- getNumber()
- getCapacity()
- getPricePerNight()
// забронировать номер, не позволяет забронировать номер на те дни,
// на которые уже есть бронь
- addReservation(Reservation $r)
- getReservations() // получить список броней
// подходит ли номер под условия брони, свободен ли он
// в указанные дни?
- canReserve(Reservation $r)
// сколько будет стоить проживание за весь период
- getPriceForReservation(Reservation $r)
// Занят ли номер в этот день?
- isOccupied(DateTime $date)
class Hotel
- construct()
- addRoom(Room $room) // добавить новый номер в отель
- getAllRooms()
- getRoomCount()
- getRoomByNumber($number) // найти номер
// найти все свободные номера, в которые можно
// поселить гостей
// с указанными параметрами
- findVacantRooms(Reservation $r)
// Посчитать число занятых номеров на данную дату
- getOccupiedCount(DateTime $date)
// Посчитать доходы за период времени
- getProfit(DateTime $fromDate, DateTime $endDate)
Класс DateTime - это встроенный в PHP класс для описания дат: http://php.net/manual/ru/class.datetime.php
Я думаю, что глядя на эту структуру, нетрудно понять, как использовать эти классы (если есть желание, можешь потом написать код для них). Ну например, создать гостиницу, заполнить ее номерами, создать несколько броней, вывести информацию по заполненности гостиницы.
Постарайся и свою структуру классов сделать такой же логичной. Если ты чувствуешь, что у тебя пока плохо с проектированием классов, можешь попросить дополнительное задание что-нибудь спроектировать.
Ты уверен, что у тебя в коде нет ошибки? Что-то не верится, что в phpstorm так все плохо.
>>1044351
И еще кое-что. Давай попробуем посмотреть на структуру твоего кода с высоты птичьего полета. Выпишем названия классов, публичных полей и методов в них:
abstract class Worker
- $rang;
- $dicrementcoffe;
- $stavka;
- $mainingDocument;
- $endStavka;
- $boss = false;
- __construct()
- getendStavka()
Что мы тут видим? Во-первых, неаккуратно написанные названия (stavka -> salary/rate, getendStavka -> getEndStavka). Если ты пишешь код прямо на ideone, то советую обзавестись редактором кода вроде Notepad++, Sublime, Eclipse PDT, Netbeans, PHPStorm (платный).
Во-вторых, видна непоследовательность. Для зарплаты у нас есть базовая ставка и метод вычисления конечной ставки. Но для кофе и документов этого нету. Хотя там тоже есть базовая и конечная величина.
Далее, видна избыточность. Для получения конечной ставки есть поле endStavka и метод getendStavka - что мы должны использовать?
Наконец, конструктор класса пустой. Это значит, что для создания объекта не требуется ничего указывать. Но разве имеет смысл создать работника, у которого не указан ранг? Нужно либо задать для ранга значение по умолчанию, либо требовать указать его через конструктор при создании.
То есть если бы я хотел воспользоваться твоим классом, я бы запутался. Как правильно его создавать? Как использовать?
Я тебе советую сначала попробовать спроектировать структуру класса, то есть просто написать, какие в нем должны быть публичные поля и методы, проверить, что все выглядит логично и аккуратно, а потом уже писать их код. Если ты не уверен, можешь запостить структуру класса в тред, я прокомментирую.
Для наглядности, давай я покажу пример правильно спроектированных классов. Допустим, есть гостиница, в ней есть номера. У каждого номера есть порядковый номер (число), вместимость (на сколько человек рассчитан) и цена за сутки. Номера могут бронироваться, на определенный диапазон дат. Нужно спроектировать классы, которые бы моделировали эту ситуацию.
Проектировать можно "сверху вниз" (начиная c гостиницы) или "снизу вверх" (начиная с номеров и броней), как удобнее. Я описываю только публичные поля и методы (так как к приватным все равно нельзя обратиться снаружи). Я сделаю такие классы:
// бронь
// При создании указываем диапазон дат и число гостей
class Reservation
- construct(DateTime $startDate, DateTime $endDate, $guestCount)
- getLength() // считает длительность в днях
- getGuestCount()
- getStartDate()
- getEndDate()
// номер
class Room
// указываем номер, вместимость, цену за сутки
- construct($number, $capacity, $pricePerNight)
- getNumber()
- getCapacity()
- getPricePerNight()
// забронировать номер, не позволяет забронировать номер на те дни,
// на которые уже есть бронь
- addReservation(Reservation $r)
- getReservations() // получить список броней
// подходит ли номер под условия брони, свободен ли он
// в указанные дни?
- canReserve(Reservation $r)
// сколько будет стоить проживание за весь период
- getPriceForReservation(Reservation $r)
// Занят ли номер в этот день?
- isOccupied(DateTime $date)
class Hotel
- construct()
- addRoom(Room $room) // добавить новый номер в отель
- getAllRooms()
- getRoomCount()
- getRoomByNumber($number) // найти номер
// найти все свободные номера, в которые можно
// поселить гостей
// с указанными параметрами
- findVacantRooms(Reservation $r)
// Посчитать число занятых номеров на данную дату
- getOccupiedCount(DateTime $date)
// Посчитать доходы за период времени
- getProfit(DateTime $fromDate, DateTime $endDate)
Класс DateTime - это встроенный в PHP класс для описания дат: http://php.net/manual/ru/class.datetime.php
Я думаю, что глядя на эту структуру, нетрудно понять, как использовать эти классы (если есть желание, можешь потом написать код для них). Ну например, создать гостиницу, заполнить ее номерами, создать несколько броней, вывести информацию по заполненности гостиницы.
Постарайся и свою структуру классов сделать такой же логичной. Если ты чувствуешь, что у тебя пока плохо с проектированием классов, можешь попросить дополнительное задание что-нибудь спроектировать.
Да ладно, я бы не был столь категоричен, для отладки именно php-кода, а я до сегодняшнего дня этим и занимался, встроенная в phpstorm среда выполнения очень удобна и быстра, не надо никуда переключаться в браузер. Как настроить автодеплой на веб-сервер я пока не знаю, буквально не до этого, да и невозможно же все одновременно изучать. На локалке развернут php, в апаче не было необходимости, развернуть - дело нехитрое, сам же понимаешь.
А по поводу метода post в phpstorm - реальный баг, гуглится на раз. Только в силу небольшого опыта я подумал, что это у меня руки кривые, а оно вон оно как, причем люди пишут об этом еще с 10 версии, а сейчас у меня стоит версия 2017, но проблема не устранена, видимо. Да и не проблема это, так-то.
Насчет отсутствия ошибки не уверен, но у других тоже такая ошибка возникает, правда большинство жалующихся отмечают, что они новички, может действительно, что-то надо еще сделать. С другой стороны, все в один голос утверждают, что на обычном сервере - апач, или xamp, например, все работает нормально.
Я не думаю, что в phpstorm все плохо, думаю, что наоборот.
Ты бы мог запускать встроенный в php сервер - он очень просто запускается и не требует настройки Апача:
https://github.com/codedokode/pasta/blob/master/soft/web-server.md#Встроенный-в-php-сервер
http://php.net/manual/ru/features.commandline.webserver.php
Мало чувствителен, на всех php >= 5.2 работает нормально. Исключение 7-ка, там некоторые вещи выпилили, но её редко встретишь.
Бокал шабли этому джентльмену!
То есть учиться сейчас лучше на 5-ой версии? А я сдури понаставил седьмой версии. Кстати, вопрос, связана ли как-то разрядность версии php и apache? То есть если сборка php 5 версии 32-разрядная, то апач тоже только 32-разрядный надо ставить, или 64-разрядный тоже можно? А то у меня при установке какие-то танцы с бубном получились как-то раз, не сразу подружил апач и php, но сейчас уже думаю, что не были установлены последние VC рантаймы.
А тебе не похуй? Рили, зачем тебе знать про версии, ты же все равно и десятой части возможностей даже допотопной четверки не осилишь. Будто бы если я скажу, что лучше учить семерку и ты такой, "о спасибо дружище, мне как раз анонимных классов не хватало, а уж биндинги в замыканиях как надоели! Хорошо, что теперь есть метод call()!".
Если ты хочешь использовать PHP как модуль Апача (mod_php), то они должны быть:
- однаковой архитектуры (битности)
- с одинаковым параметром TS/NTS (thread safe)
- желательно собраны одинаковым комилятором
Что касается VC Runtime, они скачиваются с сайта майкрософт. Проверить, есть ли у тебя нужные библиотеки или нет, можно программой вроде dependency walker. Она показывает наличие или отстутвие нужных программе dll. Этой программой надо проверить исполняемые файлы Апача (httpd.exe) и php.exe. Если там не хватает библиотеки вроде MSVCR90.dll то нужен соответствующий рантайм. Вообще, обычно об этом написано на странице скачивания Апача или PHP, надо внимательно читать ее.
> компилировать из пхп
Зачем? Если так хочется анальных наслаждений, то проще+удобнее+лучше из того же питона компилять.
Или лучше запилить на гитхабе самую простую страничку портфолио и в ней накидать ссылок (с описанием) на несколько говносайтов, но при этом со всеми плюшками?
Второй вариант вроде как лучше.
Промежуточный вариант с учетом замечаний, но не всех, так как возникли вопросы
http://sandbox.onlinephpfunctions.com/code/eba580db69d1172fe803b119dbb4c5c19b084ca1
В компании и департаментах закрыл доступ к переменным, добавил методы add() и get(), реализовано не полностью, не обращай внимания, по сути заглушки, чтобы код не сломать, завтра попробую переписать нормально.
Больше вопрос по классам с работниками и по таблице штатного расписания.
По работникам - может вообще тогда отказаться от отдельных классов - базового Employee и наследников-профессий типа Manager. А сделать один класс, в котором будет содержаться полная информация:
class Employee
{
public $profession;
public $rate;
public $rank;
public $isBoss = false;
public $cofee;
public $reports;
}
И уже при добавлении объекта заносить все данные на основании штатного расписания. В уроке сказано, что кофе и отчеты можно вычислять, но не проще ли их сразу в объекте хранить, а не бегать куда-то в справочник?
И второй вопрос по поводу списка должностей. Есть идея запихнуть все в один большой массив типа
employeesTable = array(
["Департамент закупок", "Manager", 9, 1, 0],
["Департамент закупок", "Manager", 3, 2, 0],
.............
["Департамент продаж", "Manager", 3, 2, 0],
["Департамент рекламы", "Manager", 1, 2, 1],
);
Ну и конечно же хранить это в совершенно отдельной переменной, вынести из класса департамента. И одной функцией заполнять всю структуру, одновременно создавая объекты для сотрудников и для департаментов.
> Бамп, все ещё в поисках хорошей литературы. Пока что прохожу курс из шапки и читаю мануал на php.net.
В шапке устаревшая хуйня дял анимедаунов. Если нужны основы, то на ютубе найди канал ntschool и там видосы по php за июль.
Книги по php тоже устаревшая дрисня. Такой популярный язык, а с книгами беда. Парадокс.
В изучении пхп проблемы не в книгах.
Всё что тебе надо знать о языке есть и в книгах за 2013-15 год. Если ты реально дотошный то можешь всегда актуальные изменения тут читать: http://php.net/manual/ru/migration71.php а не ждать пока тебе книгу напишут.
Другое дело это научиться строить архитектуру и классы правильно в приложении, это уже блядь другая история.
Чет полагаю за познанием грамотного ооп и паттернов вообще придется еще глубже нырять в еще более дальнюю классику. Зандстра в своей книге рекомендует вообще прочитать https://ru.wikipedia.org/wiki/Design_Patterns
Алсо кто тут дрочит на актуальность сиснтаксиса мне вот инетересно, нахуя пикрил ввели?
Я понимаю указывать для функции то, что должно в неё входить. Что бы тем кто с ней будет работать помогать сразу понять что должно быть на вход. Но нахуя указывать что на выходе должно быть? Это для того что бы в коде срать пустыми функциями с указанием что из неё должно выходить и пусть другие за тебя пишут типа?
http://ideone.com/YdFkQ0
Ты акромя пхп ничего не знаешь?
> В изучении пхп проблемы не в книгах.
Ну бля как у такого популярного языка может быть книг меньше (а 99% ещё и старое говно) чем у ...не знаю..у питона, например.
Вот например функция:
function findRoomByNumber(int $number) : Room { }
1) проще понимать код, так как это по сути служит документацией - сразу видно, что нам вернут объект Room
2) позволяет обнаружить ошибку, если новичок по привычке былокодинга попытается вернуть оттуда число или строку, PHP не даст этого сделать (новички почему-то любят такое - возвращать из одной функции данные разных типов, чтобы ей было труднее пользоваться). Или если опытный программист опечатается и попытается вернуть объект не того класса, опять же, это будет обнаружено
Вообще, до этого тип обычно указывали как phpDoc, в комментарии перед функцией, а тут сделали синтаксис для этого.
Плюс, IDE тоже может читать эти типы и когда ты напишешь
$room = findRoom(1);
$room->
она сразу же выдаст подсказки в виде списка методов класса Room. Ну и разумеется, она сможет проверить, что ты правильно используешь полученную переменную.
А как без этого ориентироваться в коде? Как понять, что вернет функция? Ты наверно привык писать небольшие программы, где просто держишь в голове весь код, но в большом по объему коде, который писало много человек, ты его помнить просто не можешь, и не хочется изучать код каждой функции, тратить время, чтобы понять, что она вернет.
Статическая типизация - это хорошо. Если ты пишешь большой проект без типизации, например, на JS, ты будешь просто время тратить на понимание, что вернет та или иная функция, а потом на отладку, когда она вернет что-то не то. Поэтому для JS придумали надъязыки TypeScript и Flow, добавляющие в JS статическую типизацию.
Насчет ООП, я скромно напомню, что в моем учебнике есть простые задания на ООП, а также у меня есть адачи про студентов и тестхаб, где изучается применение ООП в типичных веб-приложениях с таблицами и формами.
>>1045133
Мануал есть официальный, если ты знаком с программированием в общем, то его достаточно. Ну и плюс познакомиться с популярными фреймворками и библиотеками.
Как сделать, чтобы по адресу ххх.ххх.ххх.ххх/project запускался проект (index.php), который лежит в /var/www/project/src/server/public/ ?
прописал в /etc/apache2/sites-available/000-default.conf
<VirtualHost :80>
DocumentRoot /var/www/project/src/server/public/
<Directory /var/www/project/src/server/public/>
...
RewriteRule ^(.)$ index.php [QSA,L]
...
по адресу сервера ххх.ххх.ххх.ххх загружается Apache2 Debian Default Page, по адресу ххх.ххх.ххх.ххх/project или ххх.ххх.ххх.ххх/project/src/server/public/ выдает ошибку с кодом 500
>Допиши код, используя конструкторы, напиши недостающие функции padLeft/padRight, сделай вывод колонки «Всего». Не переусложняй код, там достаточно использовать 2 функции mb_strlen и str_repeat.
>Допиши код, используя конструкторы
Это как, научите?
Про конструкторы, в примере при создании сотрудника добавляется только имя и ставка, часы добавляются отдельной строкой, надо чтобы строка тоже передавалась как параметр в функцию, я оказывается забыл в своем варианте это сделать. А ты сделай :-)
То есть создание сотрудника должно быть в виде
$ivan = new Employee("Иванов Иван", ставка, часов в неделю);
Функции padleft и padright нужны для форматирования таблицы, они должны дополнить строку пробелами до нужной длины, тогда будет формироваться табличка.
То есть было:
test1
test123
а должно быть
.........test1
......test123
примерно так
Подскажите, пожалуйста, есть массив с объектами, у объектов свойство - имя, к примеру. Мне нужно сохранить уникальность этого свойства, то есть при добавлении нового объекта я должен сделать проверку на наличие. Как это правильно делается? У меня пока только идея бежать по всему массиву и в каждом объекте сравнивать это свойство.
Да вопрос в другом был, а именно про конструкторы в множественном числе. Как с ними взаимодействовать если их больше чем 1 в классе?
Так чтоли?
public function __construct($name, $rate, $hours)
{
// задаем имя и часовую ставку
$this->name = $name;
$this->rate = $rate;
$this->hours = $hours;
}
}
$ivan = new Employee("Иванов Иван", 10, $a = array (40, 10, 40, 50));
А я только сейчас понял, почему буква ы выделена, лол.
Посмотри лог ошибок Апача (/var/log/apache2/error.log или как-то так) и там причину ошибки.
Также, почему у тебя Rewriet Rule внутри блока Directory?
Имеется в виду "конструкторы" как концепция, то есть я не имел в виду, что их должно быть несколько. Разумеется, обычный конструктор в классе может быть только один (есть еще такая вещь как статические конструкторы, ты сможешь их написать, если изучишь статические методы).
>>1045223
Вместо $a = array(....) правильно писать просто array(...). Ты создаешь лишнюю переменную, которая никак не используется.
это ок?
Нет, это приведет к ошибкам, если в массиве есть повторяющиеся элементы. Надо брать ключ (который вернет array_rand) и по ключу находить элемент, если ты не знаешь как, то посмотри заново урок про массивы.
>В Линукс при создании нового процесса он наследует список открытых файлов (дескрипторов) от родителя
На примере апача и php
Апач наследует дескрипторы от какого-то "файла устройств" (или ещё какого-нибудь абстрактного родителя, неважно на самом деле интересно, но пусть это останется на самостоятельное изучение)
А php наследует дескрипторы Апача
Соответственно, если php-скрипт выполняется через консоль, то php наследует дескрипторы от неё
Я правильно понимаю?
>>1042319
>> Я попытался проверить, и в ответ получил пустую строку.
>Тебе нужно правильно обрабатывать ошибки, чтобы видеть сообщения о них, а не гадать.
При тестировании функции proc_open, я получил в stderr такую ошибку:
gpg: fatal: can't create directory `/var/www/.gnupg': Permission denied
secmem usage: 0/0 bytes in 0/0 blocks of pool 0/65536
Затем я поменял домашний каталог у www-data в /etc/passwd на каталог сервера (на который я вроде давал неограниченный доступ chmod -R 777) и всё заработало - ключ успешно сгенерировался.
Наверно следовало давать доступ на чтение/запись только для php.
Почему-то в stderr передается обычный вывод, который не должен быть ошибкой:
...+++++
.+++++
............+++++
+++++
gpg: key 447224CD marked as ultimately trusted
gpg: done
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 5 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 5u
Я не вижу в этом проблемы, за исключением того что это засоряет логи, просто это странно. Наверно это просто особенность gpg.
>> Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
>Не знаю, можно протестировать, если что, можно удалять оттуда ключ после генерации. Но конечно ключи в Бд хранить... кто-то получит доступ к БД и получит всю переписку.
Расшифровать сообщения можно будет только зная секретную фразу, которой будет являться пароль пользователя.
>Symfony Process
Для генерации ключей, очевидно, нужно запускать процесс асинхронно, потому что, у меня на виртуальной машине, уходит на это несколько секунд используя кстати демон haveged для энтропии. А что насчет получения ключей? На это не должно уходить много времени. Стоит ли здесь выполнять процесс асинхронно?
>В Линукс при создании нового процесса он наследует список открытых файлов (дескрипторов) от родителя
На примере апача и php
Апач наследует дескрипторы от какого-то "файла устройств" (или ещё какого-нибудь абстрактного родителя, неважно на самом деле интересно, но пусть это останется на самостоятельное изучение)
А php наследует дескрипторы Апача
Соответственно, если php-скрипт выполняется через консоль, то php наследует дескрипторы от неё
Я правильно понимаю?
>>1042319
>> Я попытался проверить, и в ответ получил пустую строку.
>Тебе нужно правильно обрабатывать ошибки, чтобы видеть сообщения о них, а не гадать.
При тестировании функции proc_open, я получил в stderr такую ошибку:
gpg: fatal: can't create directory `/var/www/.gnupg': Permission denied
secmem usage: 0/0 bytes in 0/0 blocks of pool 0/65536
Затем я поменял домашний каталог у www-data в /etc/passwd на каталог сервера (на который я вроде давал неограниченный доступ chmod -R 777) и всё заработало - ключ успешно сгенерировался.
Наверно следовало давать доступ на чтение/запись только для php.
Почему-то в stderr передается обычный вывод, который не должен быть ошибкой:
...+++++
.+++++
............+++++
+++++
gpg: key 447224CD marked as ultimately trusted
gpg: done
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 5 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 5u
Я не вижу в этом проблемы, за исключением того что это засоряет логи, просто это странно. Наверно это просто особенность gpg.
>> Я планировал хранить ключи в БД. Надеюсь не возникнет проблем если в keyring окажется их слишком много.
>Не знаю, можно протестировать, если что, можно удалять оттуда ключ после генерации. Но конечно ключи в Бд хранить... кто-то получит доступ к БД и получит всю переписку.
Расшифровать сообщения можно будет только зная секретную фразу, которой будет являться пароль пользователя.
>Symfony Process
Для генерации ключей, очевидно, нужно запускать процесс асинхронно, потому что, у меня на виртуальной машине, уходит на это несколько секунд используя кстати демон haveged для энтропии. А что насчет получения ключей? На это не должно уходить много времени. Стоит ли здесь выполнять процесс асинхронно?
Блэть, затем.
Введи константу а-ля BASE_URL и храни там путь
Это не просто документация, а проверка типа на выходе прямо в рантайме, очень удобно.
Я бы хотел услышать ответ на твой вопрос, но тут, к сожалению, никто скорее всего тебе не ответит. Вот бы конфу какую-нибудь, где можно за жизнь поговорить и совета спросить.
А как ваши дела?
Изучи вёрстку и жс на среднем уровне. Это сейчас легко. Bootstrap, AMP, Vue.js
И ебашь сайты васянам. По пыхе работа в основном на вордпрессе, но там тоже уметь верстать надо.
А как?
ты пытаешься не приведенного ванным юзером дать самому себе права. Зайди под рутом.
База на фри хостинге и они дали мне только мой логин. Может быть я что то не понимаю, не кидайся говном плс
Изучи их тарифные планы, на бесплатном тарифе скорее всего доступна только одна база.
Для учебы лучше развернуть сервер на локальной машине, или на виртуальной, гайды в оп-посте.
На бесплатных хостингах тебе никто не даст права администратора в БД. И создать дополнительную базу можно разве что через админку, если они это позволяют.
Типа есть либа на жс и нет на рнр? Ты траль? Или не нашёл "либы" с тупо одним циклом функции http://php.net/manual/en/function.imagecolorat.php по файлу?
У тебя есть глаза? Как ты работаешь, если не в состоянии прочитать одну строку? Давай, что написано на красной плашке?
В идеале - примерно в 2000-й. Будешь крутым челом, все захотят с тобой работать на удалёнке.
Работы дофига, больше чем ты сможешь проглотить. Особенно на апворках и проч, но нужно просто любить бекэнд. Алгоритмов хватает - самых разных, начиная от парсинга XML/JSON, создание всяких бэкгранд тасков, работа с базой, API-интерфейсы, админки, работа с фронтэндом (типа общение с мордой посредством server-sent events) и так далее.
На всяких апворках иногда жирные контакты проскакивают, типа сделать вебсайт для швейцарской клиники и т.д. Денег там немерено, но чтобы конкурировать с индусами, нужно показать высокий уровень, что ты профессионально кодишь.
Насчёт нужно ли знать JS? Конечно нужно. Если ты кодишь на php как бог, но при этом на js пишешь будто второклассник, то и дело вылетают функции TypeError: functionName is not a function, то это лютый фейл.
фриланс-бекэндер
Поведай нам свою историю успеха. Когда начинал? Как взял свой первый заказ? Работал ли ирл? Сколько уже кодишь?
Можешь форкнуть любую либу или плагин и допилить его. Наберешься опыта, потом во фриланс. На самом деле похуй, что там пугают мол начинающему нельзя и проч... Можно! В сети куча работодателей-долбоебов, которым просто нужен сайт. Любых индусов нанимают, потом смотришь в код, там ужоснах, вообще удивительно как это всё работает. После индусов я думаю даже у российских школьников есть шанс.
>>1045628
Лет 5 назад, когда твиттер бутстрап ещё только появился. Заказ взял просто, буквально наверно с 5ой попытки, написал американцу и буквально за 2 дня срубил сотню баксов. Просек эту фишку, с тех я ни одного дня в офисе не проработал, ибо нах надо. Пару часиков в день поработал и нормик, в остальное время хочу по кафешкам, слушаю лекции, развиваюсь, читаю. Денюжки тем временем на банковский счет капают. Делаю в основном темы для вордпресса, до этого года 2 был безработным, пытался пилить тему. До этого работал на днищеработе в своем мухосранске, c++ кодер, писал прошивки для роутеров за 12 тысяч в месяц.
тот-же-фрилансер
За копейки -- может быть
>написал американцу и буквально за 2 дня срубил сотню баксов
А сейчас уже такой трюк не пройдёт? И где искать таких заказчиков (если не секрет)?
>c++ кодер
Нихуя себе, туда вкатиться раз в десять сложнее, чем в веб.
Просто на пыхе это не удобно, например. Там можно получить массив пикселей одной строкой, а тут писать 20. А ещё потом переводить формат MSAccess в хекс.
Я заебался уже, хочется учить, но не получается - приходится подстраиваться под эту дичь пропуская дни, недели.
>А сейчас уже такой трюк не пройдёт? И где искать таких заказчиков (если не секрет)?
Да и сейчас свободно пройдет, 50 баксов в день это в принципе небольшие деньги для зарубежного фриланса.
>Нихуя себе, туда вкатиться раз в десять сложнее, чем в веб.
Да везде можно легко вкатиться, потому что тратить время экспертов на какую-нибудь софтину для ООО Вектор нерационально, да и сам профессионал не станет из-за копеек, а вот для джуиниора как подработка норм.
Спасибо, возьму на заметку.
>>1045578
Я просто думаю, что это мне не нужно. Считаю, что лучше время направить не на изучение JS, а на улучшение навыков программирования в целом, читая книги Стива Макконнелли, Кормена, всякие статьи про php, sql, построение баз данных, паттерны и т.д.
>>1045608
А не "начинающий" бэкендер это кто? Какие у него должны быть навыки? Можешь хотя бы вкратце описать?
>>1045627
Спасибо. Короче нужно иметь не только хорошие навыки кодинга, но и знать английский.
Можешь посоветовать какие-нибудь книги? Не для чайников, а уже более продвинутого уровня? Или ты их не читаешь? Можно на английском.
Вот кусочек моего кода: https://ideone.com/pph7Xm
> position: absolute;
>> top: 70%;
>Почему именно 70%? Не проще ли просто bottom: 0?
Если поставить bottom: 0, то почему-то не появляется прокрутка.
>Ты ведь вроде делал мои задачи на HTML, значит, ты наверно сможешь решить проблему. Давай начнем с простой задачи (реально простая):
>
>- есть див известной высоты (например, 300px или 100% от высоты страницы)
>- в него вложено содержимое неизвестной высоты
>- если оно маленькое, оно должно прижиматься к низу контейнера
>- если содержимого много, то должна появляться прокрутка
Стыдно признавать, но у меня даже не появляется идей как это сделать обычными средствами. Это нужно сделать с помощью flex?
>Я в том сообщении ( https://2ch.hk/pr/res/1000416.html#1007389 (М) ) предложил возможные подходы, нужно просто их перебрать и посмотреть, реально их тут применить или нет. Если вообще никак, то придется использовать вычисление высоты через яваскрипт (с пересчетом при ресайзе окна), но конечно средствами CSS решить эту задачу было бы гораздо лучше (но в реальных задачах иногда приходится прибегать и к яваскрипту).
Меня немного смущает прибегать к флексу, потому что он не рассчитан на старые браузеры. Хотя в старых браузерах только слегка поломается отображение, это не так критично.
Вариант с табличным отображением я не рассматриваю, потому что, как вы писали, всё равно придётся прибегнуть к js.
У меня ещё есть вопрос по js. Опять про обработчики...
Установка обработчика, это часть контроллера или отображения?
На примере SPA приложения, когда мы проходим авторизацию или переходим по разным секциям сайта, мы, с помощью обработчиков, выводим новые виджеты, которые могут иметь свою инициализацию, свои обработчики, и так же взаимодействовать друг с другом.
На пример, пройдя авторизацию, мы выводим главную страницу, на которой есть меню навигации, по клику на которое выводиться какая-нибудь другая страница со своими виджетами. Получается, что виджет авторизации взаимодействует с виджетом меню навигации, и, соответственно, меню навигации взаимодействует с какими-то другими виджетами которые выводятся на страницах. И чтобы одни виджеты могли взаимодействовать с другими, нужно передавать их как зависимость. И если на какой-то странице нужно вывести слишком много виджетов, то можно будет легко запутаться в зависимостях (как это получилось у меня, когда я попытался вынести установку обработчика из контроллера в отображение).
Создается впечатление, что должно быть что-то вроде роутера, который держит все виджеты и устанавливает обработчики на них, которые занимаются взаимодействиями между друг другом.
В таком ключе становиться не важно кто устанавливает обработчик контроллер или отображение.
Я в правильном направление мыслю насчет роутера?
> position: absolute;
>> top: 70%;
>Почему именно 70%? Не проще ли просто bottom: 0?
Если поставить bottom: 0, то почему-то не появляется прокрутка.
>Ты ведь вроде делал мои задачи на HTML, значит, ты наверно сможешь решить проблему. Давай начнем с простой задачи (реально простая):
>
>- есть див известной высоты (например, 300px или 100% от высоты страницы)
>- в него вложено содержимое неизвестной высоты
>- если оно маленькое, оно должно прижиматься к низу контейнера
>- если содержимого много, то должна появляться прокрутка
Стыдно признавать, но у меня даже не появляется идей как это сделать обычными средствами. Это нужно сделать с помощью flex?
>Я в том сообщении ( https://2ch.hk/pr/res/1000416.html#1007389 (М) ) предложил возможные подходы, нужно просто их перебрать и посмотреть, реально их тут применить или нет. Если вообще никак, то придется использовать вычисление высоты через яваскрипт (с пересчетом при ресайзе окна), но конечно средствами CSS решить эту задачу было бы гораздо лучше (но в реальных задачах иногда приходится прибегать и к яваскрипту).
Меня немного смущает прибегать к флексу, потому что он не рассчитан на старые браузеры. Хотя в старых браузерах только слегка поломается отображение, это не так критично.
Вариант с табличным отображением я не рассматриваю, потому что, как вы писали, всё равно придётся прибегнуть к js.
У меня ещё есть вопрос по js. Опять про обработчики...
Установка обработчика, это часть контроллера или отображения?
На примере SPA приложения, когда мы проходим авторизацию или переходим по разным секциям сайта, мы, с помощью обработчиков, выводим новые виджеты, которые могут иметь свою инициализацию, свои обработчики, и так же взаимодействовать друг с другом.
На пример, пройдя авторизацию, мы выводим главную страницу, на которой есть меню навигации, по клику на которое выводиться какая-нибудь другая страница со своими виджетами. Получается, что виджет авторизации взаимодействует с виджетом меню навигации, и, соответственно, меню навигации взаимодействует с какими-то другими виджетами которые выводятся на страницах. И чтобы одни виджеты могли взаимодействовать с другими, нужно передавать их как зависимость. И если на какой-то странице нужно вывести слишком много виджетов, то можно будет легко запутаться в зависимостях (как это получилось у меня, когда я попытался вынести установку обработчика из контроллера в отображение).
Создается впечатление, что должно быть что-то вроде роутера, который держит все виджеты и устанавливает обработчики на них, которые занимаются взаимодействиями между друг другом.
В таком ключе становиться не важно кто устанавливает обработчик контроллер или отображение.
Я в правильном направление мыслю насчет роутера?
Первая задача решается просто. У нас есть блок сообщений, и мы хотим прокрутку при превышении размера. Значит, нужно поставить max-height: 100% и overflow: auto;. По условиям задачи, высота контейнера у нас жестко задана (а не вычисляется из содержимого), потому мы можем использовать 100% здесь.
Получаем блок, который по высоте будет равен или меньше родителя. Остается только прижать его к низу контейнера за счет pos: absolute, width 100% (для АП элементов ширина по умолчанию определяется содержимым, а мы этого не хотим), bottom: 0;
У тебя не появлялась прокрутка из-за того, что ты наверно пытался ставить overflow на контейнер. То есть у тебя была ситуация:
- контейнер с overflow
- ребенок с position absolute, по высоте превышающий контейнер, с контентом, вываливающимся выше верхней границы контейнера
Тут есть один момент: прокрутка работает только для контента, который "вываливается" (overflows) из родителя вправо или вниз. Контент, который уходит влево или вверх, просто обрезается: https://jsfiddle.net/uusLtkku/
Я попытался найти документацию по этому поведению в описании свойства overflow в CSS2.1, а также в дополнениях из CSS3 и CSS4:
- https://www.w3.org/TR/CSS2/visufx.html#q11.0
- https://www.w3.org/TR/css-overflow-3/#scrolling-direction
- https://www.w3.org/TR/css-overflow-4/
Объяснение нашлось тут:
https://www.w3.org/TR/css-overflow-3/#scrolling-direction
> Due to Web-compatibility constraints (caused by authors exploiting legacy bugs to surreptitiously hide content from visual readers but not search engines and/or speech output), UAs must clip the scrollable overflow region of scroll containers on the block-start and inline-start sides of the box (thereby behaving as if they had no scrollable overflow on that side).
Block-start - это верхний край и inline-start это левый край для языков, где пишут справа налево и сверху вниз.
Первая задача решается просто. У нас есть блок сообщений, и мы хотим прокрутку при превышении размера. Значит, нужно поставить max-height: 100% и overflow: auto;. По условиям задачи, высота контейнера у нас жестко задана (а не вычисляется из содержимого), потому мы можем использовать 100% здесь.
Получаем блок, который по высоте будет равен или меньше родителя. Остается только прижать его к низу контейнера за счет pos: absolute, width 100% (для АП элементов ширина по умолчанию определяется содержимым, а мы этого не хотим), bottom: 0;
У тебя не появлялась прокрутка из-за того, что ты наверно пытался ставить overflow на контейнер. То есть у тебя была ситуация:
- контейнер с overflow
- ребенок с position absolute, по высоте превышающий контейнер, с контентом, вываливающимся выше верхней границы контейнера
Тут есть один момент: прокрутка работает только для контента, который "вываливается" (overflows) из родителя вправо или вниз. Контент, который уходит влево или вверх, просто обрезается: https://jsfiddle.net/uusLtkku/
Я попытался найти документацию по этому поведению в описании свойства overflow в CSS2.1, а также в дополнениях из CSS3 и CSS4:
- https://www.w3.org/TR/CSS2/visufx.html#q11.0
- https://www.w3.org/TR/css-overflow-3/#scrolling-direction
- https://www.w3.org/TR/css-overflow-4/
Объяснение нашлось тут:
https://www.w3.org/TR/css-overflow-3/#scrolling-direction
> Due to Web-compatibility constraints (caused by authors exploiting legacy bugs to surreptitiously hide content from visual readers but not search engines and/or speech output), UAs must clip the scrollable overflow region of scroll containers on the block-start and inline-start sides of the box (thereby behaving as if they had no scrollable overflow on that side).
Block-start - это верхний край и inline-start это левый край для языков, где пишут справа налево и сверху вниз.
http://195.181.244.246/
Исходники https://github.com/enotocode/birthday_reminder
Доделал реал-тайм валидацию на стороне клиента и на сервере. Вынес запросы к АПИ в отдельный слой.
Потратил пару дней на поиск и исправление ошибок. Зато теперь я уверен, что мои исходники можно запустить еще где-то кроме моего ноута. Сейчас я понимаю, почему ОП четыре раза говорил отказаться от компиляции кода в дев версии. Пересобирать скрипт после каждого чиха заметно увеличивает время на разработку.
А чего сумбит неработает?
Я знаю только чистый пхп.
Если да, то киньте гайдиков хороших, или книгу сразу, пожалуйста.
читаю на английском вполне хорошо, но информация лучше усваивается на родном все же
Пытаюсь удалить строку в базе по аякс запросу. Строка удаляется, но страница не обновляется, вместо этого в консоль прилетает весь код страницы. Без всяких варнингов. При обычном обращении к странице clients, на ней показываются все строки таблицы правильно. Что не так в коде?
Таблица clients, страница view\site\clients, контроллер \controllers\SiteController
public function actionClients()
{
if(Yii::$app->request->isAjax){
$idRow = Yii::$app->request->post('id');//принимаем id записи из js скрипта
$model = new Clients();
$model = Clients::find()->where(['id' => $idRow])->one();
$model->delete();
}
$listClients = Clients::find()->all();
return $this->render('clients', compact('listClients'));
}
И ещё вопрос про логику контроллера. Я правильно понимаю, что если на странице есть несколько кнопок, которые делают разное (передают через пост/гет/аякс данные), то все действия с ними надо писать в контроллере этой страницы, т.е. для страницы clients всё пишется в actionClients?
Получается, внутри нужно ставить проверки "пришло ли чего в аякс/гет/пост?", а если кнопок, оправляющих, к примеру, постом, несколько, то внутри ещё и проверки "пришло ли post('id'), или пришло post(name')?"
Или я опять не понял?
Насчет нескольких форм на странице (вообще, такое не очень часто требуется), есть 2 варианта.
Первый - обрабатывать все в одном контроллере.
Если это обычная форма, то логично обработчик формы делать в том же контроллере, который ее выводит. Иначе как ты при ошибке выведешь форму с заполненными значениями?
Подвох: если у тебя несколько форм на странице, и пользователь заполнит их все, то при отправке отправляются только данные из одной формы, и данные других форм теряются.
Второй вариант - сделать для каждой формы свою отдельную страницу, свой URL, который и выводит ее, и обрабатывает POST запрос. И формы с главной страницы просто отправляют данные на эту страницу.
Так обычно делают форму логина: на сайте в углу ставят форму, которая постит данные на отдельную страницу. Если логин успешен, пользователя с нее редиректит обратно туда, где он вводил логин. Если нет - показывается отдельная страница логина с формой.
Это все относится к обычным формам и нужно для того, чтобы при ошибке показать ту же форму с введенными данными.
Если это обработчик аякс-запроса, то так делать смысла нет. Для него нужен отдельный контроллер.
> а если кнопок, оправляющих, к примеру, постом, несколько,
Кнопки отправляют собственное имя, по которому можно определить, какая из них нажата.
Дополнение: Юи испоьзует довольно хитрый трюк для аякс-валидации форм. По мере заполнения формы он отправляет ее данные на тот же URL, на который она отправляется, но добавляет признак, что это аякс-запрос для валидации. И в этом случае вместо обработки формы происходит только проверка данных и возврат результата, на основании которого код показывает иконки ошибки.
Что касается твоего кода тут >>1045824 то там много странностей.
Например:
- почему ты используешь isAjax вместо какого-то явного признака, что это аякс запрос? При отладке ты вряд ли догадаешься что от какого-то второстепенного HTTP заголовка меняется поведение
- соответственно удаление логичнее делать в другом действии, не в том же, что и вывод списка
- при аякс-запросе результат обычно отдают в виде JSON, у тебя же почему-то в ответ на запрос рендерится и отдается HTML страница. Зачем?
- если ты работаешь с аяксом, прочти мой урок про правильное использование аякса https://github.com/codedokode/pasta/blob/master/js/ajax.md
> Строка удаляется, но страница не обновляется, вместо этого в консоль прилетает весь код страницы.
Ну так написано в твоем коде. Ты его сам писал или скопировал откуда-то,
не разобравшись? Всегда лучше писать самому, понимая каждую строчку.
Также, ты не привел тут JS код.
>>1045824
> $model = new Clients();
Строка лишняя.
>$model = Clients::find()->where(['id' => $idRow])->one();
> $model->delete();
Тут 2 SQL-запроса, а хватило бы одного.
И тебе нужно JSON-ответ возвращать, а не HMTL: https://github.com/samdark/yii2-cookbook/blob/master/book/response-formats.md#json-response
Ты с HTTP знаком? У ОПа на гитхабе есть пасты.
Алгоритм примерно такой:
1) Шлёшь аякс-запрос на удаление сущности
2) На стороне PHP удаляешь из базы сущность, возвращаешь JSON-ответ success: true, если неудачно то success: false
3) На стороне JS разбираешь этот JSON-ответ - если успешно - удаляешь нужный элемент из DOM-дерева.
>>1045906
На каждое действие (удаление, обновление и т.д) отдельный action, ты же сам понимаешь, что городишь неподдерживаемый код.
>>1045919
Бесплатно и быстро у хероку, но там постгрес. Платно у амазон.
Многие обсирали мой код, но он работает.
Имеется сайт на выделенном сервере с Ubuntu, PHP, nginx, Laravel. Сайт смотрит в обычный интернет, любой желающий может на него попасть через свой браузер.
Вопрос: возможно ли средствами PHP запилить get-запрос на другой сайт, но уже расположенный в зоне .onion, Т.е. Tor.
И если возможно, то как?
ОП, посмотри мой код, пожалуйста, это про компанию Вектор. Убрал из паблика все свойства. Переписал полностью классы департаментов и сотрудников. Начальные данные о составе департаментов и данные о ставках вынес в отдельные массивы, правда не очень логичным кажется постоянная передача этих данных в методы объектов ($manningTable и $staffList). Может имеет смысл их глобальными сделать.
В общем жду замечаний, а сам дальше буду пилить вторую часть задачи про антикризисные меры.
https://github.com/telepok/php-test/blob/master/oop4.php
В учебнике http://archive-ipq-co.narod.ru/ почему-то в хроме не работают ссылки в меню в левом столбце, приходится в другом браузере открывать, это только у меня так?
Ой, прилипло
Ну, так-то да, склоняюсь к этому, ну хотелось чтобы еще раз глянули код. Но за совет спасибо, пойдем дальше.
Кстати, по поводу списка студентов хотел спросить, точнее про шаблонизацию, стоит ли сразу пробовать какой-нибудь шаблонизатор, типа смарти, например? Или пока можно руками шаблоны делать? Впервые просто с этим сталкиваюсь.
Отписались разрабы с Идеона:
thanks for the report. The Multibyte String library is now not available on Ideone, but we keep these reports. When the number of the similar suggestions is big we add the requested library. We will tell you when we decide to do it.
Просто странно, никому не нужны мультибайт строки, или их поддержку так сложно добавить.
Че в пыху то перекатился?
>>1046138
Я сам говнокодер похуже тебя, чуть выше свое решение по вектору кинул, у меня там таблица в хтмл, просто знаю людей которые забили вообще на эту задачу и пошли дальше. Вот вам в помощь https://www.youtube.com/watch?v=ba3M3_Myrqg&t=1s
Да как-то я скептически к учебным роликам отношусь, мне комфортнее текст воспринимать с картинками. Хотя справедливости ради скажу, что Интенсивы от HTML Академии мне хорошо зашли, но там в придачу к видеокурсу были и рабочие материалы, макеты в PSD те же.
Тогда во второй задаче, если высота формы не определяется содержимым, то нужно так же на неё поставить pos:absolute, bottom: 0, height: x;
А для блока с сообщениями задать bottom: x; и высчитать max-height: calc(100% - x);
Не уверен что функция calc имеет здесь место, но мне приходит в голову только такое решение.
https://jsfiddle.net/55khok9f/2/
Если высота формы определяется содержимым, то здесь у меня тоже возникают трудности. Можно ещё подсказку?
Да и книжка тоже параллельно с http://php.net/ читаться будет. Хотел ваше мнение об книжке спросить просто. Покупать не хочу, 30$ стоит.
>>1046139
Веб разработка как мне кажется приятней и проще рабочее место найти.
>>1046140
Я даже к книжкам с картинками скептически отношусь, как мне кажется там больше воды. Т.е хорошая книжка как правило дорого стоит. Я вообще CSS и HTML5 по этим ресурсам изучаю:
https://www.w3.org/TR/html51/
https://drafts.csswg.org/
Ибо там все до атомов разбирается, как работает синтаксис, даже примеры ввели. А там же еще и ответвления, типа вот
https://www.w3.org/TR/2011/REC-CSS2-20110607/#minitoc
И представь как я еще не заебался это все скроллить? Поэтому полюбил книжки, ибо там все кратко. Но тем не менее, захожу на спецификаций дабы подробнее изучить некоторые вещи. В книжках алсо дают эту информацию но в аспекте который обязательно нужно знать, а все детали типа white_space_separate_tokens опускаются.
Я пытаюсь разобраться в Yii2. Нашёл видеоуроки https://www.youtube.com/channel/UCo8uH16xQsZCJL5VqZ-g9QA прошёл их, и теперь пытаюсь повторить что-то такое уже самостоятельно, поэтому в основном копипащу оттуда. Разобраться, конечно, нужно, но пока нереально. То есть, по ходу дела разбираюсь в чём-то, просто повторяя то, что работает. Ну не могу я окинуть всё взглядом и полностью разобраться во всём сразу.
>Первый - обрабатывать все в одном контроллере.
То есть, в контроллере нужно делать проверку, какая именно форма сработала, так?
>если кнопок, оправляющих, к примеру, постом, несколько, то внутри ещё и проверки "пришло ли post('id'), или пришло post(name')?"
>если у тебя несколько форм на странице, и пользователь заполнит их все, то при отправке отправляются только данные из одной формы, и данные других форм теряются.
Логично.
>Второй вариант - сделать для каждой формы свою отдельную страницу
Это тоже понятно.
>Если это обработчик аякс-запроса, то так делать смысла нет. Для него нужен отдельный контроллер.
Насколько я понял, для каждой страницы может быть только один контроллер. И это прибито гвоздями (должны совпадать их имена). То есть, для страницы view\site\clients.php есть один и только один контроллер с именем controllers\SiteController.php
>>1045968
>почему ты используешь isAjax вместо какого-то явного признака, что это аякс запрос?
Так было в примере. И какие могут быть другие признаки аякса, кроме isAjax? Это вроде логично выглядит.
>соответственно удаление логичнее делать в другом действии, не в том же, что и вывод списка
Да. Но я не знаю, как их разделить. При отправке из view\site\clients.php, срабатывает контроллер SiteController, а в нём public function actionClients(). Как я уже писал, в уроках было написано, что это всё жёстко определено. Сам вижу, что пихать разные действия в одну фнкцию как-то неправильно, но как разделить не знаю.
>при аякс-запросе результат обычно отдают в виде JSON, у тебя же почему-то в ответ на запрос рендерится и отдается HTML страница. Зачем?
Логика такая:
Во вью написано: <button type='button' onclick='deleteRow(<?=$id?>, <?=$table?>);'>Удалить</button>
В JS-скрипте deleteRow(id, table) собирает со страницы id записи и имя таблицы, а затем передаёт аяксом на url:"index.php?r=site/clients". Хм. Получается, можно и не туда передавать, а в отдельный метод? Например, site/delete, и в контроллере прописать для него этот метод public function actionDelete(). Я правильно понял?
Но возвращаюсь к текущей логике. Данные аяксом уходят в контроллер, в actionClients(). Контроллер удаляет выбранную строку из таблицы, и мне нужно, чтобы он обновил вью страницы, показав таблицу без удалённой строки. Ага, получается, вместо обновления страницы, рендер уходит, как ответ success аяксу JS-скрипта? Получается, что обновлением должен заниматься JS-скрипт? Блин, у меня мозги в трубочку от этой хрени заворачиваются.
>Ну так написано в твоем коде.
У меня проблемы с пониманием, что движок делает за меня, а что нет.
>>1045970
>Создай actionDelete и туда отсылай аякс-запросы на удаление.
Попробую.
>Тут 2 SQL-запроса, а хватило бы одного.
Такого?
$model = Clients::find()->where(['id' => $idRow])->one()->delete();
>И тебе нужно JSON-ответ возвращать
Да, уже понял, что обновление должен делать JS, а ему для этого нужно что-то дать. В уроках этот момент не был освещён, там после принятия данных через аякс, контроллер сам обновлял вью.
В общем, спасибо, буду мучать Yii дальше.
Я пытаюсь разобраться в Yii2. Нашёл видеоуроки https://www.youtube.com/channel/UCo8uH16xQsZCJL5VqZ-g9QA прошёл их, и теперь пытаюсь повторить что-то такое уже самостоятельно, поэтому в основном копипащу оттуда. Разобраться, конечно, нужно, но пока нереально. То есть, по ходу дела разбираюсь в чём-то, просто повторяя то, что работает. Ну не могу я окинуть всё взглядом и полностью разобраться во всём сразу.
>Первый - обрабатывать все в одном контроллере.
То есть, в контроллере нужно делать проверку, какая именно форма сработала, так?
>если кнопок, оправляющих, к примеру, постом, несколько, то внутри ещё и проверки "пришло ли post('id'), или пришло post(name')?"
>если у тебя несколько форм на странице, и пользователь заполнит их все, то при отправке отправляются только данные из одной формы, и данные других форм теряются.
Логично.
>Второй вариант - сделать для каждой формы свою отдельную страницу
Это тоже понятно.
>Если это обработчик аякс-запроса, то так делать смысла нет. Для него нужен отдельный контроллер.
Насколько я понял, для каждой страницы может быть только один контроллер. И это прибито гвоздями (должны совпадать их имена). То есть, для страницы view\site\clients.php есть один и только один контроллер с именем controllers\SiteController.php
>>1045968
>почему ты используешь isAjax вместо какого-то явного признака, что это аякс запрос?
Так было в примере. И какие могут быть другие признаки аякса, кроме isAjax? Это вроде логично выглядит.
>соответственно удаление логичнее делать в другом действии, не в том же, что и вывод списка
Да. Но я не знаю, как их разделить. При отправке из view\site\clients.php, срабатывает контроллер SiteController, а в нём public function actionClients(). Как я уже писал, в уроках было написано, что это всё жёстко определено. Сам вижу, что пихать разные действия в одну фнкцию как-то неправильно, но как разделить не знаю.
>при аякс-запросе результат обычно отдают в виде JSON, у тебя же почему-то в ответ на запрос рендерится и отдается HTML страница. Зачем?
Логика такая:
Во вью написано: <button type='button' onclick='deleteRow(<?=$id?>, <?=$table?>);'>Удалить</button>
В JS-скрипте deleteRow(id, table) собирает со страницы id записи и имя таблицы, а затем передаёт аяксом на url:"index.php?r=site/clients". Хм. Получается, можно и не туда передавать, а в отдельный метод? Например, site/delete, и в контроллере прописать для него этот метод public function actionDelete(). Я правильно понял?
Но возвращаюсь к текущей логике. Данные аяксом уходят в контроллер, в actionClients(). Контроллер удаляет выбранную строку из таблицы, и мне нужно, чтобы он обновил вью страницы, показав таблицу без удалённой строки. Ага, получается, вместо обновления страницы, рендер уходит, как ответ success аяксу JS-скрипта? Получается, что обновлением должен заниматься JS-скрипт? Блин, у меня мозги в трубочку от этой хрени заворачиваются.
>Ну так написано в твоем коде.
У меня проблемы с пониманием, что движок делает за меня, а что нет.
>>1045970
>Создай actionDelete и туда отсылай аякс-запросы на удаление.
Попробую.
>Тут 2 SQL-запроса, а хватило бы одного.
Такого?
$model = Clients::find()->where(['id' => $idRow])->one()->delete();
>И тебе нужно JSON-ответ возвращать
Да, уже понял, что обновление должен делать JS, а ему для этого нужно что-то дать. В уроках этот момент не был освещён, там после принятия данных через аякс, контроллер сам обновлял вью.
В общем, спасибо, буду мучать Yii дальше.
> И какие могут быть другие признаки аякса, кроме isAjax?
Как по твоему Yii определяет значение этого поля? Как он догадывается, что запрос отправлен аяксом?
Я конечно извиняюсь, но с атомами ты загнул, имхо, это же не наука, а прикладная область. В реальной практике пригодятся ли тебе такие тонкости?
И еще по поводу книги. Если ты про 4 издание на английском, то оно в первой ссылке гугла даром скачивается. Зачем покупать-то. Я вообще считаю, что не стоит покупать то, что не обернется материальной выгодой. Но это уже лирика. В общем именно книжка с картинки скачивается на раз, 800 страниц, могу почтой выслать, лол.
>Как по твоему Yii определяет значение этого поля? Как он догадывается, что запрос отправлен аяксом?
ВААААХ! ХАЙТАН-МАШИН АЯКС ПРИШЙОЛ!
Пока что это для меня чёрный ящик
На моём сайте в странице условный код JS:
<script>
readFile("http://МОЙСАЙТ/data.php");
</script>
data.php возвращает текстовый файл.
Итак, вопрос, можно ли сделать, если ссылку http://МОЙСАЙТ/data.php скопировать в адресную строку, браузер покажет мою заглушку, но никак не текстовый файл. А скрипту js на странице возвращает нужный текстовый файл.
>Magento
Не работал, но скажу что важно помнить что это CMS т.е. один из многих продуктов на PHP и то что, возможно, некоторые вещи, могут быть, реализованы в ней не канонично и не по стандартам.
Всегда стоит подтягивать знания по фундаментальным вещам и смотреть современные фреймворки, не ограничивая свои знание одной из тысячи CMS.
Я так почитал, эта мажента написана на зенд фреймворке, так что возможно там все норм. Только там какое-то свое видение MVC, с какими-то блоками, шаблоны как-то грузятся с помощью XML(что бы это значило, хуй поймешь).
Так я же об этом написал, мои намеки конеш тупые. Я имел ввиду >Покупать не хочу всмысле я ее спизжу. Просто хотел мнение узнать анонов, может кто читал, авось рецензию свою даст.
В принципе O'relly говна не пишут же?
Я бы порекомендовал идти по урокам ОПа, дополнительно гуглить и спрашивать тут. Или там какой-то простенький курс по основам на ютубе посмотреть. А учебники по пхп то еще говнище в основном. Есть только 2-3 годных, но они далкео не для ньюфагов.
Да я сам в шоке был.
Авторизация клиент-сервер
Сохраняешь куку на клиенте
При обращении к http://МОЙСАЙТ/data.php проверяешь на сервере куку -> авторизованный? отдаешь файл -> нет? отдаешь заглушку.
>приходится разбивать диапазоны на адреса, адреса на октеты, высчитывать подсети и т.д.
Напиши сам библиотеку, я вот в период изучения диапазонов и масок вообще хотел пет-проджект сделать - ip-калькулятор. Вообще и сейчас не теряю надежды, вот выучу сейчас пхп и обязательно сделаю :-)
>>1046258
Двачую оратора. В книжках, особенно если быстро надо освоить, начинаешь читать, читаешь, читаешь, предисловие, благодарности женам, любовницам, потом главу про то, каким крутым ты станешь, а потом когда доходишь до примерно 400-500 страницы, осознаешь, что еще 500-600 осталось, и начинается выгорание. А практического опыта-то и нет, и время потратил. В общем двойственно это все. Почему везде и советуют идти в офис, потому что нужны именно практические навыки, то есть результат, причем работающий.
К слову про атомы, я когда физиком должен был стать, так вот препод нам говорил слова одного ученого: Теория без практики мертва, практика без теории слепа. Такие дела.
Я, конечно, извиняюсь, но как же ты выучил пхп, если ты даже задачку "Grammar Nazi" из учебника ОП не сделал?
В шапке есть задача про студентов, файлообменник и про тестхаб - почитай их описания, попробуй решить и после этого форум не будет для тебя представлять проблемы.
Хочу сгенерить хеш файла для параметра integrity
( https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity )
Делаю так:
integrity="sha512-<?=hash_file( 'sha512', $_SERVER['DOCUMENT_ROOT'].'/s/js/script.js' ); ?>"
Получаю ошибку в браузере:
Failed to find a valid digest in the 'integrity' attribute for resource 'http://blablabla.ru/s/js/opt/main.min.js' with computed SHA-256 integrity 'i/EDj3xxy4ipuu4jpvF0fEo5QEHK3FG7Yu4Olqgpnjc='. The resource has been blocked.
Уже битый час бьюсь, не могу понять причину.
ЧЯДНТ? Чому хэши не сходятся?
Да, через прокси в тор
Да, в ошибке почему-то SHA256,
Но если ставить этот или любой алгоритм из списка -- всё равно не сходится
Проорал
Вообще проблема в том, что сайт недоступен при большом количестве посетителей. Это ведь возможно из-за нехватки оперативы? Например зашло 100 пользователей, каждый скрипт отъел 10 мб, итого 1ГБ. И если на сервере оперативы меньше, то для 101-го пользователя места уже не будет? Меня интересуют скорее общие советы и рекомендации при оптимизации, в первый раз с таким сталкиваюсь.
Чтоб за неделю осилить и переписать ядро сайта на это.
По беспределу
https://ideone.com/fork/523cQA
Суть такова, что есть ряд чисел, нужно вывести количество пятёрок в нём. У меня выводит количество всех чисел.
Пиздец ты криворукий, сравнение это ==, а = это присваивание. Ты в if не сравниваешь, а присваиваешь.
Вот как всё должно быть
https://ideone.com/60gE6g
>Пикрил.жпг
А что так стоит делать? Я думал лучше в отдельном классе создавать объект Департамента и в функцию адд департмент уже сам объект кормить.
public function addDep (Department $dep) {
...
}
Я понимаю что если еще это можно оправдать реальностью, и как бы сама организация ирл будет рожать подразделения, то вот пик два уже не очень.
>пик 3
Похоже что элегантная задумка, но для меня как для стороннего разраба ковыряющего твой код это выглядит как адовый костыль.
Вместо инфы в самих классах я вижу что это сторонний хардкод где-то в середине программы, который хз каким функциям кормится и хз как с ним вообще работать :(
Опытный игнайтеромакак в треде, что конкретно у тебя не получилось? Ты не смог либу подрубить в коде или что? И почему говнокод? Не можешь модель написать для работы с ip?
Приведи пример что ли типовых задач, интересно даже стало.
>0 попыток решить
>задача по сложности для тех кто осилил первые уроки опа
Бля, я расстроен с вас :(
Ты заменил на сервере все файлы кроме удаленного вангую.
Если я правильно понял Опа, мы не должны самостоятельно создавать объекты, а действовать через методы. В данном случае у нас есть компания, в которой есть департаменты, о которых мы знаем только название,и в этих департаментах есть сотрудники, о которых мы знаем тоже только относящуюся к ним информацию. Проще же передать в качестве параметра название, а остальное за нас пусть делает сам объект. То же самое и с сотрудниками - есть департамент, нужно добавить туда сотрудника - одна строчка кода, остальное - "магия". Это по первому и второму пикам.
По поводу штатного расписания. Везде по разному, конечно, но в некоторых организациях штатное расписание утверждается на целый год, а иногда в него не вносятся изменения и несколько лет подряд, если, собственно, не меняется штатная численность. Такой, своего рода, hard-code. Но это именно так и есть, потому что это расписание утверждается на самом высоком уровне, ведь это и фонд оплаты труда, и налогообложение и т.д. Так что для меня реальность, что это не меняется ежесекундно. Аналогично и с свойствами должностей-профессий. Оклады меняются еще реже, чем штатная численность.К сожалению.
А в плане технической реализации - ну вот я сам себе такой вариант придумал, в ентерпрайз решениях понятно, что это все может выглядеть совершенно иначе. Ну и браться это будет не из кода, очевидно, а из базы данных, ну так задача-то не о том :-)
ЗЫ. И по поводу кофе. Сейчас как раз приняты антикризисные меры. На всё введены нормы. В то числе на туалетную бумагу. И вот кончилась бумага в середине квартала - никто тебе не побежит еще закупать, ну ты понел.
Лично я уже ускакал студентов пилить. Но можно и эту задачу посмотреть. У меня подозрение, что через рекурсию можно сделать, еще не пробовал. Можно и в лоб.
Я одного только не понял, почему функция называется persistence? Что это означает?
Может быть там настройки в стиле "только добавлять"? Удалять файлы на сервере (которых нет локально) может быть опасно, так как это может быть какой-то нужный файл.
>>1046119
> В учебнике http://archive-ipq-co.narod.ru/ почему-то в хроме не работают ссылки в меню в левом столбце, приходится в другом браузере открывать, это только у меня так?
У меня работают, и никто больше вроде на это не жаловался, описанный баг может быть из-за того, что поверх них выводится какой-то прозрачный (невидимый) элемент, может быть ты можешь проверить, что там? Для этого надо правой кнопкой кликнуть на ссылку и выбрать "просмотр кода элемента". Откроется окно отладчика с HTML-кодом страницы и если ты в нем ориентируешься, ты увидишь, какой элемент закрывает ссылки.
В теории там может быть какая-то проблема из-за рекламы юкоса. Я уже давно собираюсь перенести сайт на домен без рекламы, все руки не доходят.
По поводу добавления департаментов/работников (должен ли департамент создавать работников или ему должны передавать готовых) - здесь есть 2 подхода, аггрегация и композиция: https://ru.wikipedia.org/wiki/Агрегирование_(программирование)
Агрегация - это когда один объект содержит внутри ссылку на другой. Композиция - это то же самое, но когда другой объект не может существовать отдельно.
Ты явно пытаешься тут реализовать композицию, когда депртаменты не могут существовать вне компании, а работники - вне департамента. У нее такие недостатки:
- неудобно заполнять компанию. Ты создаешь департамент, а затем отдельным методом ищешь только что созданный департамент. Эта проблема частично решается тем, что функция создания может возвращать созданный департамент, и его не придется искать. Или тем, что мы создаем департаменты сами и добавляем их в компанию.
- нельзя использовать для работников другие классы, так как в коде жестко прописано new Employee
Но в общем-то, композицию тут использовать можно, я не против. Хотя мне не нравятся функции вроде getByName(). Ты пытаешься придумать уникальный идентификатор для департамента (название), но объекты - уникальны и обладают собственной идентичностью (например, можно проверить, ссылаются ли 2 переменные на один и тот же объект, с помощью тройного равно === ). То есть лучше было писать код так, чтобы не требовалось искать департамент по имени, а можно было просто сохранить объект в переменную и использовать его.
> public function getSalary($staffList)
Вот это не красиво. Почему мы для того, чтобы просто узнать зарплату, должны что-то передавать? Ну представь, что ты в коде получил объект Компании, из него доста Департамент, из него Работника, и хочешь узнать его зарплату:
$dep = $company->getByName(...);
$employee = $dep->findEmployee(...);
echo $employee->getSalary($staffList);
Ты должен передать справочник зарплат. Но почему этот справочник должен знать ты, а не Работник, Департамент или Компания? Где должен храниться справочник - в Компании или у того, кто хочет ее как-то использовать? У тебя, получается, Компания не знает, какие зарплаты у ее работников.
То есть я вижу такие варианты:
- вынести расчет зарплаты в специальный объект КалькуляторЗарплаты, и хранить ссылку на него в Работнике. работник использует Калькулятор для расчета своей зарплаты. Выгода в том, что можно поменять зарплату всем работникам одним изменением в Калькуляторе. Трудно поменять зарплату индивидуально одному работнику.
- поместить Калькулятор в Департамент или Компанию, и сделать так, что узнать зарплату работника можно только через них ($company->getEmployeeSalary($employee)). Можно даже сделать Калькулятор частью компании.
- поместить в работника информацию, нужную для расчета своей зарплаты. Выгода в том, что можно задавать зарплату каждого работника индивидуально, но для повышения зарплаты всем придется обойти всех в цикле.
> $coffee = $staffList[$this->profession][1];
Вот это вот трудно понять. Что такое 1? Почему именно 1? Лучше было бы $staffList[$this->profession]['coffee']; или хотя бы $staffList[$this->profession][self::FIELD_COFFEE];
> ($company->getByName($manningTableItem[0]))->addEmployee($manningTableItem[1], $manningTableItem[3],
$manningTableItem[4]);
Это тяжело читать, сложное выражение, лучше было бы сделать так:
list($depName, $rank, $count, $x, $y) = $tableItem;
$departament = $company->getByName($depName);
$departament->addEmployee($rank, $z, $w, $x, $y);
Сравни, насколько лучше читается.
Имена функций принято писать с маленькой буквы.
Ну и ждем вторую часть задачи, про антикризисные меры.
> правда не очень логичным кажется постоянная передача этих данных в методы объектов ($manningTable и $staffList).
Правильно кажется.
> Может имеет смысл их глобальными сделать.
Нет, так как это предполагает что во всех компаниях всегда одинаковые ставки зарплат. И что информация о зарплатах нужна везде, в любом месте кода. Это плохо - лучше хранить информацию только там, где она действительно нужна. Чтобы разграничивать зоны ответственности, какой класс за что отвечает.
> Я понимаю что если еще это можно оправдать реальностью, и как бы сама организация ирл будет рожать подразделения, то вот пик два уже не очень.
С работниками, да, композиция плохо подходит, так как у нас может быть 2 компании и работники могут переходить из одной в другую, а композиция подразумевает, что вложенный объект не может существовать без родительского и работник уничтожается вместе с компанией.
Может быть там настройки в стиле "только добавлять"? Удалять файлы на сервере (которых нет локально) может быть опасно, так как это может быть какой-то нужный файл.
>>1046119
> В учебнике http://archive-ipq-co.narod.ru/ почему-то в хроме не работают ссылки в меню в левом столбце, приходится в другом браузере открывать, это только у меня так?
У меня работают, и никто больше вроде на это не жаловался, описанный баг может быть из-за того, что поверх них выводится какой-то прозрачный (невидимый) элемент, может быть ты можешь проверить, что там? Для этого надо правой кнопкой кликнуть на ссылку и выбрать "просмотр кода элемента". Откроется окно отладчика с HTML-кодом страницы и если ты в нем ориентируешься, ты увидишь, какой элемент закрывает ссылки.
В теории там может быть какая-то проблема из-за рекламы юкоса. Я уже давно собираюсь перенести сайт на домен без рекламы, все руки не доходят.
По поводу добавления департаментов/работников (должен ли департамент создавать работников или ему должны передавать готовых) - здесь есть 2 подхода, аггрегация и композиция: https://ru.wikipedia.org/wiki/Агрегирование_(программирование)
Агрегация - это когда один объект содержит внутри ссылку на другой. Композиция - это то же самое, но когда другой объект не может существовать отдельно.
Ты явно пытаешься тут реализовать композицию, когда депртаменты не могут существовать вне компании, а работники - вне департамента. У нее такие недостатки:
- неудобно заполнять компанию. Ты создаешь департамент, а затем отдельным методом ищешь только что созданный департамент. Эта проблема частично решается тем, что функция создания может возвращать созданный департамент, и его не придется искать. Или тем, что мы создаем департаменты сами и добавляем их в компанию.
- нельзя использовать для работников другие классы, так как в коде жестко прописано new Employee
Но в общем-то, композицию тут использовать можно, я не против. Хотя мне не нравятся функции вроде getByName(). Ты пытаешься придумать уникальный идентификатор для департамента (название), но объекты - уникальны и обладают собственной идентичностью (например, можно проверить, ссылаются ли 2 переменные на один и тот же объект, с помощью тройного равно === ). То есть лучше было писать код так, чтобы не требовалось искать департамент по имени, а можно было просто сохранить объект в переменную и использовать его.
> public function getSalary($staffList)
Вот это не красиво. Почему мы для того, чтобы просто узнать зарплату, должны что-то передавать? Ну представь, что ты в коде получил объект Компании, из него доста Департамент, из него Работника, и хочешь узнать его зарплату:
$dep = $company->getByName(...);
$employee = $dep->findEmployee(...);
echo $employee->getSalary($staffList);
Ты должен передать справочник зарплат. Но почему этот справочник должен знать ты, а не Работник, Департамент или Компания? Где должен храниться справочник - в Компании или у того, кто хочет ее как-то использовать? У тебя, получается, Компания не знает, какие зарплаты у ее работников.
То есть я вижу такие варианты:
- вынести расчет зарплаты в специальный объект КалькуляторЗарплаты, и хранить ссылку на него в Работнике. работник использует Калькулятор для расчета своей зарплаты. Выгода в том, что можно поменять зарплату всем работникам одним изменением в Калькуляторе. Трудно поменять зарплату индивидуально одному работнику.
- поместить Калькулятор в Департамент или Компанию, и сделать так, что узнать зарплату работника можно только через них ($company->getEmployeeSalary($employee)). Можно даже сделать Калькулятор частью компании.
- поместить в работника информацию, нужную для расчета своей зарплаты. Выгода в том, что можно задавать зарплату каждого работника индивидуально, но для повышения зарплаты всем придется обойти всех в цикле.
> $coffee = $staffList[$this->profession][1];
Вот это вот трудно понять. Что такое 1? Почему именно 1? Лучше было бы $staffList[$this->profession]['coffee']; или хотя бы $staffList[$this->profession][self::FIELD_COFFEE];
> ($company->getByName($manningTableItem[0]))->addEmployee($manningTableItem[1], $manningTableItem[3],
$manningTableItem[4]);
Это тяжело читать, сложное выражение, лучше было бы сделать так:
list($depName, $rank, $count, $x, $y) = $tableItem;
$departament = $company->getByName($depName);
$departament->addEmployee($rank, $z, $w, $x, $y);
Сравни, насколько лучше читается.
Имена функций принято писать с маленькой буквы.
Ну и ждем вторую часть задачи, про антикризисные меры.
> правда не очень логичным кажется постоянная передача этих данных в методы объектов ($manningTable и $staffList).
Правильно кажется.
> Может имеет смысл их глобальными сделать.
Нет, так как это предполагает что во всех компаниях всегда одинаковые ставки зарплат. И что информация о зарплатах нужна везде, в любом месте кода. Это плохо - лучше хранить информацию только там, где она действительно нужна. Чтобы разграничивать зоны ответственности, какой класс за что отвечает.
> Я понимаю что если еще это можно оправдать реальностью, и как бы сама организация ирл будет рожать подразделения, то вот пик два уже не очень.
С работниками, да, композиция плохо подходит, так как у нас может быть 2 компании и работники могут переходить из одной в другую, а композиция подразумевает, что вложенный объект не может существовать без родительского и работник уничтожается вместе с компанией.
> Вместо инфы в самих классах я вижу что это сторонний хардкод где-то в середине программы, который хз каким функциям кормится и хз как с ним вообще работать
Ну у него идея в том, что в объектах не заложены никакие настройки, и ты задаешь их при создании. То есть вполне логично было бы например задавать ставки при создании компании - а почему нет? Или при создании работника, это тоже допустимо.
>>1046579
Тебе уже написали про =/==, а я дополню еще что ты непраивльно работаешь со строками. strlen() и $string[$i] работают только для строк на латиннице, не будут работать например с кирилицей: https://github.com/codedokode/pasta/blob/master/php/strings-utf8.md
>>1046466
peak memory usage это максимальное потребление памяти за время работы скрипта. Возможно, ты выбираешь больше данных из БД, например. Если тебя интересует поиск места, где выделяется много памяти, то придется заморочиться с xdebug, который умеет делать среди прочего 2 вещи:
- трассировка - при выполнении скрипта в файл логгируется вызов каждой функции, время ее выполнения и потребление памяти до/после вызова. Можно понять, где память выделается. Недостаток - лог может получиться огромный
- профайлинг - собирается статистика по вызовам функций, то есть для каждой функции считается число ее вызовов и суммарное/среднее время выполнения. Память правда это отследить не поможет.
Ну и есть еще "ручная" трассировка - расстановка вручную в коде команд, выводящих время выполнения/потребление памяти в текущий момент.
> Вообще проблема в том, что сайт недоступен при большом количестве посетителей. Это ведь возможно из-за нехватки оперативы? Например зашло 100 пользователей, каждый скрипт отъел 10 мб, итого 1ГБ. И если на сервере оперативы меньше, то для 101-го пользователя места уже не будет? Меня интересуют скорее общие советы и рекомендации при оптимизации, в первый раз с таким сталкиваюсь.
Это другая проблема и решается другим способом. Для начала надо сделать общий обзор ситуации, найти, где узкое место, каких ресурсов не хватает для обработки запроса. Для этого в линуксе есть много инструментов:
https://habrahabr.ru/post/114082/
https://habrahabr.ru/company/ua-hosting/blog/281519/
Ну например, если ты увидишь, что диск загружен на 100%, а CPU на 50% - станет понятно, что узкое место все же в дисковой подсистеме.
Заодно стоит проверить логи (/var/log) - там может быть например информация о завершении процессов OOM-killer'ом из-за нехватки памяти. В логах веб-сервера может быть информация о превышении каких-то ограничений.
Поняв, в чем проблема, можно более детально изучать ее причины.
Для использования инструментов нужно иметь права администратора. Эксперименировать на продакшене может быть нежелательно, тогда можно имитировать систему у себя, используя ab или siege для искуственного создания нагрузки. Разумеется, копия системы в вируталке может не соответствовать реальной системе на продакшене, так что top лучше бы запускать там.
Также, проблема может быть в настройках сервера, где стоит ограничение на число параллельных соединений. Если ты используешь хостинг - с большой вероятностью там ограничения есть, и зависят от тарифа.
Также, стоит проверить настройки Юи. Не используешь ли ты dev режим на продакшене?
> Например зашло 100 пользователей, каждый скрипт отъел 10 мб, итого 1ГБ. И если на сервере оперативы меньше, то для 101-го пользователя места уже не будет?
Примерно это так, но на деле все сложнее - так как есть виртуальная память и своппинг. Также, если скрипт работает быстро, то он сгенерирует страницу и освободит память. А вот если медленно то, да, число параллеьно работающих скриптов и потребление памяти начинает расти. Ну и на веб-сервере обычно есть лимит на число соединений, и лишние ставятся в очередь.
> Вместо инфы в самих классах я вижу что это сторонний хардкод где-то в середине программы, который хз каким функциям кормится и хз как с ним вообще работать
Ну у него идея в том, что в объектах не заложены никакие настройки, и ты задаешь их при создании. То есть вполне логично было бы например задавать ставки при создании компании - а почему нет? Или при создании работника, это тоже допустимо.
>>1046579
Тебе уже написали про =/==, а я дополню еще что ты непраивльно работаешь со строками. strlen() и $string[$i] работают только для строк на латиннице, не будут работать например с кирилицей: https://github.com/codedokode/pasta/blob/master/php/strings-utf8.md
>>1046466
peak memory usage это максимальное потребление памяти за время работы скрипта. Возможно, ты выбираешь больше данных из БД, например. Если тебя интересует поиск места, где выделяется много памяти, то придется заморочиться с xdebug, который умеет делать среди прочего 2 вещи:
- трассировка - при выполнении скрипта в файл логгируется вызов каждой функции, время ее выполнения и потребление памяти до/после вызова. Можно понять, где память выделается. Недостаток - лог может получиться огромный
- профайлинг - собирается статистика по вызовам функций, то есть для каждой функции считается число ее вызовов и суммарное/среднее время выполнения. Память правда это отследить не поможет.
Ну и есть еще "ручная" трассировка - расстановка вручную в коде команд, выводящих время выполнения/потребление памяти в текущий момент.
> Вообще проблема в том, что сайт недоступен при большом количестве посетителей. Это ведь возможно из-за нехватки оперативы? Например зашло 100 пользователей, каждый скрипт отъел 10 мб, итого 1ГБ. И если на сервере оперативы меньше, то для 101-го пользователя места уже не будет? Меня интересуют скорее общие советы и рекомендации при оптимизации, в первый раз с таким сталкиваюсь.
Это другая проблема и решается другим способом. Для начала надо сделать общий обзор ситуации, найти, где узкое место, каких ресурсов не хватает для обработки запроса. Для этого в линуксе есть много инструментов:
https://habrahabr.ru/post/114082/
https://habrahabr.ru/company/ua-hosting/blog/281519/
Ну например, если ты увидишь, что диск загружен на 100%, а CPU на 50% - станет понятно, что узкое место все же в дисковой подсистеме.
Заодно стоит проверить логи (/var/log) - там может быть например информация о завершении процессов OOM-killer'ом из-за нехватки памяти. В логах веб-сервера может быть информация о превышении каких-то ограничений.
Поняв, в чем проблема, можно более детально изучать ее причины.
Для использования инструментов нужно иметь права администратора. Эксперименировать на продакшене может быть нежелательно, тогда можно имитировать систему у себя, используя ab или siege для искуственного создания нагрузки. Разумеется, копия системы в вируталке может не соответствовать реальной системе на продакшене, так что top лучше бы запускать там.
Также, проблема может быть в настройках сервера, где стоит ограничение на число параллельных соединений. Если ты используешь хостинг - с большой вероятностью там ограничения есть, и зависят от тарифа.
Также, стоит проверить настройки Юи. Не используешь ли ты dev режим на продакшене?
> Например зашло 100 пользователей, каждый скрипт отъел 10 мб, итого 1ГБ. И если на сервере оперативы меньше, то для 101-го пользователя места уже не будет?
Примерно это так, но на деле все сложнее - так как есть виртуальная память и своппинг. Также, если скрипт работает быстро, то он сгенерирует страницу и освободит память. А вот если медленно то, да, число параллеьно работающих скриптов и потребление памяти начинает расти. Ну и на веб-сервере обычно есть лимит на число соединений, и лишние ставятся в очередь.
В каком формате выдает данные функция вычисления хеша в PHP и в каком требуется их указывать в HTML? Ведь сам по себе хеш - это большое двоичное число, которое трудно представить в виде символов. Обычно используют какую-то форму, например, шестнадцатеричный вид или base64.
В PHP - http://php.net/manual/ru/function.hash-file.php
> Возвращает строку, содержащую вычисленный хеш-код в шестнадцатеричной кодировке в нижнем регистре. Если raw_output задан как TRUE, то возвращается хеш-код в виде бинарных данных.
В HTML:
> You use the Subresource Integrity feature by specifying a base64-encoded cryptographic hash of a resource (file) you’re telling the browser to fetch, in the value of the integrity attribute of any <script> or <link> element.
> An integrity value begins with at least one string, with each string including a prefix indicating a particular hash algorithm (currently the allowed prefixes are sha256, sha384, and sha512), followed by a dash, and ending with the actual base64-encoded hash.
У тебя явно используются разные способы представления хеша, попробуй сдампить результат hash_file()
Эта опция вообще-то придумана для защиты от подмены контента на сторонних серверах. Если ты отдаешь файл со своего сервера, то смысла в этом мало и ты только зря на каждый запрос тратишь процессорное время на расчет хеша.
>>1046265
Попробуй погуглить библиотеки, можно на phptrends или packagist.org
>>1046214
С книгами надо учесть то, что по крайней мере раньше, многие книги по пхп писали эпичные быдлокодеры. В том числе какую-то от о'рейли, которую я несколько лет назад смотрел по диагонали. И надо помнить, что книга может учить тебя синтаксису PHP, но при этом упускать какие-то общие концепции программирования, вроде оформления кода, особенностей ООП, особенностей работы с формами и тд.
Скидывайте названия или ссылки на книги в тред, я потом как-нибудь пролистаю и прокомментирую.
В каком формате выдает данные функция вычисления хеша в PHP и в каком требуется их указывать в HTML? Ведь сам по себе хеш - это большое двоичное число, которое трудно представить в виде символов. Обычно используют какую-то форму, например, шестнадцатеричный вид или base64.
В PHP - http://php.net/manual/ru/function.hash-file.php
> Возвращает строку, содержащую вычисленный хеш-код в шестнадцатеричной кодировке в нижнем регистре. Если raw_output задан как TRUE, то возвращается хеш-код в виде бинарных данных.
В HTML:
> You use the Subresource Integrity feature by specifying a base64-encoded cryptographic hash of a resource (file) you’re telling the browser to fetch, in the value of the integrity attribute of any <script> or <link> element.
> An integrity value begins with at least one string, with each string including a prefix indicating a particular hash algorithm (currently the allowed prefixes are sha256, sha384, and sha512), followed by a dash, and ending with the actual base64-encoded hash.
У тебя явно используются разные способы представления хеша, попробуй сдампить результат hash_file()
Эта опция вообще-то придумана для защиты от подмены контента на сторонних серверах. Если ты отдаешь файл со своего сервера, то смысла в этом мало и ты только зря на каждый запрос тратишь процессорное время на расчет хеша.
>>1046265
Попробуй погуглить библиотеки, можно на phptrends или packagist.org
>>1046214
С книгами надо учесть то, что по крайней мере раньше, многие книги по пхп писали эпичные быдлокодеры. В том числе какую-то от о'рейли, которую я несколько лет назад смотрел по диагонали. И надо помнить, что книга может учить тебя синтаксису PHP, но при этом упускать какие-то общие концепции программирования, вроде оформления кода, особенностей ООП, особенностей работы с формами и тд.
Скидывайте названия или ссылки на книги в тред, я потом как-нибудь пролистаю и прокомментирую.
Ты понимаешь принцип работы протокола HTTP? Что мешает пользователю подсмотреть передаваемый с сервера трафик? Что мешает ему воспользоваться инструментами отладки в браузере?
>>1046188
ну так открыл бы документацию или код Юи и поискал, как оно выставляется. Мне не нравится идея использовать это поле. Для аякс-действий логично делать отдельные URL.
>>1046186
Вообще, это не очень хорошо что скачивается бесплатно. Кто будет писать хорошие книги, если за них никто не заплатит?
>>1046174
>>Первый - обрабатывать все в одном контроллере.
> То есть, в контроллере нужно делать проверку, какая именно форма сработала, так?
Да.
> Насколько я понял, для каждой страницы может быть только один контроллер. И это прибито гвоздями (должны совпадать их имена). То есть, для страницы view\site\clients.php есть один и только один контроллер с именем controllers\SiteController.php
Во-первых, нет, погугли про роуты (маршруты). То что ты описал - это схема по умолчанию (кстати, плохая для SEO) и можно задавать шаблоны URL страниц явно.
Также, для аякс-запроса ты можешь сделать вообещ любой URL, так как его в браузере пользвоатель не увидит и никого не волнует, как он выглядит.
> для страницы view\site\clients.php
Когда я говорил про второй вариант, я имел в виду отдельные страницы для каждой формы, с отдельными URL, контроллерами и шаблонами.
> Да. Но я не знаю, как их разделить. При отправке из view\site\clients.php, срабатывает контроллер SiteController, а в нём public function actionClients(). Как я уже писал, в уроках было написано, что это всё жёстко определено. Сам вижу, что пихать разные действия в одну фнкцию как-то неправильно, но как разделить не знаю.
Сделать для аякс-запроса другой URL и другой контроллер.
> Получается, можно и не туда передавать, а в отдельный метод?
Да
> Получается, что обновлением должен заниматься JS-скрипт?
Есть разные варианты, можно просто удалять строчку из DOM, можно генерировать новую таблицу и яваскриптом подменять содерждимое. Удалять одну строчку конечно лучше, так как при подмене могут быть нежелательные эффекты вроде потери фокуса, содержимого инпутов и тд.
> Блин, у меня мозги в трубочку от этой хрени заворачиваются.
Возможно, что проблем бы не было , если бы ты изучил бы отдельно яваскрипт и DOM, научился бы работать с формами и таблицами без фреймворка. Хотя я не уверен.
> У меня проблемы с пониманием, что движок делает за меня, а что нет.
нужно читать документацию (не уроки) и исходный код - благо Юи относительно простой и прямолинейный. Почитаешь исходный код, и думаю, вопросы отпадут.
Ты понимаешь принцип работы протокола HTTP? Что мешает пользователю подсмотреть передаваемый с сервера трафик? Что мешает ему воспользоваться инструментами отладки в браузере?
>>1046188
ну так открыл бы документацию или код Юи и поискал, как оно выставляется. Мне не нравится идея использовать это поле. Для аякс-действий логично делать отдельные URL.
>>1046186
Вообще, это не очень хорошо что скачивается бесплатно. Кто будет писать хорошие книги, если за них никто не заплатит?
>>1046174
>>Первый - обрабатывать все в одном контроллере.
> То есть, в контроллере нужно делать проверку, какая именно форма сработала, так?
Да.
> Насколько я понял, для каждой страницы может быть только один контроллер. И это прибито гвоздями (должны совпадать их имена). То есть, для страницы view\site\clients.php есть один и только один контроллер с именем controllers\SiteController.php
Во-первых, нет, погугли про роуты (маршруты). То что ты описал - это схема по умолчанию (кстати, плохая для SEO) и можно задавать шаблоны URL страниц явно.
Также, для аякс-запроса ты можешь сделать вообещ любой URL, так как его в браузере пользвоатель не увидит и никого не волнует, как он выглядит.
> для страницы view\site\clients.php
Когда я говорил про второй вариант, я имел в виду отдельные страницы для каждой формы, с отдельными URL, контроллерами и шаблонами.
> Да. Но я не знаю, как их разделить. При отправке из view\site\clients.php, срабатывает контроллер SiteController, а в нём public function actionClients(). Как я уже писал, в уроках было написано, что это всё жёстко определено. Сам вижу, что пихать разные действия в одну фнкцию как-то неправильно, но как разделить не знаю.
Сделать для аякс-запроса другой URL и другой контроллер.
> Получается, можно и не туда передавать, а в отдельный метод?
Да
> Получается, что обновлением должен заниматься JS-скрипт?
Есть разные варианты, можно просто удалять строчку из DOM, можно генерировать новую таблицу и яваскриптом подменять содерждимое. Удалять одну строчку конечно лучше, так как при подмене могут быть нежелательные эффекты вроде потери фокуса, содержимого инпутов и тд.
> Блин, у меня мозги в трубочку от этой хрени заворачиваются.
Возможно, что проблем бы не было , если бы ты изучил бы отдельно яваскрипт и DOM, научился бы работать с формами и таблицами без фреймворка. Хотя я не уверен.
> У меня проблемы с пониманием, что движок делает за меня, а что нет.
нужно читать документацию (не уроки) и исходный код - благо Юи относительно простой и прямолинейный. Почитаешь исходный код, и думаю, вопросы отпадут.
> Я вообще CSS и HTML5 по этим ресурсам изучаю:
Правильно. Я их тоже когда-то читал. Но хотел бы (нескромно) посоветовать мой курс задач на HTML/CSS из шапки - там ты сможешь проверить применение теоретических знаний на практике.
>>1046186
> В реальной практике пригодятся ли тебе такие тонкости?
Я когда-то тоже читал спецификации и мне это помогает - я понимаю, как будут работать те или иные CSS правила. А те, кто не понимает и ставят правила наугад, подбирая их опытным путем - тратят больше времени, верстка получается менее прочная.
>>1046153
Можно, так, хотя calc работает не везде: https://caniuse.com/#search=calc - на некоторых платформах только с конца 2013 года. А без поддержки calc сообщения будут обрезаться.
А что насчет варианта, когда высота формы неизвестна и определяется содержимым? Тут я вижу ровно 2 варианта:
- таблица с 2 ячейками. Плюс: хорошая поддержка в браузерах, есть вертикальное выравнивание. Проблема: если контейнером является ячейка таблицы, мы не можем использовать на ней position: absolute/relative, overflow, не можем для детей использовать проценты в height/max-height (так как проценты в height на непозиционированных элементах работают только когда высота родителя известна или вычисляется).
- flex позволяет сделать вертикальную ось, на ней 2 элемента, и задать политику распределения свободного места так, что нижняя часть получает сколько ей нужно, а верхняя - все остальное. Проблема: разные баги и поддержка браузеров.
Баги flex:
- http://css-live.ru/articles/uporyadochivanie-bagov-krossbrauzernosti-flexbox.html
- https://github.com/philipwalton/flexbugs
Поизучай описание flex, и заодно посмотри, может можно сделать какую-то альтернативу для старых браузеров (например чтобы в них просто выводилось 2 блока без overflow, и сообщения прокручивались за счет прокрутки всей страницы).
>>1046138
Смарти мне не очень нравится, он старый, и синтаксис не очень, и там изначально не было автоэкранирования, советую посмотреть твиг - он используется в Симфони, так что еще пригодится.
>>1046135
Если сложности с ООП, то стоит вернуться на шаг назад и порешать мои задачи на ООП из учебника. Также, внимательно изучить комментарии к самой задаче - там много информации.
> Я вообще CSS и HTML5 по этим ресурсам изучаю:
Правильно. Я их тоже когда-то читал. Но хотел бы (нескромно) посоветовать мой курс задач на HTML/CSS из шапки - там ты сможешь проверить применение теоретических знаний на практике.
>>1046186
> В реальной практике пригодятся ли тебе такие тонкости?
Я когда-то тоже читал спецификации и мне это помогает - я понимаю, как будут работать те или иные CSS правила. А те, кто не понимает и ставят правила наугад, подбирая их опытным путем - тратят больше времени, верстка получается менее прочная.
>>1046153
Можно, так, хотя calc работает не везде: https://caniuse.com/#search=calc - на некоторых платформах только с конца 2013 года. А без поддержки calc сообщения будут обрезаться.
А что насчет варианта, когда высота формы неизвестна и определяется содержимым? Тут я вижу ровно 2 варианта:
- таблица с 2 ячейками. Плюс: хорошая поддержка в браузерах, есть вертикальное выравнивание. Проблема: если контейнером является ячейка таблицы, мы не можем использовать на ней position: absolute/relative, overflow, не можем для детей использовать проценты в height/max-height (так как проценты в height на непозиционированных элементах работают только когда высота родителя известна или вычисляется).
- flex позволяет сделать вертикальную ось, на ней 2 элемента, и задать политику распределения свободного места так, что нижняя часть получает сколько ей нужно, а верхняя - все остальное. Проблема: разные баги и поддержка браузеров.
Баги flex:
- http://css-live.ru/articles/uporyadochivanie-bagov-krossbrauzernosti-flexbox.html
- https://github.com/philipwalton/flexbugs
Поизучай описание flex, и заодно посмотри, может можно сделать какую-то альтернативу для старых браузеров (например чтобы в них просто выводилось 2 блока без overflow, и сообщения прокручивались за счет прокрутки всей страницы).
>>1046138
Смарти мне не очень нравится, он старый, и синтаксис не очень, и там изначально не было автоэкранирования, советую посмотреть твиг - он используется в Симфони, так что еще пригодится.
>>1046135
Если сложности с ООП, то стоит вернуться на шаг назад и порешать мои задачи на ООП из учебника. Также, внимательно изучить комментарии к самой задаче - там много информации.
Можно в функции, тут главное - сделать объектную модель компании, а вывод таблицы это второстепенная задача.
>>1046127
Не объявляй функции внутри функций. Потому что при втором вызове функции и попытке создать вложенную функцию произойдет ошибка.
Не называй переменные ничего не значащими именами вроде array.
>>1046105
Поднять у себя на сервере тор-прокси и обращаться к нему.
>>1046089
Ты предыдущие замечания исправил? Какой смысл мне писать то же самое второй или третий раз?
Прочитай пожалуйста внимательно этот пост с замечаниями сначала >>1044779
>>1045770
> Меня немного смущает прибегать к флексу, потому что он не рассчитан на старые браузеры. Хотя в старых браузерах только слегка поломается отображение, это не так критично.
Можно попробовать "прогрессивное улучшение": в новых брузерах форма закреплена и прокручивается список, а в старых список без прокрутки и прокручивается вместе со всей страницей. Этого можно добиться, опираясь например на то, что старые браузеры игнорируют непонятные им свойства:
display: block; / для старых /
display: flex; / для новых, переопределит предыдущую строку /
желательно конечно оставлять комментарии в таких сложных случаях.
На практике обычно не заморачиваются с поддержкой старых браузеров (из соображений экономии), но для учебной задачи может быть полезно поломать голову.
> Установка обработчика, это часть контроллера или отображения?
Я не знаю, думаю, что можно делать и так и так, лишь бы это было единообразно в приложении.
> И чтобы одни виджеты могли взаимодействовать с другими, нужно передавать их как зависимость.
Как раз нет. Принцип в том что виджеты друг о друге не знают. Их связывает вышестоящий код, примерно так:
var contactList = new ContactListWidget(...);
var messagesList = new MessagesWidget(...);
contactList.addOnSelectHandler(function(contact) {
var messages = findMessages(contact);
messagesList.loadMessages(messages);
});
Тут 2 виджета не знают друг о друге. Их связывает вышестоящий код.
Еще связь может быть через модель и ее события - список сообщений содержит модель, и когда мы отправляем сообщение, модель генерирует событие, и список сообщений отрисовывает новое отправленное сообщение:
var messageModel = new MessageModel;
var messageList = new MessageListWidget(messageModel);
var formWidget = new SendMessageFormWidget();
formWidget.onMessageSent(function (message) {
messageModel.sendMessage(message);
});
то есть возможны разные схемы - мы можем снаружи управлять виджетом, либо же он может отслеживать состояние модели и отображать данные из нее. Ну например, тут мы сами отслеживаем отправку сообщения из формы, но могли бы передать в нее модель и сделать, чтобы она сама отправляла данные в модель.
> Создается впечатление, что должно быть что-то вроде роутера, который держит все виджеты и устанавливает обработчики на них, которые занимаются взаимодействиями между друг другом.
Это и есть вышестоящий контроллер. То есть виджет отвечает только за свою область, а контроллер манипулирует виджетами и отвечает за состояние всей страницы.
Можно в функции, тут главное - сделать объектную модель компании, а вывод таблицы это второстепенная задача.
>>1046127
Не объявляй функции внутри функций. Потому что при втором вызове функции и попытке создать вложенную функцию произойдет ошибка.
Не называй переменные ничего не значащими именами вроде array.
>>1046105
Поднять у себя на сервере тор-прокси и обращаться к нему.
>>1046089
Ты предыдущие замечания исправил? Какой смысл мне писать то же самое второй или третий раз?
Прочитай пожалуйста внимательно этот пост с замечаниями сначала >>1044779
>>1045770
> Меня немного смущает прибегать к флексу, потому что он не рассчитан на старые браузеры. Хотя в старых браузерах только слегка поломается отображение, это не так критично.
Можно попробовать "прогрессивное улучшение": в новых брузерах форма закреплена и прокручивается список, а в старых список без прокрутки и прокручивается вместе со всей страницей. Этого можно добиться, опираясь например на то, что старые браузеры игнорируют непонятные им свойства:
display: block; / для старых /
display: flex; / для новых, переопределит предыдущую строку /
желательно конечно оставлять комментарии в таких сложных случаях.
На практике обычно не заморачиваются с поддержкой старых браузеров (из соображений экономии), но для учебной задачи может быть полезно поломать голову.
> Установка обработчика, это часть контроллера или отображения?
Я не знаю, думаю, что можно делать и так и так, лишь бы это было единообразно в приложении.
> И чтобы одни виджеты могли взаимодействовать с другими, нужно передавать их как зависимость.
Как раз нет. Принцип в том что виджеты друг о друге не знают. Их связывает вышестоящий код, примерно так:
var contactList = new ContactListWidget(...);
var messagesList = new MessagesWidget(...);
contactList.addOnSelectHandler(function(contact) {
var messages = findMessages(contact);
messagesList.loadMessages(messages);
});
Тут 2 виджета не знают друг о друге. Их связывает вышестоящий код.
Еще связь может быть через модель и ее события - список сообщений содержит модель, и когда мы отправляем сообщение, модель генерирует событие, и список сообщений отрисовывает новое отправленное сообщение:
var messageModel = new MessageModel;
var messageList = new MessageListWidget(messageModel);
var formWidget = new SendMessageFormWidget();
formWidget.onMessageSent(function (message) {
messageModel.sendMessage(message);
});
то есть возможны разные схемы - мы можем снаружи управлять виджетом, либо же он может отслеживать состояние модели и отображать данные из нее. Ну например, тут мы сами отслеживаем отправку сообщения из формы, но могли бы передать в нее модель и сделать, чтобы она сама отправляла данные в модель.
> Создается впечатление, что должно быть что-то вроде роутера, который держит все виджеты и устанавливает обработчики на них, которые занимаются взаимодействиями между друг другом.
Это и есть вышестоящий контроллер. То есть виджет отвечает только за свою область, а контроллер манипулирует виджетами и отвечает за состояние всей страницы.
Маркер <?php работает только вне PHP кода. Ты используешь его внутри и потому он не рабоатет. МАнуал:
- http://php.net/manual/ru/language.basic-syntax.phpmode.php
- http://web.archive.org/web/20161119062218/http://www.phpinfo.su/articles/practice/shablony_v_php.html
Лучше всего сделать шаблон в отдельном файле, если это невозможно, то можно в функции, такого вида:
function renderTable(...) {
?>
HTML код
<?php
}
>>1045658
Это для тех, кто не хочет развиваться и искать удаленную работу.
>>1045587
по виртуальной машине как раз гайдов нет ((
>>1045537
Вообще, на сложные проекты лучше нанимать постоянных разработчиков, так как в них надо долго разбираться и фрилансеру с одной задачей просто это невыгодно, он набыдлокодит и уйдет, потом разгребай за ним.
>>1045428
Проще всего использовать относительные ссылки без хоста: /some/url. Если ты хочешь запускать проект из папки, придется как-то передавать с сервера или задавать в конфиге базовый путь.
>>1045400
> Апач наследует дескрипторы от какого-то "файла устройств"
Обычно сервисы запускает systemd, я думаю что на stdin он дает /dev/null, а stderr ловит и отправляет в логи. Но зависит от настроек. Глянул у себя в Дебиане:
$ ps ax --format 'pid,ppid,wchan,cmd' | grep apache
1707 1 poll_s /usr/sbin/apache2 -k start
17353 1707 inet_c /usr/sbin/apache2 -k start
17354 1707 inet_c /usr/sbin/apache2 -k start
# управляющий процесс
$ sudo ls -l /proc/1707/fd/
lr-x------ 1 root root 64 Aug 16 12:37 0 -> /dev/null
l-wx------ 1 root root 64 Aug 16 12:37 1 -> /dev/null
lr-x------ 1 root root 64 Aug 16 12:37 10 -> /dev/urandom
lrwx------ 1 root root 64 Aug 16 12:37 11 -> /tmp/.ZendSem.EPXa2M (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 12 -> /run/lock/apache2/proxy.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 13 -> /run/lock/apache2/rewrite-map.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 14 -> /run/lock/apache2/mpm-accept.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 2 -> /var/log/apache2/error.log
lrwx------ 1 root root 64 Aug 16 12:37 3 -> socket:[12811]
lrwx------ 1 root root 64 Aug 16 12:37 4 -> socket:[12812]
lr-x------ 1 root root 64 Aug 16 12:37 5 -> pipe:[116001]
l-wx------ 1 root root 64 Aug 16 12:37 6 -> pipe:[116001]
Видно, что 0 и 1 указывают на устройство /dev/null, а 2 - на лог (который скорее всего сам Апач и открыл). Ссылки на удаленные файлы - это временные файлы, которые создаются и сразу же удаляются из каталога, но остаются на диске, пока они открыты. Пайпы и сокеты либо используются для коммуникации с воркерами либо просто забыли закрыть.
# Рабочий процесс
$ sudo ls -l /proc/17353/fd/
lr-x------ 1 root root 64 Aug 16 12:39 0 -> /dev/null
l-wx------ 1 root root 64 Aug 16 12:39 1 -> /dev/null
lr-x------ 1 root root 64 Aug 16 12:39 10 -> /dev/urandom
lrwx------ 1 root root 64 Aug 16 12:39 11 -> /tmp/.ZendSem.EPXa2M (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 12 -> /run/lock/apache2/proxy.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 13 -> /run/lock/apache2/rewrite-map.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 14 -> /run/lock/apache2/mpm-accept.1707 (deleted)
lrwx------ 1 root root 64 Aug 16 12:39 15 -> anon_inode:[eventpoll]
l-wx------ 1 root root 64 Aug 16 12:39 2 -> /var/log/apache2/error.log
lrwx------ 1 root root 64 Aug 16 12:39 3 -> socket:[12811]
lrwx------ 1 root root 64 Aug 16 12:39 4 -> socket:[12812]
lr-x------ 1 root root 64 Aug 16 12:39 5 -> pipe:[116001]
l-wx------ 1 root root 64 Aug 16 12:39 6 -> pipe:[116001]
Кстати, я вспомнил про такую штуку, как утечка дескрипторов. Так как дескрипторы по умолчанию передаются по наследству, то привилегированный процесс, запустив непривилегированный процесс, может случайно передать ему лишние дескрипторы с доступом к каким-то файлам. Например, можно увидеть, что на ideone программе доступны какие-то открытые дескрипторы.
> Соответственно, если php-скрипт выполняется через консоль, то php наследует дескрипторы от неё
> Я правильно понимаю?
да.
> Затем я поменял домашний каталог у www-data в /etc/passwd на каталог сервера (на который я вроде давал неограниченный доступ chmod -R 777) и всё заработало - ключ успешно сгенерировался.
Вообще, достаточно было поменять переменную окружения HOME перед запуском программы - это не требует правки /etc/passwd.
> Почему-то в stderr передается обычный вывод, который не должен быть ошибкой:
Обычно у программ есть опции для управления уровнем подробностей при выводе данных. тут например https://www.gnupg.org/documentation/manpage.html есть опция quiet. А также опция "Use batch mode. Never ask, do not allow interactive commands."
> А что насчет получения ключей? На это не должно уходить много времени. Стоит ли здесь выполнять процесс асинхронно?
Ты намучаешься с асинхронными операциями в php, так что лучше их избегать. Тем более, ты вроде ключи хотел хранить в БД.
Маркер <?php работает только вне PHP кода. Ты используешь его внутри и потому он не рабоатет. МАнуал:
- http://php.net/manual/ru/language.basic-syntax.phpmode.php
- http://web.archive.org/web/20161119062218/http://www.phpinfo.su/articles/practice/shablony_v_php.html
Лучше всего сделать шаблон в отдельном файле, если это невозможно, то можно в функции, такого вида:
function renderTable(...) {
?>
HTML код
<?php
}
>>1045658
Это для тех, кто не хочет развиваться и искать удаленную работу.
>>1045587
по виртуальной машине как раз гайдов нет ((
>>1045537
Вообще, на сложные проекты лучше нанимать постоянных разработчиков, так как в них надо долго разбираться и фрилансеру с одной задачей просто это невыгодно, он набыдлокодит и уйдет, потом разгребай за ним.
>>1045428
Проще всего использовать относительные ссылки без хоста: /some/url. Если ты хочешь запускать проект из папки, придется как-то передавать с сервера или задавать в конфиге базовый путь.
>>1045400
> Апач наследует дескрипторы от какого-то "файла устройств"
Обычно сервисы запускает systemd, я думаю что на stdin он дает /dev/null, а stderr ловит и отправляет в логи. Но зависит от настроек. Глянул у себя в Дебиане:
$ ps ax --format 'pid,ppid,wchan,cmd' | grep apache
1707 1 poll_s /usr/sbin/apache2 -k start
17353 1707 inet_c /usr/sbin/apache2 -k start
17354 1707 inet_c /usr/sbin/apache2 -k start
# управляющий процесс
$ sudo ls -l /proc/1707/fd/
lr-x------ 1 root root 64 Aug 16 12:37 0 -> /dev/null
l-wx------ 1 root root 64 Aug 16 12:37 1 -> /dev/null
lr-x------ 1 root root 64 Aug 16 12:37 10 -> /dev/urandom
lrwx------ 1 root root 64 Aug 16 12:37 11 -> /tmp/.ZendSem.EPXa2M (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 12 -> /run/lock/apache2/proxy.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 13 -> /run/lock/apache2/rewrite-map.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 14 -> /run/lock/apache2/mpm-accept.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:37 2 -> /var/log/apache2/error.log
lrwx------ 1 root root 64 Aug 16 12:37 3 -> socket:[12811]
lrwx------ 1 root root 64 Aug 16 12:37 4 -> socket:[12812]
lr-x------ 1 root root 64 Aug 16 12:37 5 -> pipe:[116001]
l-wx------ 1 root root 64 Aug 16 12:37 6 -> pipe:[116001]
Видно, что 0 и 1 указывают на устройство /dev/null, а 2 - на лог (который скорее всего сам Апач и открыл). Ссылки на удаленные файлы - это временные файлы, которые создаются и сразу же удаляются из каталога, но остаются на диске, пока они открыты. Пайпы и сокеты либо используются для коммуникации с воркерами либо просто забыли закрыть.
# Рабочий процесс
$ sudo ls -l /proc/17353/fd/
lr-x------ 1 root root 64 Aug 16 12:39 0 -> /dev/null
l-wx------ 1 root root 64 Aug 16 12:39 1 -> /dev/null
lr-x------ 1 root root 64 Aug 16 12:39 10 -> /dev/urandom
lrwx------ 1 root root 64 Aug 16 12:39 11 -> /tmp/.ZendSem.EPXa2M (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 12 -> /run/lock/apache2/proxy.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 13 -> /run/lock/apache2/rewrite-map.1707 (deleted)
l-wx------ 1 root root 64 Aug 16 12:39 14 -> /run/lock/apache2/mpm-accept.1707 (deleted)
lrwx------ 1 root root 64 Aug 16 12:39 15 -> anon_inode:[eventpoll]
l-wx------ 1 root root 64 Aug 16 12:39 2 -> /var/log/apache2/error.log
lrwx------ 1 root root 64 Aug 16 12:39 3 -> socket:[12811]
lrwx------ 1 root root 64 Aug 16 12:39 4 -> socket:[12812]
lr-x------ 1 root root 64 Aug 16 12:39 5 -> pipe:[116001]
l-wx------ 1 root root 64 Aug 16 12:39 6 -> pipe:[116001]
Кстати, я вспомнил про такую штуку, как утечка дескрипторов. Так как дескрипторы по умолчанию передаются по наследству, то привилегированный процесс, запустив непривилегированный процесс, может случайно передать ему лишние дескрипторы с доступом к каким-то файлам. Например, можно увидеть, что на ideone программе доступны какие-то открытые дескрипторы.
> Соответственно, если php-скрипт выполняется через консоль, то php наследует дескрипторы от неё
> Я правильно понимаю?
да.
> Затем я поменял домашний каталог у www-data в /etc/passwd на каталог сервера (на который я вроде давал неограниченный доступ chmod -R 777) и всё заработало - ключ успешно сгенерировался.
Вообще, достаточно было поменять переменную окружения HOME перед запуском программы - это не требует правки /etc/passwd.
> Почему-то в stderr передается обычный вывод, который не должен быть ошибкой:
Обычно у программ есть опции для управления уровнем подробностей при выводе данных. тут например https://www.gnupg.org/documentation/manpage.html есть опция quiet. А также опция "Use batch mode. Never ask, do not allow interactive commands."
> А что насчет получения ключей? На это не должно уходить много времени. Стоит ли здесь выполнять процесс асинхронно?
Ты намучаешься с асинхронными операциями в php, так что лучше их избегать. Тем более, ты вроде ключи хотел хранить в БД.
Твои идея насчет "магии" (инкапсуляции) в общем-то верная, но надо учиывать некоторые вещи. Ты предлагаешь для депаратемнтов создать идентфикаторы (имена), но объект сам по себе обладает идентичностью и проще сохранить объект в переменную чем каждый раз искать его по имени.
> Проще же передать в качестве параметра название, а остальное за нас пусть делает сам объект.
Вот как раз проще может быть вызвать метод напрямую на объекте, сравни:
$company->getSalaryInDepartment('marketing');
и
$marketingDep = $company->findDepartment('marketing');
$marketingDep->getSalary();
С работниками будет еще неудобнее. Ты предлагаешь все действия собрать в компании, но лучше разделить ответственность между классами. Чтобы работник мог что-то сделать в пределах своей зоны отвественности.
По поводу добавления - тут есть 2 подхода (департамент создает работников либо добавляет существующих) и у них есть свои плюсы и минусы. Например, передача объекта снаружи позволяет нам как угодно настроить его перед добавлением. Также, если работники бывают разных классов, департамент может не знать, как их создавать, особенно если можно добавлять новые классы, о которых департамент пока не знает.
Также, а что насчет сценария "работник уволился из одного департамента или компании и перешел на работу с другую"? В случае когда департамент создает работников, это невозможно.
> . Везде по разному, конечно, но в некоторых организациях штатное расписание утверждается на целый год, а иногда в него не вносятся изменения и несколько лет подряд
Тем не менее это значит что оно может меняться. И - сюрприз - во второй части потребуется индивидуально менять зарплату. Я специально разделил задачу на 2 части, чтобы имитироват реальную работу программиста, который не знает, что завтра придет в голову менеджеру. И ты сможешь проверить, насколько гибок оказался твой код, насколько он готов к изменениям.
> спойлер
Ну вот видишь, моя задача очень жизненная, не то что эти глупые задачи про наследование моделей машин и кошечек с методом sayMeow() из других учебников.
Твои идея насчет "магии" (инкапсуляции) в общем-то верная, но надо учиывать некоторые вещи. Ты предлагаешь для депаратемнтов создать идентфикаторы (имена), но объект сам по себе обладает идентичностью и проще сохранить объект в переменную чем каждый раз искать его по имени.
> Проще же передать в качестве параметра название, а остальное за нас пусть делает сам объект.
Вот как раз проще может быть вызвать метод напрямую на объекте, сравни:
$company->getSalaryInDepartment('marketing');
и
$marketingDep = $company->findDepartment('marketing');
$marketingDep->getSalary();
С работниками будет еще неудобнее. Ты предлагаешь все действия собрать в компании, но лучше разделить ответственность между классами. Чтобы работник мог что-то сделать в пределах своей зоны отвественности.
По поводу добавления - тут есть 2 подхода (департамент создает работников либо добавляет существующих) и у них есть свои плюсы и минусы. Например, передача объекта снаружи позволяет нам как угодно настроить его перед добавлением. Также, если работники бывают разных классов, департамент может не знать, как их создавать, особенно если можно добавлять новые классы, о которых департамент пока не знает.
Также, а что насчет сценария "работник уволился из одного департамента или компании и перешел на работу с другую"? В случае когда департамент создает работников, это невозможно.
> . Везде по разному, конечно, но в некоторых организациях штатное расписание утверждается на целый год, а иногда в него не вносятся изменения и несколько лет подряд
Тем не менее это значит что оно может меняться. И - сюрприз - во второй части потребуется индивидуально менять зарплату. Я специально разделил задачу на 2 части, чтобы имитироват реальную работу программиста, который не знает, что завтра придет в голову менеджеру. И ты сможешь проверить, насколько гибок оказался твой код, насколько он готов к изменениям.
> спойлер
Ну вот видишь, моя задача очень жизненная, не то что эти глупые задачи про наследование моделей машин и кошечек с методом sayMeow() из других учебников.
Так как ты не сказал, в чем именно проблема могу дать только общий совет:
Попробуй переписать код внутри цикла примерно так:
- прибавляем проценты и комиссию к остатку долга (!не вычитаем ничего пока!)
- если остаток маленький, выплачиваем сколько осталось и уходим
- иначе платим 5000
«Платим» здесь значит уменьшаем долг и увеличиваем общую сумму выплаченного.
Я и так здесь помогаю иногда, просто на всё здесь отвечать некогда, надо пилить плагины, плагины сами себя не запилят. Всё что тебе нужно - это просто сильная мотивация изучить PHP.
>>1047034
Мельком прочел ответы и код, слишком много буков, но навскидку, основные ошибки автора этого кода:
1) Непонимание dependency injection в PHP, то есть в методе не нужно ничего создавать, в метод нужно передавать уже созданный экземпляр.
2) Злоупотребление абстракциями, читай про бритву Оккама. То есть не нужно плодить новые сущности. Если скажем, должности можно сделать отдельным двумерным массивом, то отдельный класс для этого не нужен. В статистике есть информационные критерии Акаике, Шварца и Байесовский критерий, используются для оценки моделей. Если у модели слишком много параметров, за это начисляется штраф. Т.е. нужно находить наипростейшее решение.
3) Нерациональное использование памяти и игнорирование стандартных генераторов, типа yield. Зачем изобретать велосипеды с foreach, когда есть array_search().
http://ideone.com/sw0x5e
Как сделать цикл так, чтобы обойтись одним echo?
Спасибо за ответ по оптимизациям и поиску уязвимых мест, буду изучать новые инструменты и выяснять причины. Очень заинтересовало нагрузочное тестирование.
Добавлю: не вводя новую переменную и т.п.
>Генераторы тут при чем? И dependency injection?
Это ответ не конкретно тебе, а вообще всей дискуссии. Вот здесь >>1046695 первая картинка, нерационально хранить массив в переменной класса. Если бы там было не 3-5 департаментов, а например миллион департаментов, то все эти экземпляры, что он наплодил, сидели бы в памяти. Для этого и нужен yield, который выдает 1 департамент за раз.
Там же в методе addDepartment() образец dependency injection, там нужно передавать ссылку на уже созданный экземпляр (хотя лучше бы просто избавится от класса и сделать массивом).
Я конечно извиняюсь, но почему ты не использовал блок-схему предложенную в теории к задаче. Я уж не говорю про однострочник, который ты в в условие for впихнул, я конечно учусь, но это же тяжело читать постороннему человеку.
Ладно, это эмоции :-)
Я делал по схеме, предложенной изначально, если ее адаптировать к твоему варианту, то как-то так получается (идеология начальной задачи) - брейк - выход из цикла делаем когда кредит выплачен. Ты хотел одно эхо? Ты его получил!
http://ideone.com/E3Ez3g
Вот мой вариант, можно сказать традиционный. На момент написания у меня возникли сомнения из-за жестко забитого верхнего предела цикла, якобы заведомого недостижимого, но так опять же задача-то про другое была.
http://ideone.com/h8vBRT
И скромно замечу - в твоем коде сильно не хватает пробелов, не говоря уже об инициализации переменных.
Всё, разобрался, это сам сервер Cloud9 возвращает помимо 'ok' кучу всякой хуйни, а впаше нужно именно две буквы. Теперь бы ещё понять, как это отключить.
Понятно. Надо было создать .htaccess и туда прописать настройки. Вроде он перестал ошибки запихивать и вк больше не выёбывается. Спасибо, пацаны.
>Но это именно так и есть, потому что это расписание утверждается на самом высоком уровне, ведь это и фонд оплаты труда, и налогообложение и т.д. Так что для меня реальность, что это не меняется ежесекундно. Аналогично и с свойствами должностей-профессий. Оклады меняются еще реже, чем штатная численность.К сожалению.
Ты не должен так рассуждать. Тебя и пытаются научить тут тому, что у тебя в системе должна быть гибкость и расширяемость и везде должна быть подстелена соломка. Изменились зарплаты? Уволены сотрудники? Кончилось кофе? Менеджерам дали премии? У тебя должен быть предусмотрен механизм для этого! Пусть не впилен заранее, но инфа в объектах должна храниться так, что бы у тебя была возможность легко без кучи костылей и не нарушая общий стиль всё это вписать, понимаешь?
Просто по задумке у тебя сферическая контора в вакууме, а в реальности в итоге будет любой кавардак случаться. И лучше всё это дело учитывать. Попробуй решить антикризисные меры со своим кодом и ты всё мб поймешь о чем я, вангую что с твоим подходм у тебя будет дикий обсер и демотивация :( (Но ты не переживай, ведь антикризисные меры из дополнения к этой задаче - и есть реальная задача которую нужно тут решить - понимаешь?)
Хм, ну так тоже можно.
Я знаю, что криво оформил, делал наспех.
Мне не нравится конструкция ОПа из-за условия ($month<=20). Пример: анон взял кредит 400000, а выплачивает по 20000. Тогда код ОПа выведет только первые 20 месяцев, а мой вариант будет работать как надо.
И еще я не понял, зачем ОП использует конструкцию вроде {$month} вместо простого вывода переменной без фигурных скобок.
https://stackoverflow.com/questions/5647461/how-do-i-send-a-post-request-with-php
погуглил за тебя, не благодари
Проблема в том что у меня нет никакого понимания как это все работает, все эти потоки и контексты. Я могу конечно скопипастить, нихуя не поняв что я сделал, но как то не круто это. Но все равно спасибо.
http://php.net/manual/ru/intro.stream.php
http://php.net/manual/ru/function.stream-context-create.php
Ну вот прочитай, мб разберешься. Я тоже не могу понять некоторые вещи полностью и воспринимаю просто как инструмент. Ты же не пытаешься копать глубже в нативные функции языка и как например внутри устрен там mt_rand() или mb_substr()? Просто знаешь что подать на вход и что ожидается при этом на выход. Вот и тут так же, прочитай и пойми как использовать и этого будет достаточно. Это даже знать не обязательно. Я на самом деле это не сейчас нагуглил, а 2 года назад на работе, когда нужно было сделать рассылку post запросов с нашего сервиса на чужой. Просто держу теперь в голове и когда надо будет снова воспользуюсь.
>2) Злоупотребление абстракциями, читай про бритву Оккама. То есть не нужно плодить новые сущности.
А как это тогда лучше реализовывать? Типа вместо наследования и всего такого ты делаешь 1 класс общий и отдельный класс с отличающимися конфигурациями?
А при создании экземпляра просто передаешь инфу о том какая конфигурация нужна?
Накидал кливо но быстро http://ideone.com/4vjBfF
Я то думал что везде нужно обмазываться в итоге наследованием и parent::__construct()
Что-то типа этого http://ideone.com/C94uot
Начнешь плодить классы для каждой отдельной сущности и увязнешь в абстракциях и неочевидных ошибках.
Сложно и неочевидно это.
В итоге так и предполагается, что никто в итоге не использует "чистое ООП" с тем как оно там задумано изначально с цепочками наследований и прочими инкапсуляциями, придется всю жизнь наверное в это "въезжать"
http://ideone.com/nuAT2v
Я не могу согласиться. Во-первых, откуда взялся миллион департаментов? В задаче такого нет. Во-вторых, как тут помогут генераторы? Попробуй посмотреть внимательно на сценарий использования класса и подумать, как туда вставить генераторы - ты увидишь, что это как минимум сильно усложнит код и скорее всего еще и замедлит работу программы.
Наконец, если ж на то пошло, то работников там еще больше - их тоже генераторами делать? Нужно адекватно оценить задачу, а не стремиться использовать генераторы, просто, чтобы они были.
И еще, у нас ведь департаменты генерируются на основе массива с названиями, даже если ты добавишь генератор, названия все равно надо будет где-то хранить.
В общем, это вредный совет. Не нужно использовать генераторы ни для департаментов, ни для чего-то еще.
> Там же в методе addDepartment() образец dependency injection
Нет, Департамент - это не зависимость класса Компания. Это сущности, которыми она управляет. Разберись внимательнее, что такое зависимость.
> там нужно передавать ссылку на уже созданный экземпляр (хотя лучше бы просто избавится от класса и сделать массивом).
Чем лучше? Массив только хуже, в нем не определена структура, нет комментариев, методов и констант. Единственное, что кода чуть меньше получается, но зато он менее понятный и с ним труднее работать. И это задача на ООП, чтобы учиться исопльзовать классы, а ты предалагешь тут вместо этого на массивах написать нечитаемую лапшу.
Ты какие-то вредные советы даешь.
> Зачем изобретать велосипеды с foreach, когда есть array_search().
Потому что array_search не ищет по полю класса.
> То есть не нужно плодить новые сущности. Если скажем, должности можно сделать отдельным двумерным массивом, то отдельный класс для этого не нужен.
Можно сделать разные классы для профессий, и можно сделать один класс. Оба варианта допустимы. Если у профессий будет разное поведение, то придется делать отдельные классы.
>>1047341
Тут код нелогичный, так как ты передаешь классу больше данных, чем ему требуется. Зачем ему массив с зарплатой, если можно сразу передать значение зарплаты.
>>1047432
У тебя какой-то массиво-ориентированный код. Ну то есть вместо того, чтобы сделать в классе поле salary, ты сделал в нем массив со свойствами и зарплату надо искать через $this->current_worker['salary']. Зачем ты внутрь объекта кладешь массив, если можно просто сделать поле $this->salary? Это усложнение, которое не дает никакой выгоды, такое ощущение, что тебе объекты не нравятся, а нравится везде использовать массивы.
Из-за использования массива непонятно, что именно хранится в поле current_worker, твой код труднее понять, так как мы должны найти все места, где в это поле что-то записывается.
Функция get_worker_salary зачем-то содержит return false. Это усложеняет ее использование, так как мы теперь должны при каждом вызове проверять ее результат. Не нужно так делать.
Профессии стоило бы сделать константами.
Наконец, а исходной задаче была еще идея про то, что можно добавлять новые профессии, не трогая старый код, а просто добавляя классы.
>>1047484
> В итоге так и предполагается, что никто в итоге не использует "чистое ООП" с тем как оно там задумано изначально с цепочками наследований и прочими инкапсуляциями, придется всю жизнь наверное в это "въезжать"
Это просто он усложняет код.
Я не могу согласиться. Во-первых, откуда взялся миллион департаментов? В задаче такого нет. Во-вторых, как тут помогут генераторы? Попробуй посмотреть внимательно на сценарий использования класса и подумать, как туда вставить генераторы - ты увидишь, что это как минимум сильно усложнит код и скорее всего еще и замедлит работу программы.
Наконец, если ж на то пошло, то работников там еще больше - их тоже генераторами делать? Нужно адекватно оценить задачу, а не стремиться использовать генераторы, просто, чтобы они были.
И еще, у нас ведь департаменты генерируются на основе массива с названиями, даже если ты добавишь генератор, названия все равно надо будет где-то хранить.
В общем, это вредный совет. Не нужно использовать генераторы ни для департаментов, ни для чего-то еще.
> Там же в методе addDepartment() образец dependency injection
Нет, Департамент - это не зависимость класса Компания. Это сущности, которыми она управляет. Разберись внимательнее, что такое зависимость.
> там нужно передавать ссылку на уже созданный экземпляр (хотя лучше бы просто избавится от класса и сделать массивом).
Чем лучше? Массив только хуже, в нем не определена структура, нет комментариев, методов и констант. Единственное, что кода чуть меньше получается, но зато он менее понятный и с ним труднее работать. И это задача на ООП, чтобы учиться исопльзовать классы, а ты предалагешь тут вместо этого на массивах написать нечитаемую лапшу.
Ты какие-то вредные советы даешь.
> Зачем изобретать велосипеды с foreach, когда есть array_search().
Потому что array_search не ищет по полю класса.
> То есть не нужно плодить новые сущности. Если скажем, должности можно сделать отдельным двумерным массивом, то отдельный класс для этого не нужен.
Можно сделать разные классы для профессий, и можно сделать один класс. Оба варианта допустимы. Если у профессий будет разное поведение, то придется делать отдельные классы.
>>1047341
Тут код нелогичный, так как ты передаешь классу больше данных, чем ему требуется. Зачем ему массив с зарплатой, если можно сразу передать значение зарплаты.
>>1047432
У тебя какой-то массиво-ориентированный код. Ну то есть вместо того, чтобы сделать в классе поле salary, ты сделал в нем массив со свойствами и зарплату надо искать через $this->current_worker['salary']. Зачем ты внутрь объекта кладешь массив, если можно просто сделать поле $this->salary? Это усложнение, которое не дает никакой выгоды, такое ощущение, что тебе объекты не нравятся, а нравится везде использовать массивы.
Из-за использования массива непонятно, что именно хранится в поле current_worker, твой код труднее понять, так как мы должны найти все места, где в это поле что-то записывается.
Функция get_worker_salary зачем-то содержит return false. Это усложеняет ее использование, так как мы теперь должны при каждом вызове проверять ее результат. Не нужно так делать.
Профессии стоило бы сделать константами.
Наконец, а исходной задаче была еще идея про то, что можно добавлять новые профессии, не трогая старый код, а просто добавляя классы.
>>1047484
> В итоге так и предполагается, что никто в итоге не использует "чистое ООП" с тем как оно там задумано изначально с цепочками наследований и прочими инкапсуляциями, придется всю жизнь наверное в это "въезжать"
Это просто он усложняет код.
Напишите функции:
- isFinished(p) - возвращает, завершен ли промис (то есть true если он rejected/resolved, false если еще pending)
- extractValue(p) - если промис разрезолвлен, возвращает его значение, иначе null
- isResolved(p) - возвращает true если промис разрезолвлен
Писать можно как на PHP, так и на JS, в случае с JS предполагаем что промисы соответствуют спецификации Promise/A+, в случае PHP - что они соответствуют PromiseInterface: https://github.com/reactphp/promise/blob/master/src/PromiseInterface.php
Поправка: я тут заметил, что с JS промисами это скорее всего невозможно, так что задача остается только для PHP промисов.
Ну ок, сейчас все распишу.
Текущая задача: Есть таблица абонентов, им выделяется определенный диапазон адресов (например 20.20.20.6/29, 8 адресов тобишь), также есть таблица услуг абонентов, услуги могут быть следующие: ТВ, телефония и интернет. В таблице услуг на ТВ и телефонию выделяется один фиксированный IP, а на интернет диапазон и хранится в базе он в таком виде: 20.20.20.6-20.20.20.8. На странице нужно отобразить какой адрес под какую услугу выделен и какие остались свободны. Получается если просто делать выборку из таблицы услуг, то получится получить только все занятые адреса (и то с адресами для интернета приходится повозиться). А вот чтобы показать свободные адреса приходится сильно говнокодить. https://ideone.com/LJ6xdf
А на счет библиотеки, вот например нашел такую https://github.com/tapmodo/php-ipv4
Игнайтер при инициализации библиотеки запускает конструктор, а конструктор этой либы запрашивает какое-то число типа long, я в принципе не знаю чего он хочет. Ну вот как-то так.
И еще вопрос такой: чтобы не пилить в контроллерах методы по 500 строк их лучше разбить на отдельные функции, и вызывать их в контроллере. А сами функции где лучше описывать, в этом же контроллере, в модели или в библиотеке?
>Сложности ООП
Да я даже до того раздела не дошел, дропнул на моменте когда нужно было надрачивать код, чтобы закрепить знания все, практики 5.5% было. Дропнул не потому, что не интересно стало, а потому что проблемы начались. Прошел год почти, но я все вспоминаю уже, щас посмотрю ОП-шапку и после того как ее закончу с CSS и HTML то потом буду ебош делать PHP и C. После чего перекачусь в C++ и на стороне буду закреплять PHP. Мне никакой мотивации не нужно, у меня жопа прижата. Извиняюсь за каламбур.
А да, книжки пока что те что я скинул, на стороне Керниган с Ритчи. А так я думаю более чем достаточно в спецификации PHP будет написано.
У кого тоже есть желание написать сначала PhP и потом вы опоминаетесь и осознаете что это зашквар?
к примеру у меня есть таблица city (id, country_id) и есть таблица city_language (city_id, language_id, name)
в таблице city поле `id` автоинкремент и праймери ключ, в city_language есть праймери ключ city_id который является внешним ключом city.id. Так вот вопрос, у меня сгенерированная форма (actionIndex со стандартным _search) предлагает мне искать по ID, я к примеру хочу добавить поле City Name который бы искал совпадения по таблице city_language по полю name, как мне расширить поиск на другую модель/таблицу?
А всё, нашел способ. Но готов послушать советы
Есть таблица с двумя полями: id и client.
Есть готовая переменная $client с именем клиента.
Всё что мне нужно от жизни - вытащить из таблицы соответствующий ему id.
Элементарно же. Но я уже четвёртый час и так и сяк пробую, нихрена не получается.
А ещё непонятно, почему не получается. Консоль показывает только "SyntaxError: JSON.parse: unexpected character at line 3 column 1 of the JSON data", ну или ещё какою невнятную ерунду. А что там внутре у него происходит - чёрт его знает.
Таки родил велосипед.
$rows = (new \yii\db\Query())
->select(['id'])
->from('clients')
->where(['client'=>$nameNewClient])
->one();
foreach ($rows as $key => $value) {
$rows = $value;
}
$json_data = array(0 => $rows);
echo json_encode($json_data);$rows = (new \yii\db\Query())
->select(['id'])
->from('clients')
->where(['client'=>$nameNewClient])
->one();
foreach ($rows as $key => $value) {
$rows = $value;
}
$json_data = array(0 => $rows);
echo json_encode($json_data);
Случайно код дважды скопировал.
Как мне сделать СплСтак с несколькими элементами, не пуша их по-одному?
Есть класс Student c переменными $name, $surname (и другими). Есть конструктор:
function __construct($name, $surnametб ...)
{
$this->name = $name;
$this->surname = $surname;
...
}
Всё бы ничего но: при про получении данных из бд я хочу, что бы эти данные заносились в класс Student, ставлю мод FETCH_CLASS, но выдаётся ошибка, что я не заполняю конструктор:
PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function Student::__construct(), 0 passed and exactly 8 expected in
При этом если убрать конструктор из класса, то всё будет работать. Почему так происходит я понимаю, вопрос вот в чём: как сохранить конструктор в классе и работать с FETCH_CLASS?
Свойство $id есть. Вопрос не в этом.
Вопрос в том, что при использовании метода FETCH_CLASS мне нужно заполнить конструктор, но мой конструктор нужно заполнять данными, которые я получаю из FETCH_CLASS
Что-то у тебя в package.json все перепутано: в dependencies стоит webpack-dev-server, а сам webpack в devDependencies. Ты пробовал на продакшене использовать npm install --production и проверить, все ли работает?
В ридми не указана используемая версия JS. Это лучше указывать, чтобы знать, какие вещи можно использовать и какие нельзя.
.babelrc наверно лишний, если опции указаны в конфиге webpack.
У тебя подключен webpack.HotModuleReplacementPlugin - но используется ли он? Как я понимаю из документации, приложение должно явно поддерживать возможность обновления. Также, непонятно, зачем добавлен NoErrorsPlugin - вроде судя по описанию он предназначен для игнорирования синтаксических ошибок.
Насчет директорий - думаю, не стоит создавать лишние уровни вложенности и стоит вместо этого сделать в корне папки client, server, и может быть, там же templates и public (или внутри server).
В ридми не описана команда для генерации собранного скрипта (там есть watch, но надо бы описать и build).
https://github.com/enotocode/birthday_reminder/blob/master/src/client/index.js - тут у тебя слишком много импортов, многие из которых не используются.
Насчет импортов - а не лучше было бы писать не так:
createStore()
а так?
redux.createStore()
Чтобы было видно, откуда импортировано имя? Ну и заодно не придется константы по одной импортировать.
> Replace the path in RewriteBase directive with path that you use to access to the project
В Apache2.4 вроде бы RewriteBase уже не нужен. Ну и я бы советовал сделать публичную папку корневой.
Хорошо бы сделать поддержку для встроенного в PHP сервера - для отладки он вполне годится и не требует возни с Апачом ( http://php.net/manual/ru/features.commandline.webserver.php ) . Можно это сделать лиюо файлом server.php, либо в index.php с помощью проверки php_sapi() или как-то так.
Далее, почему-то объем собранного скрипта составляет 1Мб. Не многовато ли для простейшего приложения? Там конечно что-то можно выжать минификацией, может быть сжатием, но конечно, как-то жирно получается.
Скомпилировано все как-то плохо, потому что у меня в Chromium выдается ошибка на аргументы по умолчанию (в reducers.js). То есть у тебя на выходе получается ES6 файл, а не ES5. А хорошо бы еще для ES3 браузеров добавить полифиллы для поддержки ES5 (хотя я сомневаюсь, что это возможно, так как в ES5 есть штуки вроде defineProperty(), которые по идее в ES3 эмулировать нельзя. Но в ES3 браузерах были похожие нестандартные функции, может полифилл их использует). Если что, информация по браузерам: https://caniuse.com/#search=es5
Я нашел, как исправить проблему, но предоставлю тебе возможность подумать самостоятельно.
Вообще, я предлагаю все же подумать, поискать варианты в dev режиме грузить файлы напрямую, без сборки (предположим, что у разработчика браузер поддерживает нужные стандарты). Вообще, интересно, что разработчики там сделали source maps и их поддержку, а о такой простой вещи, как загрузка кода напрямую, не подумали.
Также, если хочется усложнить жизнь (но улучшить ее для пользователей), можно попробовать собирать 2 версии:
- для пользователей с поддержкой ES6 (уровень поддержки: https://caniuse.com/#search=es6 )
- для пользователей с ES5
- для пользователей без ES5 дополнительно давать полифиллы в надежде, что заработает
Транспиляция скорее всего делает код менее эффективным и более громоздким, и если браузер поддерживает ES6, почему бы не давать ему более оптимальный код в ES6?
Если компиляци идет долго, можно было бы в теории сохранять промежуточные результаты и перекомпилировать только изменившиеся файлы - в Си так делают уже лет 40. Но не знаю, поддерживает ли вебпак такое. Впрочем, это open source, никто не запрещает допилить такую возможность. Также в режиме watch промежуточные результаты можно кешировать в оперативной памяти.
Далее, после транспиляции кода в ES5 у меня происходит ошибка тут:
> Uncaught TypeError: Cannot read property 'userName' of undefined
> validate @ VM909 validator.js:21
Это из-за того, что ты там перекрываешь внешнюю переменную локальной:
> https://github.com/enotocode/birthday_reminder/blob/master/src/client/services/validator.js
> inputsValidationRules = inputsValidationRules)
Это компилируется в
> var inputsValidationRules = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : inputsValidationRules;
И естественно, не работает (кстати, интересно, зачем там проверка на undefined - мне кажется, ее быть не должно - может это баг в babel или все же я что-то перепутал?).
Дальше, мне не нравится идея что надо где-то в исходниках править пути. Я предлагаю либо сделать папку public корневой, либо передавать пути с сервера. Зачем клиентскому коду свой конфиг? Пусть нужные данные передает сервер.
Ну например, проще всего было бы при выдаче страницы из PHP передавать нужные данные:
<script>
startApplication(param1, param2, param3);
</script>
Дальше, ошибки никак не отображаются. Например, я задал неправильный URL сервера, и при попытке отправки формы ничего не происходит. Должна быть реакция на действия пользователя, почитай урок https://github.com/codedokode/pasta/blob/master/js/ajax.md
Что касается валидации, которая идет в фоне - в принципе, там сетевые ошибки не так критичны (так как валидация будет произведена при отправке формы). Там можно сделать так:
- если поле прошло валидацию, показывать зеленую галочку
- если не пррошло - ошибку
- если не удалось проверить - ничего не показывать
Но при нажатии кнопки реакция должна быть в любом случае - как индикатор ожидания, так и ошибка/успех.
Пока пользователь ничего не ввел, показывать ошибки нелогично. Ошибки должны появляться как реакция на действия пользователя. Также, на валидацию стоит поставить ограничение частоты запросов (вроде debounce или аналогичной функции).
Ответ на запрос на валидацию (POST /validate) очень странный - 201 Created. Код ответа выбран неправильно.
В случае с ошибками, может быть так, что имя поля у ошибки указано неправильно, или ошибка относится к невидимому полю, или не относится ни к какому полю. Нужно обеспечить вывод и таких сообщений тоже.
Серверная часть
https://github.com/enotocode/birthday_reminder/blob/master/src/server/Services/autoloader.php - это надо убрать и заменить на автозагрузку в композере. Желательно также использовать PSR-4.
Сообщения о PHP ошибках, возможно, стоило бы писать еще и в стандартный логгер (который идет в лог веб-сервера). У тебя лог к тому же прямо в public лежит зачем-то. Как я понимаю, в дев режиме этот лог может быть полезен, но зачем он на продакшене, когда можно писать ошибки в стандартный лог?
https://github.com/enotocode/birthday_reminder/blob/master/src/server/public/index.php#L29
> $data = json_decode($request->getContent(), true);
> $request->request->replace(is_array($data) ? $data : array());
Нет проверки на ошибку декодирования, также, а не лучше ли класть данные в какое-то отдельное место, чтобы не путать с обычными пост-данными?
Папку Classes надо переименовать так как название ничего не значит.
> static public function loadValidatorMetadata(ClassMetadata $metadata) {
У этого подхода есть недостаток в том, что нельзя делать правила валидации, которые используют сервисы через DI. Например, проверки по БД. Или вызов внешнего класса-валидатора.
Что-то у тебя в package.json все перепутано: в dependencies стоит webpack-dev-server, а сам webpack в devDependencies. Ты пробовал на продакшене использовать npm install --production и проверить, все ли работает?
В ридми не указана используемая версия JS. Это лучше указывать, чтобы знать, какие вещи можно использовать и какие нельзя.
.babelrc наверно лишний, если опции указаны в конфиге webpack.
У тебя подключен webpack.HotModuleReplacementPlugin - но используется ли он? Как я понимаю из документации, приложение должно явно поддерживать возможность обновления. Также, непонятно, зачем добавлен NoErrorsPlugin - вроде судя по описанию он предназначен для игнорирования синтаксических ошибок.
Насчет директорий - думаю, не стоит создавать лишние уровни вложенности и стоит вместо этого сделать в корне папки client, server, и может быть, там же templates и public (или внутри server).
В ридми не описана команда для генерации собранного скрипта (там есть watch, но надо бы описать и build).
https://github.com/enotocode/birthday_reminder/blob/master/src/client/index.js - тут у тебя слишком много импортов, многие из которых не используются.
Насчет импортов - а не лучше было бы писать не так:
createStore()
а так?
redux.createStore()
Чтобы было видно, откуда импортировано имя? Ну и заодно не придется константы по одной импортировать.
> Replace the path in RewriteBase directive with path that you use to access to the project
В Apache2.4 вроде бы RewriteBase уже не нужен. Ну и я бы советовал сделать публичную папку корневой.
Хорошо бы сделать поддержку для встроенного в PHP сервера - для отладки он вполне годится и не требует возни с Апачом ( http://php.net/manual/ru/features.commandline.webserver.php ) . Можно это сделать лиюо файлом server.php, либо в index.php с помощью проверки php_sapi() или как-то так.
Далее, почему-то объем собранного скрипта составляет 1Мб. Не многовато ли для простейшего приложения? Там конечно что-то можно выжать минификацией, может быть сжатием, но конечно, как-то жирно получается.
Скомпилировано все как-то плохо, потому что у меня в Chromium выдается ошибка на аргументы по умолчанию (в reducers.js). То есть у тебя на выходе получается ES6 файл, а не ES5. А хорошо бы еще для ES3 браузеров добавить полифиллы для поддержки ES5 (хотя я сомневаюсь, что это возможно, так как в ES5 есть штуки вроде defineProperty(), которые по идее в ES3 эмулировать нельзя. Но в ES3 браузерах были похожие нестандартные функции, может полифилл их использует). Если что, информация по браузерам: https://caniuse.com/#search=es5
Я нашел, как исправить проблему, но предоставлю тебе возможность подумать самостоятельно.
Вообще, я предлагаю все же подумать, поискать варианты в dev режиме грузить файлы напрямую, без сборки (предположим, что у разработчика браузер поддерживает нужные стандарты). Вообще, интересно, что разработчики там сделали source maps и их поддержку, а о такой простой вещи, как загрузка кода напрямую, не подумали.
Также, если хочется усложнить жизнь (но улучшить ее для пользователей), можно попробовать собирать 2 версии:
- для пользователей с поддержкой ES6 (уровень поддержки: https://caniuse.com/#search=es6 )
- для пользователей с ES5
- для пользователей без ES5 дополнительно давать полифиллы в надежде, что заработает
Транспиляция скорее всего делает код менее эффективным и более громоздким, и если браузер поддерживает ES6, почему бы не давать ему более оптимальный код в ES6?
Если компиляци идет долго, можно было бы в теории сохранять промежуточные результаты и перекомпилировать только изменившиеся файлы - в Си так делают уже лет 40. Но не знаю, поддерживает ли вебпак такое. Впрочем, это open source, никто не запрещает допилить такую возможность. Также в режиме watch промежуточные результаты можно кешировать в оперативной памяти.
Далее, после транспиляции кода в ES5 у меня происходит ошибка тут:
> Uncaught TypeError: Cannot read property 'userName' of undefined
> validate @ VM909 validator.js:21
Это из-за того, что ты там перекрываешь внешнюю переменную локальной:
> https://github.com/enotocode/birthday_reminder/blob/master/src/client/services/validator.js
> inputsValidationRules = inputsValidationRules)
Это компилируется в
> var inputsValidationRules = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : inputsValidationRules;
И естественно, не работает (кстати, интересно, зачем там проверка на undefined - мне кажется, ее быть не должно - может это баг в babel или все же я что-то перепутал?).
Дальше, мне не нравится идея что надо где-то в исходниках править пути. Я предлагаю либо сделать папку public корневой, либо передавать пути с сервера. Зачем клиентскому коду свой конфиг? Пусть нужные данные передает сервер.
Ну например, проще всего было бы при выдаче страницы из PHP передавать нужные данные:
<script>
startApplication(param1, param2, param3);
</script>
Дальше, ошибки никак не отображаются. Например, я задал неправильный URL сервера, и при попытке отправки формы ничего не происходит. Должна быть реакция на действия пользователя, почитай урок https://github.com/codedokode/pasta/blob/master/js/ajax.md
Что касается валидации, которая идет в фоне - в принципе, там сетевые ошибки не так критичны (так как валидация будет произведена при отправке формы). Там можно сделать так:
- если поле прошло валидацию, показывать зеленую галочку
- если не пррошло - ошибку
- если не удалось проверить - ничего не показывать
Но при нажатии кнопки реакция должна быть в любом случае - как индикатор ожидания, так и ошибка/успех.
Пока пользователь ничего не ввел, показывать ошибки нелогично. Ошибки должны появляться как реакция на действия пользователя. Также, на валидацию стоит поставить ограничение частоты запросов (вроде debounce или аналогичной функции).
Ответ на запрос на валидацию (POST /validate) очень странный - 201 Created. Код ответа выбран неправильно.
В случае с ошибками, может быть так, что имя поля у ошибки указано неправильно, или ошибка относится к невидимому полю, или не относится ни к какому полю. Нужно обеспечить вывод и таких сообщений тоже.
Серверная часть
https://github.com/enotocode/birthday_reminder/blob/master/src/server/Services/autoloader.php - это надо убрать и заменить на автозагрузку в композере. Желательно также использовать PSR-4.
Сообщения о PHP ошибках, возможно, стоило бы писать еще и в стандартный логгер (который идет в лог веб-сервера). У тебя лог к тому же прямо в public лежит зачем-то. Как я понимаю, в дев режиме этот лог может быть полезен, но зачем он на продакшене, когда можно писать ошибки в стандартный лог?
https://github.com/enotocode/birthday_reminder/blob/master/src/server/public/index.php#L29
> $data = json_decode($request->getContent(), true);
> $request->request->replace(is_array($data) ? $data : array());
Нет проверки на ошибку декодирования, также, а не лучше ли класть данные в какое-то отдельное место, чтобы не путать с обычными пост-данными?
Папку Classes надо переименовать так как название ничего не значит.
> static public function loadValidatorMetadata(ClassMetadata $metadata) {
У этого подхода есть недостаток в том, что нельзя делать правила валидации, которые используют сервисы через DI. Например, проверки по БД. Или вызов внешнего класса-валидатора.
Клиентская часть
Мне не нравится то, что функции плохо организованы и просто беспорядочно свалены внутри модуля. Ну например, возьмем services/validator.js. Вот API этого модуля:
- validator(dispatch, index, valueName, value, inputsValidationRules = inputsValidationRules)
- validator.asyncValidation(fn)
- validator.VALIDATE_WITH_API
- validator.validateWithAPI(value, valueName, index, dispatch)
Зачем экспортируется константа VALIDATE_WITH_API? Почему модуль содержит несколько функций и они налеплены на другую функцию? Мне кажется, export default function стоит применять только там, где экспортируется одна функция. А так получается просто свалка.
Я тебе советую прежде чем писать код, попробовать описать АПИ модуля. Тогда сразу будут видны проблемы.
Ну вот возьмем, например, основную функцию validate(dispatch, index, valueName, value, inputsValidationRules = inputsValidationRules). Почему ей надо передавать dispatch? Почему она зависит от редукса? Она отдельно от него провести валидацию не способна? Я вижу тут явное нарушение разделения ответсвенности, ты разнес код по файлам, но без всякой логики, потому что эти файлы все спутаны друг с другом и должны быть объединены в один.
Также, если это не универсальная функция валидации, а функция валидации формы регистрации, то правила должны быть в ней заложены, не надо требовать их передавать отдельно.
Нужно сделать хорошее АПИ модулей. То, что сейчас, это просто свалка из функций.
Посмотрим еще на apiCall.js:
- apiCall(middleware) - непонятно, почему для обращения к АПИ нужно middleware редукса. А что, без редукса обратиться к АПИ нельзя?
- apiCall.CONNECTION_ERROR
- apiCall.RECIEVE_RESPONSE
- apiCall.recieveResponse(dispatch, json, action)
Тоже лишние зависимости от редукса.
Далее, неправильно обрабатываются ошибки в промисах:
> fetch(url, request)
> .then(
> ...
> error => console.log('Error while connecting to server', error)
> .then(
> json => dispatch(recieveResponse(dispatch, json, action)),
> error => dispatch(connectionError(error))
Функция function (error) { return console.log('Error while connecting to server', error); } превращает ошибку в успешный результат (null) и на следующем шаге это приведет к вызову функции json => dispatch(recieveResponse(dispatch, json, action)).
Изучи промисы получше, можно например почитать статью https://www.promisejs.org/implementing/
Дальше, этот синтаксис мне кажется очень неудачным:
> const apiCall = store => next => action => {
Тут написано const, но объявляется не константа, а функцию (возвращающая функцию). Зачем запутывать читателя? Ну и выражение store => next => action => довольно тяжело воспринимать.
Вообще, стрелочные функции были придуманы для простых выражений вроде [].map(x => x * 2); Мне кажется, что не стоит использовать их везде, так как ухудшается читабельность.
Правила валидации на клиенте логично было бы генерировать из серверных.
Также, надо будет потом проверить, нет ли проблем с потреблением памяти при большом объеме данных или действий. Ну например, создать 10, 100, 1000 дней рождений и замерить потребление памяти браузером. Или пропустить через диспетчер 10, 100, 1000 действий и посмотреть, не течет ли (неограниченно увеличивается) потребление памяти.
Также, я мельком почитал гитхаб redux-thunk и сразу увидел там проблему: там никак не учитывается асинхронность функций. Ну например, когда ты делаешь 2 запроса к АПИ, сначала может придти ответ на второй, а только потом на первый. К примеру:
- пользователь вводит неправильное значение, отправляется запрос 1
- пользователь вводит правильное значение, отправляется запрос 2
- приходит ответ на запрос 2, отображается индикатор, что все правильно
- приходит ответ на запрос 1, отображается ошибка
Тут конечно можно решить проблему костылем, присылая в ответе значение поля и сравнивая его перед выводом. Но это конечно не решит другие аналогичные проблемы.
> return {inputs: state.inputs}; //TODO: is it copy or link?
Если inputs это объект или массив, то, видимо, передача ссылки.
Меня еще немного беспокоит вот эта штука: https://github.com/enotocode/birthday_reminder/blob/master/src/client/Login/initialState.js - по мере усложнения приложения тут будет гигантский массив.
https://github.com/enotocode/birthday_reminder/blob/master/src/client/Login/actions.js
Тут по названиям функций трудно понять, что они делают. Что, например, делает функция userCreated? Обычно функции называют в стиле сделайЧтоТо().
Ну и наверно неплохо бы подумать об автоматизированном тестировании приложения.
Клиентская часть
Мне не нравится то, что функции плохо организованы и просто беспорядочно свалены внутри модуля. Ну например, возьмем services/validator.js. Вот API этого модуля:
- validator(dispatch, index, valueName, value, inputsValidationRules = inputsValidationRules)
- validator.asyncValidation(fn)
- validator.VALIDATE_WITH_API
- validator.validateWithAPI(value, valueName, index, dispatch)
Зачем экспортируется константа VALIDATE_WITH_API? Почему модуль содержит несколько функций и они налеплены на другую функцию? Мне кажется, export default function стоит применять только там, где экспортируется одна функция. А так получается просто свалка.
Я тебе советую прежде чем писать код, попробовать описать АПИ модуля. Тогда сразу будут видны проблемы.
Ну вот возьмем, например, основную функцию validate(dispatch, index, valueName, value, inputsValidationRules = inputsValidationRules). Почему ей надо передавать dispatch? Почему она зависит от редукса? Она отдельно от него провести валидацию не способна? Я вижу тут явное нарушение разделения ответсвенности, ты разнес код по файлам, но без всякой логики, потому что эти файлы все спутаны друг с другом и должны быть объединены в один.
Также, если это не универсальная функция валидации, а функция валидации формы регистрации, то правила должны быть в ней заложены, не надо требовать их передавать отдельно.
Нужно сделать хорошее АПИ модулей. То, что сейчас, это просто свалка из функций.
Посмотрим еще на apiCall.js:
- apiCall(middleware) - непонятно, почему для обращения к АПИ нужно middleware редукса. А что, без редукса обратиться к АПИ нельзя?
- apiCall.CONNECTION_ERROR
- apiCall.RECIEVE_RESPONSE
- apiCall.recieveResponse(dispatch, json, action)
Тоже лишние зависимости от редукса.
Далее, неправильно обрабатываются ошибки в промисах:
> fetch(url, request)
> .then(
> ...
> error => console.log('Error while connecting to server', error)
> .then(
> json => dispatch(recieveResponse(dispatch, json, action)),
> error => dispatch(connectionError(error))
Функция function (error) { return console.log('Error while connecting to server', error); } превращает ошибку в успешный результат (null) и на следующем шаге это приведет к вызову функции json => dispatch(recieveResponse(dispatch, json, action)).
Изучи промисы получше, можно например почитать статью https://www.promisejs.org/implementing/
Дальше, этот синтаксис мне кажется очень неудачным:
> const apiCall = store => next => action => {
Тут написано const, но объявляется не константа, а функцию (возвращающая функцию). Зачем запутывать читателя? Ну и выражение store => next => action => довольно тяжело воспринимать.
Вообще, стрелочные функции были придуманы для простых выражений вроде [].map(x => x * 2); Мне кажется, что не стоит использовать их везде, так как ухудшается читабельность.
Правила валидации на клиенте логично было бы генерировать из серверных.
Также, надо будет потом проверить, нет ли проблем с потреблением памяти при большом объеме данных или действий. Ну например, создать 10, 100, 1000 дней рождений и замерить потребление памяти браузером. Или пропустить через диспетчер 10, 100, 1000 действий и посмотреть, не течет ли (неограниченно увеличивается) потребление памяти.
Также, я мельком почитал гитхаб redux-thunk и сразу увидел там проблему: там никак не учитывается асинхронность функций. Ну например, когда ты делаешь 2 запроса к АПИ, сначала может придти ответ на второй, а только потом на первый. К примеру:
- пользователь вводит неправильное значение, отправляется запрос 1
- пользователь вводит правильное значение, отправляется запрос 2
- приходит ответ на запрос 2, отображается индикатор, что все правильно
- приходит ответ на запрос 1, отображается ошибка
Тут конечно можно решить проблему костылем, присылая в ответе значение поля и сравнивая его перед выводом. Но это конечно не решит другие аналогичные проблемы.
> return {inputs: state.inputs}; //TODO: is it copy or link?
Если inputs это объект или массив, то, видимо, передача ссылки.
Меня еще немного беспокоит вот эта штука: https://github.com/enotocode/birthday_reminder/blob/master/src/client/Login/initialState.js - по мере усложнения приложения тут будет гигантский массив.
https://github.com/enotocode/birthday_reminder/blob/master/src/client/Login/actions.js
Тут по названиям функций трудно понять, что они делают. Что, например, делает функция userCreated? Обычно функции называют в стиле сделайЧтоТо().
Ну и наверно неплохо бы подумать об автоматизированном тестировании приложения.
посчитать выплату в текущем месяце;
сделать выплату;
вывести результаты;
если долг дошел до нуля, выходим;
>>1047178
Можно поменять условие с числа месяцев на то, что долг дошел до нуля, но хорошо бы предусмотреть защиту на случай, когда долг растет быстрее, чем его выплачивают.
> зачем ОП использует конструкцию вроде {$month} вместо простого вывода
Почему бы и нет.
>>1047185
Потоки - это абстракция, в которую можно читать и/или писать данные (например, с помощью fread/fwrite). Например: поток данных из сетевого сокета, тело ответа на HTTP запрос, открытый файл, поток распакованных данных из сжатого файла (данные сами распаковываются по мере чтения). Абстракция сделана для того, чтобы одной и той же функцией можно было работать с разными видами потоков и не делать на каждый вид потока свои функции.
У потоков есть также специальный синтаксис для указания типа и параметров потока. Например, file://somefile.txt или http://example.com. Можно добавлять свои типы потоков, написав свой stream wrapper.
Мануал http://php.net/manual/ru/book.stream.php
Контекст потока - это дополнительные опции потока, у разных типов потоков они свои: http://php.net/manual/ru/stream.contexts.php . Ну например, для потока получения данных по HTTP они могут задавать таймаут ожидания ответа от сервера.
Если тебе нужно получать данные по HTTP, возможно что тебе лучше разобраться с потоками, а потом взять библиотеку с удобным интерфейсом, например, guzzle.
>>1047807
Лучше следовать MVC и вынести операции с данными в отдельные классы (модели, сервисы), а в контроллерах оставить обработку запроса и отдачу ответа.
посчитать выплату в текущем месяце;
сделать выплату;
вывести результаты;
если долг дошел до нуля, выходим;
>>1047178
Можно поменять условие с числа месяцев на то, что долг дошел до нуля, но хорошо бы предусмотреть защиту на случай, когда долг растет быстрее, чем его выплачивают.
> зачем ОП использует конструкцию вроде {$month} вместо простого вывода
Почему бы и нет.
>>1047185
Потоки - это абстракция, в которую можно читать и/или писать данные (например, с помощью fread/fwrite). Например: поток данных из сетевого сокета, тело ответа на HTTP запрос, открытый файл, поток распакованных данных из сжатого файла (данные сами распаковываются по мере чтения). Абстракция сделана для того, чтобы одной и той же функцией можно было работать с разными видами потоков и не делать на каждый вид потока свои функции.
У потоков есть также специальный синтаксис для указания типа и параметров потока. Например, file://somefile.txt или http://example.com. Можно добавлять свои типы потоков, написав свой stream wrapper.
Мануал http://php.net/manual/ru/book.stream.php
Контекст потока - это дополнительные опции потока, у разных типов потоков они свои: http://php.net/manual/ru/stream.contexts.php . Ну например, для потока получения данных по HTTP они могут задавать таймаут ожидания ответа от сервера.
Если тебе нужно получать данные по HTTP, возможно что тебе лучше разобраться с потоками, а потом взять библиотеку с удобным интерфейсом, например, guzzle.
>>1047807
Лучше следовать MVC и вынести операции с данными в отдельные классы (модели, сервисы), а в контроллерах оставить обработку запроса и отдачу ответа.
Не надо писать в контроллере стены кода, лучше бы сделать класс-сервис и метод в нем для получения нужной информации.
Также, ты там переусложнил, разве в Юи нет простого метода вроде findBy? Ну и может быть написать SQL запрос будет лаконичнее чем городить конструкции в Query Builder.
>>1047934
Вставлять циклом
>>1048053
> как сохранить конструктор в классе и работать с FETCH_CLASS?
Наверно, никак. Надо получать данные массивом и из него создавать объект. FETCH_CLASS работает только с пустым конструктором и публичными полями.
Вообще, что касается ООП, там в PDO все довольно плохо, такое ощущение, что его авторы сами ООП толком не поняли. Ну например, по умолчанию FETCH_CLASS сначала создает объект, заполняет поля, и только потом вызывает конструктор.
Есть в этом смысл? Зачем так извращаться?
Ничего не смог нагулить по этой теме.
Как я думаю пикрил должнен работать: если категория является подкатегорией родителя с айди 4 - напечатать название, если нет - цифру 1.
Как она работает: если категория является подкатегорией родителя - напечатать название И ВСЕГДА печатает единицу.
Почему так происходит?
Лень разбирать детально твой код, но как я понял:
$customer_internet_ip = array(); //Массив назначенных абоненту адресов, определенных под интернет
$customer_ip = array(); //Массив назначенных абоненту адресов
$customer_free_ip = array(); //Массив свободных адресов абонента
Эти 3 массива из базы прилетает в метод?
Если ты не можешь средствами мускуля сделать 1 грамотный запрос, и в итоге вынужден средствами пхп фильтровать/пересортировывать или еще как-то работать с массивами - это нормально я думаю, ты же не можешь неделю ебаться с одним запросом если плохо знаешь мускуль.
И вообще это ведь не в контроллере у тебя этот код?
Если в контроллере то вынеси это в отдельный метод либы по работе с ip (если нет то создай её): https://www.codeigniter.com/userguide3/general/creating_libraries.html
В контроллере потом просто пишешь:
$this->load->library('ip_stuff');
$view_data['free_ips'] = $this->ip_stuff->get_free_ip($customer_internet_ip, $customer_ip, $customer_free_ip);
//$view_data - массив инфы который ты собираешь для вьюхи
$this->load->view('uslugi', $view_data);
То есть ты просто портянку кода перенес в отдельную функцию и поручаешь его ей.
>Игнайтер при инициализации библиотеки запускает конструктор
Во первых вот форк этой либы и чел что-то там работает над ней https://github.com/colinodell/php-ipv4
Во вторых ты когда создаешь новый объект у себя в коде, то это не игнайтер что-то требует, а у самого класса есть метод конструктор. Он на то и конструктор, что автоматически выполняется при создании объекта.
Пикрил1 - ты ему должен скормить ip туда
Там же есть примеры использования - пикрил2.
Или ты просто либу засунул в папочку и игнайтер сам пытается создать объект без твоих выдзовов либы где-либо в коде? Возможно ты как-то не так подключаешь, такого не должно быть.
>>1047807
>чтобы не пилить в контроллерах методы по 500 строк их лучше разбить на отдельные функции, и вызывать их в контроллере.
Да. Любой код который ты можешь обернуть в функцию лучше обернуть и вынести в модель/либу.
Я в основном работал в проектах, где в основном никто не делал никакие либы и хелперы. Всё было тупо сваливалось в модели и там лежало. Сам до сих пор не понимаю в чем будет критическая разница между складыванием какой-нибудь функции в модель или в либу кроме семантики.
Еще если у тебя например есть код который ты используешь в каждом методе контроллера, то ты его должен выносить в конструктор контроллера.
Скажем для доступа в контроллер личного кабинета юзер должен быть обязательно залогиненным, вот проверку на то что он залогиненность(куки или что у вас там) ты и выносишь в конструктор этого контроллера. И когда юзер будет ломиться в любой метод этого контроллера например /site/user/cabinet - то будет выполняться проверка его подлинности на этапе конструктора контроллера user
Далее если у тебя например есть код который используется во всех контроллерах на сайте: например подгрузка языка. То ты должен создать какой-то базовый контроллер который будет наследоваться от CI_controller - в его конструктор вынести этот код, и этого базового уже наследовать свои контроллеры, а в их конструкторах подгружать их конструкторы через parent::__construct
https://www.codeigniter.com/userguide3/general/core_classes.html
Лень разбирать детально твой код, но как я понял:
$customer_internet_ip = array(); //Массив назначенных абоненту адресов, определенных под интернет
$customer_ip = array(); //Массив назначенных абоненту адресов
$customer_free_ip = array(); //Массив свободных адресов абонента
Эти 3 массива из базы прилетает в метод?
Если ты не можешь средствами мускуля сделать 1 грамотный запрос, и в итоге вынужден средствами пхп фильтровать/пересортировывать или еще как-то работать с массивами - это нормально я думаю, ты же не можешь неделю ебаться с одним запросом если плохо знаешь мускуль.
И вообще это ведь не в контроллере у тебя этот код?
Если в контроллере то вынеси это в отдельный метод либы по работе с ip (если нет то создай её): https://www.codeigniter.com/userguide3/general/creating_libraries.html
В контроллере потом просто пишешь:
$this->load->library('ip_stuff');
$view_data['free_ips'] = $this->ip_stuff->get_free_ip($customer_internet_ip, $customer_ip, $customer_free_ip);
//$view_data - массив инфы который ты собираешь для вьюхи
$this->load->view('uslugi', $view_data);
То есть ты просто портянку кода перенес в отдельную функцию и поручаешь его ей.
>Игнайтер при инициализации библиотеки запускает конструктор
Во первых вот форк этой либы и чел что-то там работает над ней https://github.com/colinodell/php-ipv4
Во вторых ты когда создаешь новый объект у себя в коде, то это не игнайтер что-то требует, а у самого класса есть метод конструктор. Он на то и конструктор, что автоматически выполняется при создании объекта.
Пикрил1 - ты ему должен скормить ip туда
Там же есть примеры использования - пикрил2.
Или ты просто либу засунул в папочку и игнайтер сам пытается создать объект без твоих выдзовов либы где-либо в коде? Возможно ты как-то не так подключаешь, такого не должно быть.
>>1047807
>чтобы не пилить в контроллерах методы по 500 строк их лучше разбить на отдельные функции, и вызывать их в контроллере.
Да. Любой код который ты можешь обернуть в функцию лучше обернуть и вынести в модель/либу.
Я в основном работал в проектах, где в основном никто не делал никакие либы и хелперы. Всё было тупо сваливалось в модели и там лежало. Сам до сих пор не понимаю в чем будет критическая разница между складыванием какой-нибудь функции в модель или в либу кроме семантики.
Еще если у тебя например есть код который ты используешь в каждом методе контроллера, то ты его должен выносить в конструктор контроллера.
Скажем для доступа в контроллер личного кабинета юзер должен быть обязательно залогиненным, вот проверку на то что он залогиненность(куки или что у вас там) ты и выносишь в конструктор этого контроллера. И когда юзер будет ломиться в любой метод этого контроллера например /site/user/cabinet - то будет выполняться проверка его подлинности на этапе конструктора контроллера user
Далее если у тебя например есть код который используется во всех контроллерах на сайте: например подгрузка языка. То ты должен создать какой-то базовый контроллер который будет наследоваться от CI_controller - в его конструктор вынести этот код, и этого базового уже наследовать свои контроллеры, а в их конструкторах подгружать их конструкторы через parent::__construct
https://www.codeigniter.com/userguide3/general/core_classes.html
Так а я так и сколотил, уже, засунул в отдельную переменную и сравниваю отдельной строкой. Но почему изначальный if так не может?
Хуйня какая-то, чет я не понимаю азов.
>Но почему изначальный if так не может?
Может. Просто ты вместо сравнения делаешь непонять что.
Ой, хуй с ним. Я уже тупо вставил
> $test = true
>if $test == true
>echo "2"
И он где-то выдает одну двойку а где-то две. Видимо что-то там с массивом связано, а я слишком туп чтоб понять.
Эх хуле я такой безрукий по жизни.
Да лано, без этой фигни сделаю, там не критично. Спс.
>Или ты просто либу засунул в папочку и игнайтер сам пытается создать объект без твоих выдзовов либы где-либо в коде?
Именно так. Просто загрузил либу и сразу эксепшн. Все остальные библиотеки, которыми я пользуюсь без конструкторов, поэтому у меня с этим проблема возникла.
Вообще, все что ты мне написал я и до этого знал, но все равно спасибо за развернутый ответ. Теперь буду выносить все методы в библиотеки.
Посмотрел этот курс от ntschool, как советовал анон. Выяснил для себя, что по видеоурокам учиться гораздо быстрее и легче.
Теперь вопрос: куда идти дальше? Проходить учебник на сайте ОПа? Пока что смотрю другие видео с того канала и начал проходить курс на html academy.
Откопал древний пост на хабре, и там автор пишет, что главное - это учиться у профессионалов. А как отличить хорошие книги от плохих?
Сам хочу работать в фулстеке, но для начала хорошо выучить именно php.
И еще - что скажете про этот сайт?: http://getjump.me/ru-php-the-right-way/
Мне кажется ты что-то не так сделал.
я поключал в игнайтере вот эту вот тяжеловесную поебень: https://github.com/tecnickcom/TCPDF
и там тоже есть конструктор и всё такое. И всё работало. Возможно ты всё таки как-то не так подключаешь. Ты через автолоад подгружаешь левые либы или в коде вручную?
Советую начать писать код - стало быть решать задачи. Учиться у профессионалов - это когда речь уже идет о сложной архитектуре. Когда ты решаешь простые задачи - это как арифметика - тут почти невозможно решать что-то неправильно. Поэтому просто сел и пошел хуярить код одну задачу за другой.
Ты же всегда будешь складывать 2 и 2 как 2+2, а не как (210 + 210)/10 правильно?
Положил файлы Address, Subnet и Subnetiteraror в папку libraries/ipv4. Подключаю так: $this->load->library('ipv4/Address');
И на этой строке эксепшн об отсутствующем параметре конструктора. Я понимаю что при подключении библиотеки вторым параметром можно передавать настройки для конструктора, но я все еще не знаю что за $long от меня хочет этот конструктор.
Избавляемся от JOIN'a избавляемся от больших условий: WHERE product_option in (2,4,6,25,52,152) OR product_option = 152 OR product_option = 242 и тд, мне идея только счас пришла в голову, дома попробую создать таблицу и поиграться с выборками.
К примеру: опция Цвет: товар А в есть в следующих цветах:
Синий - Нет, Красный - Да, Зеленый - нет, фиолетовый - да, Черный ДА
В битовой маске это будет как 01011
и с помощью операторов битового сравнения мы можем создавать выборки OR, EQUAL одним оператором
Не беда с книгами. Выбираешь всё что по PHP7 и читаешь. Рекомендую Котерова например.
Спасибо.
А это действительно сработает! И поиск будет быстрее в n раз. Где n - количество таблиц.
Вот эти задачи: http://archive-ipq-co.narod.ru/l1/mou-ikkai.html
Если не можешь то иди с 0 перечитывай всё и прорешивай начиная с http://archive-ipq-co.narod.ru/l1/conditions.html
>>1048611
>Вот это реши раз знаешь примитивы
Получилось как-то так: https://ideone.com/ZKc2yk
Реклама, я двачера издалека вижу.
Я проиграл, он даже комменты трет.
И второй вопрос: при покупке, есть ли смысл переносить купленное из корзины в отдельную таблицу покупок? Или просто в корзине сделать маркер "куплено"?
В опенкарте например реализовали таким образом, что корзина хранится сериализованной строкой в колонке `cart` в таблице customer. При оформлении заказа создается запись в `order` и в `order_product`. По итогу в `cart` записывается пустой массив.
Не особо удобно, так как если админ захочет посмотреть статистику - кто какой товар добавлял в корзину - придется доставать все записи, прогонять по ансериалайзу и сортировать-фильтровать. А так да, если заводить отдельную таблицу по корзине покупателя один-к-одному, то при создании заказа - надо очищать запись в таблице хранения корзин для конкретного юзера и создавать запись в order.
Подробнее? Понятно, что в таблицу корзины идёт ид, ид юзера и ид товара. Что ещё?
>>1048655
>А так да, если заводить отдельную таблицу по корзине покупателя один-к-одному, то при создании заказа - надо очищать запись в таблице хранения корзин для конкретного юзера и создавать запись в order.
Ну а как быть с разрастанием? Допустим, в магазине 1000 человек в день что-то кладёт в корзину (даже если не все покупают). Спустя год таблица корзины разрастётся неимоверно. Чистить старые записи?
Даже если у тебя миллион юзеров с корзинами - это не большая база, правильно расставив индексы - ты даже не заметишь замедления в работе. Можно к примеру в таблице cart сделать user_id первичным ключом, выборка будет мгновенной так как mysql будет знать наперед что запись единичная в данной таблице
>в таблице cart сделать user_id первичным ключом
Не, не получится. Там же для каждого товара отдельная строка. Если Вася добавтил себе в корзину карандаши и ручки, то в таблице cart будет две строки: одна для карандашей и одна для ручек. И user_id у них будет одинаковый.
Сделай cart и cart_product, customer будет 1-к-1 к cart, а cart к cart_product будет один-ко-многим
Блин, анон, я хз конечно, не хочу никого обижать, но я просмотрел 2 часа 12 минут его видео и получил ровно хуй целых ноль десятых знаний. Даже первая страница опсайта дала в 100 раз больше инфы чем он.
Кого обижать и чем? Это же не мои видео, лол. Просто анон посоветовал, я и посмотрел.
Насчет второго не уверен. Разве что на опсайте больше практики.
Не посоветую, в том то и дело что сам ищу и никак найти не могу. Год назад тут кидали ссылку на LoftScool курсы. Вот там просто 10 из 10. С самого нуля сразу тебя заставляют писать скрипты, к 8 видео ты уде на Laravel пишешь, к 12 у тебя уже 3 своих пректа. Но они вовремя лавочку прикрыли, теперь там только за деньги доступ.
Я тебя понял, спасибо.
>$num = 51516665.7419439799951156;
>var_dump((string)$num);
>string(15) "51516665.741944"
Как перевести флоат в строку как есть, без вот этого окргулительства? Почему 15(16) символов, а остальное теряется?
Хочу себе IDE с такой подсветкой ошибок.
>Почему 15(16) символов, а остальное теряется?
Потому что в Си по умолчанию флоат выводится с 6 знаками после запятой.
NYET
>$num = 0.112233445566778899999;
>string(16) "0.11223344556678"
>$num = 1.112233445566778899999;
>string(15) "1.1122334455668"
>$num = 10.112233445566778899999;
>string(15) "10.112233445567"
>$num = 999.112233445566778899999;
>string(15) "999.11223344557"
6 знаков после запятой тут даже рядом не плавали.
Мне кажется компилятор обрезает твой флоат, поэтому и не получается привести к строке:
сравни
>var_dump(sprintf('%.16F', $num));
>string(25) "51516665.7419439777731895"
>должно быть 51516665.7419439799951156
Компьютер хранит дробные числа в приближенном формате: N знаков (мантисса) и положение запятой (экспонента). Причем еще и в двоичном коде (процессору так удобнее, внутри компьютера все представлено в двоичном виде, в виде нулей и единиц).
Вообще, исторически когда-то были числа с фиксированной запятой: отводилась память под хранение N цифр (пусть для примера N = 16), и запятая была жестко зафиксирована в определенной позиции (например, в середине):
00000001,00000000
99999999,00000000
00000000,50000000
Соответственно, сложение/вычитание таких чисел можно проводить так, как будто бы запятой нет, а при умножении/делении приходилось сдвигать результат.
Но эта система имеет недостаток: она позволяет представлять небольшой диапазон чисел, в данном случае от 10 ^ -8 до 10^9. Потому придумали другую схему, с плавающей запятой, где отдельно хранятся знаки числа (мантисса) и положение запятой (экспонента). То есть дробное число представляется в виде пары небольших целых чисел.
То есть число переводится в двоичный вид, из него берется сколько-то знаков и положение запятой и сохраняется. И в памяти число представляется как (мантисса) x 2 ^ (экспонента). Мантисса это обычно дробное число, > 0 и < 1, то есть она всегда начинается с нуля и запятой, потому компьютер их не сохраняет, а хранит только идущие после запятой числа.
Наверно с двоичными числами тебе это трудно представить себе, потому для аналогии можно представить то же самое в десятичном виде - принципы будут те же. Допустим, у нас используется десятичная система и 4 знака для мантиссы. Тогда числа бы представлялись так:
15600 = 0,156 x 10 ^ 5 (то есть в памяти сохраняется M=156, E=5)
0.002 = 0,2 x 10 ^ -2 (хранится как M=2, E=-2)
10 000 000 = 0,1 x 10 ^ 8 (хранится как M=1, E=8)
10 000 001 = 0,1 x 10 ^ 8 (M=1, E=8)
В последнем примере мы видим что из-за ограничения на размер мантиссы (4 цифры) число сохранилось с потерей точности.
Однако, такая система позволяет сохранять числа в гораздо большем диапазоне. Если мы вернемся к начальному примеру, где мы выделили память на хранение 16 цифр, то в системе с плавающей запятой мы бы могли выделить 12 цифр под мантиссу, 4 цифры под экспоненту и сохранять числа в диапазоне от 10 ^ -9999 до 10 ^ 9999 (10 ^ 9999 это очень большое число, которое состоит из 1 и 9999 нулей). То есть используя тот же объем памяти, мы можем записывать как очень крошечные, так и гиганстские числа. Конечно, мы теряем в точности - храним только первые 12 знаков числа, но на практике этого достаточно. Большинство реальных датчиков - измерители веса, напряжения, силы тока - все равно имеют меньшую точность.
Также, надо понимать, что некоторые числа нельзя представить точно в таком формате, сколько бы памяти мы не выделили. Например, возьмем число 1/3 = 0,33333... . Это бесконечная дробь, и сколько бы цифр не выделяли под мантиссу, все равно записанное число не будет точно совпадать с истинным.
То есть надо просто принять что числа с плавающей запятой имеют ограниченную точность, и могут не соответствовать исходному числу.
Возвращаясь к твоему вопросу: "как перевести флоат как есть" - да никак, когда ты делал $num = ...., произошло преобразование в формат с плавающей запятой и в память сохранилось число, отличающееся от исходного. Ты можешь потребовать вывести хоть 30 цифр после запятой, но последние цифры будут представлять просто случайный шум. Так как нужной информации для их вывода не сохранено.
В PHP используется стандарт IEEE754 (так как его поддержка встроена в процессоры), числа двойной точности, где под экспоненту выделено 10 бит (от 2 ^-1024 до 2 ^ +1024), что примерно равно 3 десятичным цифрам, а под мантиссу - 52 бита, что примерно равно 15 десятичным цифрам.
http://php.net/manual/ru/language.types.float.php
https://habrahabr.ru/post/112953/
https://ru.wikipedia.org/wiki/IEEE_754-2008
В твоем числе должны сохраниться (примерно) только первые 15 значащих цифр, то есть
51 516 665.7419439
"примерно 15" - так как число хранится в памяти в двоичном формате, а не в десятичном и последняя цифра может поменяться при преобразованиях.
Компьютер хранит дробные числа в приближенном формате: N знаков (мантисса) и положение запятой (экспонента). Причем еще и в двоичном коде (процессору так удобнее, внутри компьютера все представлено в двоичном виде, в виде нулей и единиц).
Вообще, исторически когда-то были числа с фиксированной запятой: отводилась память под хранение N цифр (пусть для примера N = 16), и запятая была жестко зафиксирована в определенной позиции (например, в середине):
00000001,00000000
99999999,00000000
00000000,50000000
Соответственно, сложение/вычитание таких чисел можно проводить так, как будто бы запятой нет, а при умножении/делении приходилось сдвигать результат.
Но эта система имеет недостаток: она позволяет представлять небольшой диапазон чисел, в данном случае от 10 ^ -8 до 10^9. Потому придумали другую схему, с плавающей запятой, где отдельно хранятся знаки числа (мантисса) и положение запятой (экспонента). То есть дробное число представляется в виде пары небольших целых чисел.
То есть число переводится в двоичный вид, из него берется сколько-то знаков и положение запятой и сохраняется. И в памяти число представляется как (мантисса) x 2 ^ (экспонента). Мантисса это обычно дробное число, > 0 и < 1, то есть она всегда начинается с нуля и запятой, потому компьютер их не сохраняет, а хранит только идущие после запятой числа.
Наверно с двоичными числами тебе это трудно представить себе, потому для аналогии можно представить то же самое в десятичном виде - принципы будут те же. Допустим, у нас используется десятичная система и 4 знака для мантиссы. Тогда числа бы представлялись так:
15600 = 0,156 x 10 ^ 5 (то есть в памяти сохраняется M=156, E=5)
0.002 = 0,2 x 10 ^ -2 (хранится как M=2, E=-2)
10 000 000 = 0,1 x 10 ^ 8 (хранится как M=1, E=8)
10 000 001 = 0,1 x 10 ^ 8 (M=1, E=8)
В последнем примере мы видим что из-за ограничения на размер мантиссы (4 цифры) число сохранилось с потерей точности.
Однако, такая система позволяет сохранять числа в гораздо большем диапазоне. Если мы вернемся к начальному примеру, где мы выделили память на хранение 16 цифр, то в системе с плавающей запятой мы бы могли выделить 12 цифр под мантиссу, 4 цифры под экспоненту и сохранять числа в диапазоне от 10 ^ -9999 до 10 ^ 9999 (10 ^ 9999 это очень большое число, которое состоит из 1 и 9999 нулей). То есть используя тот же объем памяти, мы можем записывать как очень крошечные, так и гиганстские числа. Конечно, мы теряем в точности - храним только первые 12 знаков числа, но на практике этого достаточно. Большинство реальных датчиков - измерители веса, напряжения, силы тока - все равно имеют меньшую точность.
Также, надо понимать, что некоторые числа нельзя представить точно в таком формате, сколько бы памяти мы не выделили. Например, возьмем число 1/3 = 0,33333... . Это бесконечная дробь, и сколько бы цифр не выделяли под мантиссу, все равно записанное число не будет точно совпадать с истинным.
То есть надо просто принять что числа с плавающей запятой имеют ограниченную точность, и могут не соответствовать исходному числу.
Возвращаясь к твоему вопросу: "как перевести флоат как есть" - да никак, когда ты делал $num = ...., произошло преобразование в формат с плавающей запятой и в память сохранилось число, отличающееся от исходного. Ты можешь потребовать вывести хоть 30 цифр после запятой, но последние цифры будут представлять просто случайный шум. Так как нужной информации для их вывода не сохранено.
В PHP используется стандарт IEEE754 (так как его поддержка встроена в процессоры), числа двойной точности, где под экспоненту выделено 10 бит (от 2 ^-1024 до 2 ^ +1024), что примерно равно 3 десятичным цифрам, а под мантиссу - 52 бита, что примерно равно 15 десятичным цифрам.
http://php.net/manual/ru/language.types.float.php
https://habrahabr.ru/post/112953/
https://ru.wikipedia.org/wiki/IEEE_754-2008
В твоем числе должны сохраниться (примерно) только первые 15 значащих цифр, то есть
51 516 665.7419439
"примерно 15" - так как число хранится в памяти в двоичном формате, а не в десятичном и последняя цифра может поменяться при преобразованиях.
Добавлю еще, что иногда все же требуется большая точность, чем может обеспечить double. Хотя, как я уже писал, реальные измерительные приборы не могут выдать такую точность, но в каких-то теоретических расчетах она может понадобиться. Например, в криптографии.
Для таких случаев есть специальные библиотеки для вычислений повышенной точности:
http://php.net/manual/ru/book.bc.php
http://php.net/manual/ru/book.gmp.php
Заметь, что эти библиотеки доступны бесплатно, не запатентованы и не требуется писать их самому. Будь благодарен.
В Си есть float (32-битные) и double (64-битные) типы. В PHP используется только double. На современных процессорах по моему особой выгоды в производительности от использования float нет, а в PHP ее нет тем более (так как там код выполняется не напрямую).
Вот видишь какой ты умный, намного круче меня(не сарказм). Хоть у меня и два года опыта, я только вот так вот смог высрать:
https://ideone.com/ULbjG8
И это хуевое решение, (https://www.codewars.com/ говорит что мой скрипт на тестах слишком много времени занимает, подозреваю что просто зависает, а твой проходит нормально)
>В PHP используется только double.
Хм, в мануале написано, что точность целых и дробных зависит от ОС(х32 или х64).
От архитектуры зависит размер целых чисел (int), на 32-битных системах это -2 млрд ... + 2 млрд, на 64-битных - это гораздо больше. Для дробных и огромных чисел используется double независимо от архитектуры. Легко это проверить, выполнив команду var_dump(1.23456789123456789); на 32-битной системе. У меня она выдает double(1.2345678912346) и мы видим что тут сохранены примерно 15 цифр.
Задача про кредитный калькулятор и веб-сервер. Все работает. Но одна строчка очень тупит. Понять не могу что не так.
https://pastebin.com/eX1ATeNW
Значит, я добавил все свойства в класс emploee:
class Employee
//Сотрудник владеет полной информацией о себе
{
private $profession;
private $rate;
private $rank;
private $isBoss;
private $coffee;
private $reports;
Соответственно, для всех вычислений последующих не надо обращаться ни к каким внешним массивам, все хранится в одном объекте, упрощается вычисление всех производных значений как для сотрудника, так и для департамента.
По ходу раздумий появились идеи некоторые:
1. Может добавить в класс сотрудника поле - "Департамент". Это позволит всегда знать, в каком департаменте сотрудник.
2. Может сделать отдельный массив с сотрудниками за пределами объектов департаментов. Можно тогда вообще отказаться от объекта департамента. Спорный подход мне кажется.
3. На уровне идеи - в компании хранится массив с объектами-департаментами, внутри сотрудники, все как обычно. Но добавление сотрудника производить не методом addEmployee($employee) в объекте департамент, а методом самой компании - addEmployee($department, $employee) ну и дальше с сотрудниками работать через методы компании. Что-то подобное ты и упоминал, как мне кажется.
> 1. Может добавить в класс сотрудника поле - "Департамент". Это позволит всегда знать, в каком департаменте сотрудник.
Можно, но тогда ты будешь обязан его обновлять когда сотрудник принимается на работу или увольняется из департамента. То есть выгода от использования этого поля должна перевесить затраты на написание кода который будет его поддерживать в актуальном состоянии.
То есть подумай, так ли оно тебе нужно.
Кстати, плюс ООП в том, что мы можем такие вещи, как обновление поля делать автоматически и прозрачно для вызывающего кода.
> Может сделать отдельный массив с сотрудниками за пределами объектов департаментов. Можно тогда вообще отказаться от объекта департамента.
А в чем выгода? В департаменте есть методы вроде подсчета общей суммарной зарплаты, где ты их реализуешь? В отдельной функции? Тогда ты просто код, собранный в классе, разнесешь в несвязанные функции и непонятно, в чем выгода.
> На уровне идеи - в компании хранится массив с объектами-департаментами, внутри сотрудники, все как обычно. Но добавление сотрудника производить не методом addEmployee($employee) в объекте департамент, а методом самой компании - addEmployee($department, $employee) ну и дальше с сотрудниками работать через методы компании.
Можно, а в чем выгода? Все равно же в итоге компания будет вызывать метод департамента для добавления сотрудника (если список сотрудников сделан приватным полем).
То есть по твоим предложениям - попробуй подумать, а зачем это нужно, какая от этого польза.
>На современных процессорах по моему особой выгоды в производительности от использования float нет
Неправда, такая выгода есть и для ЦП, и для ГП (в которых есть даже FP16)
ОП отпишись пожалуйста, интересно услышать твоё мнение
> Какие подводные камни если я заведу для категорий поле с битовой маской например и буду хранить в денормализованной таблице product ?
Отступление от принципов нормализации, невозможность сделать внешний ключ с products на product_categories, проблемы, если категорий станет больше, невозможность использовать индекс при поиске по категории.
Какие ты там нашел плюсы?
> Поиск будет на мой взгляд намного быстрее без лишнего джоина
Как насчет сделать тест (миллион товаров/десяток категорий)? Мне кажется, что с маской будет медленнее из-за невозможности использовать индексы. Я сам на тест, конечно, тратить время не буду.
Алсо, мне не нравится, когда люди без пруфов пишут "джойн медленный". Кто вам такое сказал?
Тут написано https://stackoverflow.com/questions/4584637/double-or-float-which-is-faster что для интеловских архитектур разницы нет, и математический сопроцессор работает на даблах. Разница может быть только из-за увеличения объема памяти.
Что касается GPU, в них не силен.
В PHP все это разумеется значения не имеет в силу наличия накладных расходов на интерпретацию (а переменные в PHP вообще по 50 байт занимают если я не путаю).
Тут впрочем есть другое мнение: https://social.msdn.microsoft.com/Forums/en-US/2b190cfa-e221-4e91-b40c-c2a00aee4b36/float-vs-double-performance-on-arm?forum=winappswithnativecode
Опять же, к PHP это никак не относится.
>Отступление от принципов нормализации
От них отступают в любом инет-магазине все у кого больше 500к товаров. У меня на инет магазине с 1.2кк товаров только таблица product_attribute хранит 26кк записей, намного удобнее денормализировать данные 2 таблицы чем даже 3 джоина делать таблицы product_attribute (Красная блузка с 8 пуговицами, белым воротником и черно-белым принтом
>Алсо, мне не нравится, когда люди без пруфов пишут "джойн медленный"
Лучше сделать выборку из одной денормализированной таблицы чем 5 джоинов по нормализированным.
Пруфы могу запостить если мне не будет лень, у меня как раз есть подходящая база
Вообще, я тут поймал себя на мысли, что в огромных каталогах товаров обычно никто и не ищет с помощью БД - используют какой-нибудь поисковой демон вроде sphinx, lucene и тд, а там данные уже в денормализованном и оптимизированном для поиска виде.
"от них отступают все" - ну это не аргумент, может этот магазин какие-нибудь фрилансеры наспех делали, их решения точно за образец брать не стоит.
Да, наверное ты прав, при поиске по битовой маске индекс не поможет совсем
Где есть вменяемый мануал о том, как это использовать?
inb4 запускаешь всё подряд и смотришь на ошибки
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
https://2ch.hk/pr/res/1048938.html (М)
Опять уёбок перекатил тред через жопу. Просили же не делать так, ты необучаемый?
http://php.net/manual/en/intro.event.php
Есть вступление. В нем написано
> This is a port of libevent to the PHP infrastructure.
Так что может быть полезно ознакомиться с сишной библиотекой libevent.
Ну или вообще с принципами работы таких библиотек (начать можно с знаменитой статьи про C10K Problem http://www.kegel.com/c10k.html можно попробовать нагуглить переводы ).
Сделал форму создания/редактирования теста, вынес код из контроллеров в сервисы.
Накопилось "немного" вопросов
https://pastebin.com/qW5g4qZA
Сказать то что хотел? Ничего не понял.
Ты глупый? Он выучил что-то 6 лет назад и делает так ВСЁ ЭТО ВРЕМЯ, видит что кто-то делает не так, и его корежит. Я тоже постоянно функции описываю как:
function kek() {
....
}
function brek() {
...
}
А когда вижу код набитый таким синтаксисом:
function kek()
{
...
}
function brek()
{
...
}
то у меня всё начинает плыть перед глазами уже от непривычки.
Чем старше человек, тем он более черствый и всё менее гибкий в любых вопросах, привычки всё глубже укореняются и т.д.
Просто хуй забей.
>А когда вижу код набитый таким синтаксисом:
Вот с этого всегда проигрывал. Есть же требования и
ctrl
alt
L
Который сам тебе форматнёт за 2 секунды.
Я тригерю его на счёт кнкатенации. Там вообще разрыв шаблона.
так что:
function kek() {
....
}
что:
function kek()
{
...
}
- оба варианта являются приемлимыми, просто в итоге пишут все как кому нравится. Но я привык хуярить первый - так как меньше строк занимает, и для меня читабельнее, так как 1 строка открывающая и 1 закрывающая, а во втором варианте 2 открывающих как бы и просто непривычно видеть по 2 строки перед остумами тела функции.
И вот открываешь чужой файл что бы там что-то изменить, а там эта ебучая стена
}
}
}
}
}
}
Ну просто привел как пример что первое вспомнилось. На самом то деле рили похуй, и сидеть в чужих файлах скобки переносить или бомбить с этого - я ебал.
>>1048592 >>1048601
Алсо вот это говно прочекал и теперь у меня весь ютуб этой парашей засран. пацаны не рекомендую тратить на это время. Пока вы посмотрите курс длиною в 20 часов или сколько он там длится, вы узнаете ровно нихуя - базарю. Вам будут рассказывать основы и пояснять про ловушки для лохов, которые вы тут же забудете из-за отсутствия практики и так далее. Цель таких курсов - срубить бабла на лохах же. Комменты вообще как будто ботами написаны. Лучше отдельно выучить пхп, а потом отдельно же постичь основы html, и ооокак отдавать или делать html динамическим с помощью php будет потом постичь куда проще, чем сидеть наблюдать на экране <p><? echo "helo world"; ?></p> И слушать про подводные камни этой хуйни.
так что:
function kek() {
....
}
что:
function kek()
{
...
}
- оба варианта являются приемлимыми, просто в итоге пишут все как кому нравится. Но я привык хуярить первый - так как меньше строк занимает, и для меня читабельнее, так как 1 строка открывающая и 1 закрывающая, а во втором варианте 2 открывающих как бы и просто непривычно видеть по 2 строки перед остумами тела функции.
И вот открываешь чужой файл что бы там что-то изменить, а там эта ебучая стена
}
}
}
}
}
}
Ну просто привел как пример что первое вспомнилось. На самом то деле рили похуй, и сидеть в чужих файлах скобки переносить или бомбить с этого - я ебал.
>>1048592 >>1048601
Алсо вот это говно прочекал и теперь у меня весь ютуб этой парашей засран. пацаны не рекомендую тратить на это время. Пока вы посмотрите курс длиною в 20 часов или сколько он там длится, вы узнаете ровно нихуя - базарю. Вам будут рассказывать основы и пояснять про ловушки для лохов, которые вы тут же забудете из-за отсутствия практики и так далее. Цель таких курсов - срубить бабла на лохах же. Комменты вообще как будто ботами написаны. Лучше отдельно выучить пхп, а потом отдельно же постичь основы html, и ооокак отдавать или делать html динамическим с помощью php будет потом постичь куда проще, чем сидеть наблюдать на экране <p><? echo "helo world"; ?></p> И слушать про подводные камни этой хуйни.
А курс от loftschool? У них вроде как больше на практику направлено. Вот список тем.
>Лучше отдельно выучить пхп
По сайту ОПа?
Да, тым сразу идёт "Вот это говно вы будете использовать каждый день. Остальное мы рассказывать не будем". Но там курс насыщенный пипец просто.
Изучение кодинга можно разделить на 3 условно говоря части.
1. Изучение примитивов: Как работать с переменными, строками, массивами, как это всё создавать заполнять и так далее.
Операции арифметические и логические, присваивание, сравнение. Как и чем можно оперировать, что можно складывать и так далее.
Условия, циклы, как писать свои функции и как работать со встроенными функциями языка.
Это всё грубо говоря примитивы. Не важно где ты о них прочитаешь, в книге за час, в учебнике опа за 5 часов или выкинув бабло на курсы для лохов длиною в 100 часов. Это всё так и так наработается потому что используется постоянно и нужно всегда.
Твоя задача выучить это всё и не важно как и где, без этого рили нельзя
2. Скажем что алгоритмы - решения мелких задач разной степени тяжести от самх простых до тех на которые дрочат олимпиадники. Это когда есть задача и ты берешь и решаешь её используя эти самые примитивы. Может нарабатываться годами или не быть постигнуто никогда в топовой степени. Просто потому что в разработке в итоге совсем не всегда нужно сидеть и хуярить сложные алгоритмы а иногда ты сидишь и просто гоняешь данные туда-сюда (взять из базы инфу, что-то там обработать с ней, вывести во вьюху, повторять до увольнения от охуевшей скуки)
Ты можешь вообще не касаться этого, а сразу нырнуть в формочки, верстку и базы данных что бы делать простые проекты по типу лендосов или сайтов визиток
3. Архитектура - это то как у тебя в программе в итоге всё устроено, что куда берется, что куда ходит, что выходит и так далее. Где какой алгоритм опять же используется и как что с чем взаимодействует. Тоже может нарабатываться годами.
Опять же можно не касаться этого, когда просто берешь и начинаешь хуярить на готовых решениях по типу вордпрессов и прочих modx или на фреймворках, где ты в принципе тоже привязан к определенной парадигме
Изучать в итоге можно дохуя и до бесконечности всё это. Курсы в итоге смотря какую цель преследуют. Если фастиком высрать готовую вордпресс-макаку, то это одно, там будут дрочить чисто как работать на вродпрессе. Если же втюхать вам за 40 часов примитивы - то это другое.
>По сайту ОПа?
Как угодно. Примитивы похуй как и где. Но оп еще и дроч на алгоритмы втихую под видом "простых" задачек для закрепления дает, что многие могут долго очень на них сидеть.
Изучение кодинга можно разделить на 3 условно говоря части.
1. Изучение примитивов: Как работать с переменными, строками, массивами, как это всё создавать заполнять и так далее.
Операции арифметические и логические, присваивание, сравнение. Как и чем можно оперировать, что можно складывать и так далее.
Условия, циклы, как писать свои функции и как работать со встроенными функциями языка.
Это всё грубо говоря примитивы. Не важно где ты о них прочитаешь, в книге за час, в учебнике опа за 5 часов или выкинув бабло на курсы для лохов длиною в 100 часов. Это всё так и так наработается потому что используется постоянно и нужно всегда.
Твоя задача выучить это всё и не важно как и где, без этого рили нельзя
2. Скажем что алгоритмы - решения мелких задач разной степени тяжести от самх простых до тех на которые дрочат олимпиадники. Это когда есть задача и ты берешь и решаешь её используя эти самые примитивы. Может нарабатываться годами или не быть постигнуто никогда в топовой степени. Просто потому что в разработке в итоге совсем не всегда нужно сидеть и хуярить сложные алгоритмы а иногда ты сидишь и просто гоняешь данные туда-сюда (взять из базы инфу, что-то там обработать с ней, вывести во вьюху, повторять до увольнения от охуевшей скуки)
Ты можешь вообще не касаться этого, а сразу нырнуть в формочки, верстку и базы данных что бы делать простые проекты по типу лендосов или сайтов визиток
3. Архитектура - это то как у тебя в программе в итоге всё устроено, что куда берется, что куда ходит, что выходит и так далее. Где какой алгоритм опять же используется и как что с чем взаимодействует. Тоже может нарабатываться годами.
Опять же можно не касаться этого, когда просто берешь и начинаешь хуярить на готовых решениях по типу вордпрессов и прочих modx или на фреймворках, где ты в принципе тоже привязан к определенной парадигме
Изучать в итоге можно дохуя и до бесконечности всё это. Курсы в итоге смотря какую цель преследуют. Если фастиком высрать готовую вордпресс-макаку, то это одно, там будут дрочить чисто как работать на вродпрессе. Если же втюхать вам за 40 часов примитивы - то это другое.
>По сайту ОПа?
Как угодно. Примитивы похуй как и где. Но оп еще и дроч на алгоритмы втихую под видом "простых" задачек для закрепления дает, что многие могут долго очень на них сидеть.
Ну я от курса и жду примерно как раз такой схемы: синтаксис, основы - алгоритмы, базы данных - субд, цмски, фреймворки.
ну и платить за курсы я никогда и не собирался, потому что результат весьма сомнительный
>ctrl
>alt
>L
У меня эта комбинация почему-то блокирует компьютер, как Win+L. При этом редактор успевает переформатировать текст. Дома нет такой хуйни, только на работе.
>Но оп еще и дроч на алгоритмы втихую под видом "простых" задачек для закрепления дает, что многие могут долго очень на них сидеть.
Есть такая тема, я уже запарился задачку про вектор ковырять, но тоже дело принципа, однако.
Server: https://ideone.com/siL9px
Client: https://ideone.com/6ZigUh
Если я на сервере (20 строка) буду возвращать строку, которую я считал (не новую реверсивную), то буквы на сайте нормальные, но в консоли по прежнему ебатня. Почему это происходит?
Вопрос исчерпан.
А, все, не надо. Кое как подключил phpseclib и оно работает.
Нет, в векторе нет никаких алгоритмов. Другое дело бонусные задачи про банкомат и прочее. В векторе ты как раз занимаешься тем что гоняешь туда-сюда строчки по сути и не более. Там как раз развиваются зачатки архитектуры.
В комментариях была указана ссылка на https://wiki.archlinux.org/index.php/GnuPG#Unattended_passphrase где говориться как вернуть старый функционал:
>First, edit the gpg-agent configuration to allow loopback pinentry mode:
>~/.gnupg/gpg-agent.conf
>allow-loopback-pinentry
Этого файла не было, и я создал его в ручную, но это не имеет значения, потому что эта опция включена по умолчанию.
https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html#Agent-Options
>--no-allow-loopback-pinentry
>--allow-loopback-pinentry
>Disallow or allow clients to use the loopback pinentry features; see the option pinentry-mode for details. Allow is the default.
>Second...
>...add the option to the configuration:
>~/.gnupg/gpg.conf
>pinentry-mode loopback
После этого я не смог пользоваться gpg и получил ошибку:
gpg: .../gpg.conf:242: invalid option
Я не нашел эту опцию в списке опций gpg.conf ( https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html#GPG-Configuration-Options ), но нашёл в "Agent’s Assuan Protocol" https://www.gnupg.org/documentation/manuals/gnupg/Agent-OPTION.html#Agent-OPTION , где не понятно как устанавливать эти опции.
Есть какие-нибудь идеи как настроить этот протокол?
Кстати, зашифрованный, с помощью gnupg_encrypt(), текст замечательно расшифровался с помощью той js библиотеки.
В комментариях была указана ссылка на https://wiki.archlinux.org/index.php/GnuPG#Unattended_passphrase где говориться как вернуть старый функционал:
>First, edit the gpg-agent configuration to allow loopback pinentry mode:
>~/.gnupg/gpg-agent.conf
>allow-loopback-pinentry
Этого файла не было, и я создал его в ручную, но это не имеет значения, потому что эта опция включена по умолчанию.
https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html#Agent-Options
>--no-allow-loopback-pinentry
>--allow-loopback-pinentry
>Disallow or allow clients to use the loopback pinentry features; see the option pinentry-mode for details. Allow is the default.
>Second...
>...add the option to the configuration:
>~/.gnupg/gpg.conf
>pinentry-mode loopback
После этого я не смог пользоваться gpg и получил ошибку:
gpg: .../gpg.conf:242: invalid option
Я не нашел эту опцию в списке опций gpg.conf ( https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html#GPG-Configuration-Options ), но нашёл в "Agent’s Assuan Protocol" https://www.gnupg.org/documentation/manuals/gnupg/Agent-OPTION.html#Agent-OPTION , где не понятно как устанавливать эти опции.
Есть какие-нибудь идеи как настроить этот протокол?
Кстати, зашифрованный, с помощью gnupg_encrypt(), текст замечательно расшифровался с помощью той js библиотеки.
Так что кому не нравится тред в бамплимите, можно перейти и общаться там.
Я пока туда переходить не буду, еще дня 2-3 тут посижу, отвечая на неотвеченные посты.
Ну а автор треда, который решил, что шапка не нужна, без всякой аргументации, а просто потому что ему так больше нравится, пусть один и сидит в своем пустом треде.
> Я не нашел эту опцию в списке опций gpg.conf
Если подняться в мануале на 1 шаг вверх ( https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html#Invoking-GPG_002dAGENT ) то видно, что это опции для утилиты gpg-agent (которая, как я вроде упоминал, нужна для того, чтобы вводить пароль для расшифровки ключа только один раз, она расшифровывает ключ и держит его в памяти).
То есть эта опция тебе не поможет. Конечно, ты бы мог попробовать использовать gpg-agent: запускать его, написать специальную программу, которая будет выполнять роль программы pinentry, и получать откуда-то пароль, но это выглядит довольно громоздко и усложненно.
Попробуем посмотреть мануал по библиотеке gpgme:
> https://www.gnupg.org/documentation/manuals/gpgme/Decrypt.html#Decrypt
> Function: gpgme_error_t gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain)
Здесь видно (методом исключения), что ключ и параметры шифрования хранятся в контексте ctx.
> https://www.gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html#Pinentry-Mode
> Function: gpgme_error_t gpgme_set_pinentry_mode (gpgme_ctx_t ctx, gpgme_pinentry_mode_t mode)
> The function gpgme_set_pinentry_mode specifies the pinentry mode to be used.
> For GnuPG >= 2.1 this option is required to be set to GPGME_PINENTRY_MODE_LOOPBACK to enable the passphrase callback mechanism in GPGME through gpgme_set_passphrase_cb.
> ...
> GPGME_PINENTRY_MODE_LOOPBACK
> Redirect Pinentry queries to the caller. This enables the use of gpgme_set_passphrase_cb whis pinentry queries redirected to gpgme.
Здесь можно задать способ получения пароля к ключу, в том числе с использование коллбека, который вызовет библиотека gpgme.
Там упоминается функция gpgme_set_passphrase_cb: https://www.gnupg.org/documentation/manuals/gpgme/Passphrase-Callback.html#Passphrase-Callback
> Function: void gpgme_set_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t passfunc, void *hook_value)
То есть концепция при работе с сишной библиотекой такая:
- создаем контекст
- задаем для него нашу-функцию колллбек, которая будет вызываться для получения пароля к ключу
- запускаем расшифровку
Но это все в сишной библиотеке, а реализована ли такая возможность в php-расширении? Поищем в коде, благо он почти весь там в одном файле, и находим:
https://github.com/php-gnupg/php-gnupg/blob/master/gnupg.c#L1438
Внутри функции gnupg_decrypt() есть вызов такой функции, задающей коллбек. Вот сам коллбек: https://github.com/php-gnupg/php-gnupg/blob/master/gnupg.c#L579
Он ищет пароль для расшифровки по имени пользователя в чем-то похожем на массив с названием decryptkeys. Посмотрим, что с этой переменной делают в файле. Мы находим обращения к ней в реализациях PHP-функций:
> gnupg_cleardecryptkeys
> gnupg_adddecryptkey
Последняя функция описана в комментарии как proto bool gnupg_adddecryptkey(string key), но почитав исходники, мы видим что у нее есть 2 варианта вызова:
- как метод на каком-то объекте (по-видимому, соответствующем контексту в библиотеке gpgme), с 2 строковыми аргументами
- как функция с 3 аргументами: ресурс (скорее всего контекст gpgme), и 2 строки (key_id, passphrase)
Это в некоторых расширениях так делают, когда функции можно вызывать как функции либо как методы на объекте (похожее есть в расширении DateTime например).
Соответственно, функция gnupg_adddecryptkey($ctx, $key_id, $passphrase) делает следующее:
- вызывает gpgme_get_key(PHPC_THIS->ctx, key_id, &gpgme_key, 1) - function gpgme_get_key gets the key with the fingerprint (or key ID) fpr from the crypto backend
- делает цикл по subkey в найденном ключе:
> gpgme_subkey = gpgme_key->subkeys;
> while (gpgme_subkey) {
> if (gpgme_subkey->secret == 1) {
> PHPC_HASH_CSTR_ADD_PTR(
> PHPC_THIS->decryptkeys, gpgme_subkey->keyid,
> passphrase, passphrase_len + 1);
> }
> gpgme_subkey = gpgme_subkey->next;
> }
- добавляет passphrase в массив decryptkeys для всех идентификаторов подключей
То есть, мне кажется, что функция gnupg_adddecryptkey должна задавать пароль для всех подключей указанного в key_id ключа. Она есть в мануале: http://php.net/manual/en/function.gnupg-adddecryptkey.php и там есть примеры использования.
Еще, может быть стоит посмотреть, может есть другие реализации pgp? Я вот беглым гуглением наткнулся на несколько проектов: https://www.google.ru/search?q=github+php+gnupg&newwindow=1&gbv=1&sei=hb-dWan1GKS56ASWraPgDQ
Также, я несколько раз видел упоминание библиотеки libsodium, которая содерждит готовые криптографические примитивы - может, можно просто на них повторить алгоритм gpg?
> Я не нашел эту опцию в списке опций gpg.conf
Если подняться в мануале на 1 шаг вверх ( https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html#Invoking-GPG_002dAGENT ) то видно, что это опции для утилиты gpg-agent (которая, как я вроде упоминал, нужна для того, чтобы вводить пароль для расшифровки ключа только один раз, она расшифровывает ключ и держит его в памяти).
То есть эта опция тебе не поможет. Конечно, ты бы мог попробовать использовать gpg-agent: запускать его, написать специальную программу, которая будет выполнять роль программы pinentry, и получать откуда-то пароль, но это выглядит довольно громоздко и усложненно.
Попробуем посмотреть мануал по библиотеке gpgme:
> https://www.gnupg.org/documentation/manuals/gpgme/Decrypt.html#Decrypt
> Function: gpgme_error_t gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain)
Здесь видно (методом исключения), что ключ и параметры шифрования хранятся в контексте ctx.
> https://www.gnupg.org/documentation/manuals/gpgme/Pinentry-Mode.html#Pinentry-Mode
> Function: gpgme_error_t gpgme_set_pinentry_mode (gpgme_ctx_t ctx, gpgme_pinentry_mode_t mode)
> The function gpgme_set_pinentry_mode specifies the pinentry mode to be used.
> For GnuPG >= 2.1 this option is required to be set to GPGME_PINENTRY_MODE_LOOPBACK to enable the passphrase callback mechanism in GPGME through gpgme_set_passphrase_cb.
> ...
> GPGME_PINENTRY_MODE_LOOPBACK
> Redirect Pinentry queries to the caller. This enables the use of gpgme_set_passphrase_cb whis pinentry queries redirected to gpgme.
Здесь можно задать способ получения пароля к ключу, в том числе с использование коллбека, который вызовет библиотека gpgme.
Там упоминается функция gpgme_set_passphrase_cb: https://www.gnupg.org/documentation/manuals/gpgme/Passphrase-Callback.html#Passphrase-Callback
> Function: void gpgme_set_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t passfunc, void *hook_value)
То есть концепция при работе с сишной библиотекой такая:
- создаем контекст
- задаем для него нашу-функцию колллбек, которая будет вызываться для получения пароля к ключу
- запускаем расшифровку
Но это все в сишной библиотеке, а реализована ли такая возможность в php-расширении? Поищем в коде, благо он почти весь там в одном файле, и находим:
https://github.com/php-gnupg/php-gnupg/blob/master/gnupg.c#L1438
Внутри функции gnupg_decrypt() есть вызов такой функции, задающей коллбек. Вот сам коллбек: https://github.com/php-gnupg/php-gnupg/blob/master/gnupg.c#L579
Он ищет пароль для расшифровки по имени пользователя в чем-то похожем на массив с названием decryptkeys. Посмотрим, что с этой переменной делают в файле. Мы находим обращения к ней в реализациях PHP-функций:
> gnupg_cleardecryptkeys
> gnupg_adddecryptkey
Последняя функция описана в комментарии как proto bool gnupg_adddecryptkey(string key), но почитав исходники, мы видим что у нее есть 2 варианта вызова:
- как метод на каком-то объекте (по-видимому, соответствующем контексту в библиотеке gpgme), с 2 строковыми аргументами
- как функция с 3 аргументами: ресурс (скорее всего контекст gpgme), и 2 строки (key_id, passphrase)
Это в некоторых расширениях так делают, когда функции можно вызывать как функции либо как методы на объекте (похожее есть в расширении DateTime например).
Соответственно, функция gnupg_adddecryptkey($ctx, $key_id, $passphrase) делает следующее:
- вызывает gpgme_get_key(PHPC_THIS->ctx, key_id, &gpgme_key, 1) - function gpgme_get_key gets the key with the fingerprint (or key ID) fpr from the crypto backend
- делает цикл по subkey в найденном ключе:
> gpgme_subkey = gpgme_key->subkeys;
> while (gpgme_subkey) {
> if (gpgme_subkey->secret == 1) {
> PHPC_HASH_CSTR_ADD_PTR(
> PHPC_THIS->decryptkeys, gpgme_subkey->keyid,
> passphrase, passphrase_len + 1);
> }
> gpgme_subkey = gpgme_subkey->next;
> }
- добавляет passphrase в массив decryptkeys для всех идентификаторов подключей
То есть, мне кажется, что функция gnupg_adddecryptkey должна задавать пароль для всех подключей указанного в key_id ключа. Она есть в мануале: http://php.net/manual/en/function.gnupg-adddecryptkey.php и там есть примеры использования.
Еще, может быть стоит посмотреть, может есть другие реализации pgp? Я вот беглым гуглением наткнулся на несколько проектов: https://www.google.ru/search?q=github+php+gnupg&newwindow=1&gbv=1&sei=hb-dWan1GKS56ASWraPgDQ
Также, я несколько раз видел упоминание библиотеки libsodium, которая содерждит готовые криптографические примитивы - может, можно просто на них повторить алгоритм gpg?
Виндовая консоль не выводит utf-8, увы.
Обработка ошибок у тебя не печатает причину ошибки.
mb_detect_encoding в общем не работает и ее лучше не использовать, в общем случае определить кодировку текста невозможно, это делают статистическими методами при условии определенных ограничений (например, исходя из предположения что в строке содержится осмысленный текст).
Код написан не совсем верно, так как тебе стоит посмотреть мануал по socket_write/read:
> socket_write() не обязательно записывает все байты из указанного буфера. Нормально то, что, в зависимости от сетевых буферов и т. д., только некоторое количество данных, даже один байт, будет записан, хотя ваш буфер больше. Вы должны следить за тем, чтобы непреднамеренно не забыть передать остаток ваших данных.
То же касается и socket-read - она может возвращать данные в любых объемах, хоть по 1 байту за вызов, и надо это учесть.
в сервере $spawn открывается внутри цикла, но не закрывается.
Что касается "формочек" и "таблиц", у ОПа есть задача про студентов с комментариями, с паттернами работы с БД, с правилами обработки форм, с описанием уязвимостей, с советами по архитектуре. Не в каждом учебнике или курсе такие вещи описываются нормально (если бы описывались - я бы и не мучался, а просто дал ссылки).
Если это расписание только уроков с теорией и предполагается что учащийся дополнителньо изучает что-то самостоятельно, то ок. Но вообще, оно выглядит очень сжатым, у меня ощущение, что они там по верхам скачут, чтобы как можно больше слов выучить для собеседования.
https://docs.google.com/spreadsheets/d/1-ovmqMq-Un4NCvDTxyGRQOi282ZLf6ZqmpS_pB4o_pw/edit#gid=0
Ну вот например на ООП у них отведен одно занятие. Еще один урок - на паттерны, причем там явно паттерны для собеседования. Уязвимости они изучают уже после того, как должны сделать "лендинг".
На phpunit отведен вообще кусочек занятия. Ну что они освоят за это время?
Сроки очень сжатые там. Сомневаюсь, что реально в них уложиться, особенно если человек еще где-то работает.
Завтра, на свежую голову, ознакомлюсь подробнее! Это очень сложно.
>То есть, мне кажется, что функция gnupg_adddecryptkey должна задавать пароль для всех подключей указанного в key_id ключа.
Да, действительно, именно она задает пароль. Я перепутал эти функции в начале. Просто изначально ошибка выдавалась именно на функции decrypt, поэтому я предполагаю, что это именно из-за сломанной передачи passphrase, как было сказано в комментариях - сама функция decrypt, конечно же, не принимает его.
Я ещё забыл протестировать дешифровку ключем без пароля, для полной уверенности что проблема именно в этом.
>Еще, может быть стоит посмотреть, может есть другие реализации pgp?
Да, я тоже думал прибегнуть к ним, но я немного сомневался в их надёжности. Я протестирую завтра библиотеку которую предлагал ранее для генерации ключей: https://pear.php.net/package/Crypt_GPG
>Также, я несколько раз видел упоминание библиотеки libsodium, которая содерждит готовые криптографические примитивы - может, можно просто на них повторить алгоритм gpg?
Это займет слишком много времени. Я хотел всего лишь написать качественный мессенджер, но с шифрованием оказалось слишком много подводных камней. Но это ничего, в Crypt_GPG должны работать эти функции. На самом деле, остается сделать только последний рывок.
Мне интересна задача с повторением алгоритма gpg, поэтому я оставлю это на дальнейшую реализацию. Ах как много ещё предстоит сделать
Так же, вы писали большой пост насчёт архитектуры приложения. На это тоже уйдет какое-то время. Я пока параллельно буду писать вариант без модели, чтобы не терять время, и потом ощутить на своей шкуре преимущество хорошей архитектуры (пока мне это не так очевидно).
Правда там примеры уровня assertEquals(0, count([])) и assertEquals(2, 1+1).
Это все замечательно, но как тестировать реальное mvc приложение? Где нет методов возведения числа в квадрат, а есть только получение данных из базы и вывод в html.
Допустим в контроллере я получаю данные из реквеста, передаю их в метод сервиса.
Сервис исполняет методы репозиториев, где тупо sql/dql/builder.
Что тут тестить? Ок, допустим у меня на главной странице выводятся 5 последних записей.
Логично проверить, что выводятся именно пять, и что эти записи действительно последние по дате.
Занести подготовленные данные в бд, выполнить тестируемый метод и проверить что он вернет ожидаемый результат?
Ок, а какие есть способы занесения этих данных в базу, ну то есть нельзя ли как-то подгрузить фикстуры из doctrine fixtures классов, потому что мне не очень хочется теперь дублировать их еще и в xml или yaml специально для phpunit.
И как указать базу для тестирования? В симфони почему-то есть отдельные конфиги для dev и prod, но я не вижу для test.
Нагуглил пока что-то такое
https://stackoverflow.com/questions/9196035/temporary-doctrine2-fixtures-for-testing-with-phpunit
А, все, как сменить базу нашел.
http://symfony.com/doc/current/testing/database.html#changing-database-settings-for-functional-tests
Тут еще какие-то моки вместо реальных репозиториев.
// Now, mock the repository so it returns the mock of the employee
$employeeRepository = $this->createMock(ObjectRepository::class);
Из пасты опа
> Моки — это классы-заглушки, которые используются чтобы проверить, что определенная функция была вызвана (по моему, они не очень часто нужны).
Что сие значит? Что за моки и как их готовить?
Чет я не понял, они предлагают проверять не результат запроса (набор объектов), а сам запрос (sql строку)?
http://symfony2-document.readthedocs.io/en/latest/cookbook/testing/doctrine.html#writing-your-unit-test
Ну во, к сожалению, криптография не такая простая вещь. Что касается повторения gpg - я не думаю, что там что-то сверхсложное, там какие-то стандартные операции используются, я думаю (исходники ведь доступны, можно проверить при желании).
Насчет Crypt_gpg - сразу предупрежу, что по описанию:
> Crypt_GPG uses PHP's program execution functions to run GnuPG as a subprocess, performing the desired action. Crypt_GPG automatically handles process control, stream handling and error checking of the GnuPG subprocess. Crypt_GPG uses PHP streams internally for most actions, allowing (among other things) any streamable resource to be used with the Crypt_GPG file commands.
То есть он просто запускает процесс gpg.
>>1049233
Есть PSR, где написано как должна ставиться скобка. Свои стандарты придумывать не надо, от этого никакой выгоды.
>>1049917
А урок читал https://gist.github.com/codedokode/a455bde7d0748c0a351a ?
Вообще, я бы советовал для начала все же тестировать более мелкие компоненты, например, модели в рамках MVC. Так как GUI тесты - сложные и хрупкие.
> Сервис исполняет методы репозиториев, где тупо sql/dql/builder.
Его можно протестировать, что он не падает при вызове функций.
> Что тут тестить? Ок, допустим у меня на главной странице выводятся 5 последних записей.
Для начала можно протестировать что страница вообще рендерится.
> Ок, а какие есть способы занесения этих данных в базу, ну то есть нельзя ли как-то подгрузить фикстуры из doctrine fixtures классов, потому что мне не очень хочется теперь дублировать их еще и в xml или yaml специально для phpunit.
Я думаю, можно. Дублировать не надо.
Вообще, с БД есть разные подходы:
- перед каждым тестом очищать БД, накатывать миграции и данные - медленно
- сделать дамп и накатывать его перед каждым тестом
- делать это, но перед группой тестов, а не каждым
- перед тестом начинать транзакцию и откатывать после выполнения
Для ускорения некоторые используют sqlite с БД в памяти или mysql с данными в tmpfs (тоже в памяти по сути).
Наконец, есть еще интересный способ. Можно тестировать класс с помощью вызовов методов, результат которых зависит от друг друга.
Ну например, если у тебя есть класс с методами addPost, getPosts, deletePost, то можно сделать такой тест:
- добавить пост через addPost
- проверить что getPosts его вернет
- удалить пост
- проверить, что getPosts его не возвращает
Так мы тестируем класс с минимумом лишнего кода, и без заглядывания в его внутреннее устройство.
Этот подход хорошо работает с классами, которые что-нибудь кодируют/разбирают. Ну например, мы бы могли тестировать функции json_encode/json_decode, пропуская разные данные через обе эти функции и проверяя, что ничего не теряется.
Вообще, важно учитывать такие вещи:
- тесты должны быть максимально устойчивые, чтобы их не надо было переделывать при любых мелких правках кода. Не надо закладывать в них слишком много деталей
- тесты должны использовать классы/функции так же, как их использует обычный код, не должны полагаться на знание их внутреннего устройства. То есть лучше искать добавленную запись с помощью того же класса, чем лезть в обход него в БД напрямую.
> Где нет методов возведения числа в квадрат, а есть только получение данных из базы и вывод в html.
тестируй, что методы получения данных что-нибудь возвращают.
Также, заметь что есть еще тестирование на уровне контроллеров, что избавляет от необходимости поднимать сервер и эмулировать браузер.
> Что тут тестить? Ок, допустим у меня на главной странице выводятся 5 последних записей.
Ты закладываешь слишком много деталей в тест. Что, если ты поменяешь число выводимых записей? Я бы просто проверял, что они возвращаются.
> И как указать базу для тестирования? В симфони почему-то есть отдельные конфиги для dev и prod, но я не вижу для test.
Надо смотреть мануалы, для начала, можно попробовать создать конфиг для test самому и выставить соответствующее окружение.
Вообще, тестирование само по себе отдельная творческая задача, не проще программирования, и иногда надо поломать голову. Если хочешь, можем разобрать конкретные примеры кода, а без кода я могу только общими словами описать.
Ну во, к сожалению, криптография не такая простая вещь. Что касается повторения gpg - я не думаю, что там что-то сверхсложное, там какие-то стандартные операции используются, я думаю (исходники ведь доступны, можно проверить при желании).
Насчет Crypt_gpg - сразу предупрежу, что по описанию:
> Crypt_GPG uses PHP's program execution functions to run GnuPG as a subprocess, performing the desired action. Crypt_GPG automatically handles process control, stream handling and error checking of the GnuPG subprocess. Crypt_GPG uses PHP streams internally for most actions, allowing (among other things) any streamable resource to be used with the Crypt_GPG file commands.
То есть он просто запускает процесс gpg.
>>1049233
Есть PSR, где написано как должна ставиться скобка. Свои стандарты придумывать не надо, от этого никакой выгоды.
>>1049917
А урок читал https://gist.github.com/codedokode/a455bde7d0748c0a351a ?
Вообще, я бы советовал для начала все же тестировать более мелкие компоненты, например, модели в рамках MVC. Так как GUI тесты - сложные и хрупкие.
> Сервис исполняет методы репозиториев, где тупо sql/dql/builder.
Его можно протестировать, что он не падает при вызове функций.
> Что тут тестить? Ок, допустим у меня на главной странице выводятся 5 последних записей.
Для начала можно протестировать что страница вообще рендерится.
> Ок, а какие есть способы занесения этих данных в базу, ну то есть нельзя ли как-то подгрузить фикстуры из doctrine fixtures классов, потому что мне не очень хочется теперь дублировать их еще и в xml или yaml специально для phpunit.
Я думаю, можно. Дублировать не надо.
Вообще, с БД есть разные подходы:
- перед каждым тестом очищать БД, накатывать миграции и данные - медленно
- сделать дамп и накатывать его перед каждым тестом
- делать это, но перед группой тестов, а не каждым
- перед тестом начинать транзакцию и откатывать после выполнения
Для ускорения некоторые используют sqlite с БД в памяти или mysql с данными в tmpfs (тоже в памяти по сути).
Наконец, есть еще интересный способ. Можно тестировать класс с помощью вызовов методов, результат которых зависит от друг друга.
Ну например, если у тебя есть класс с методами addPost, getPosts, deletePost, то можно сделать такой тест:
- добавить пост через addPost
- проверить что getPosts его вернет
- удалить пост
- проверить, что getPosts его не возвращает
Так мы тестируем класс с минимумом лишнего кода, и без заглядывания в его внутреннее устройство.
Этот подход хорошо работает с классами, которые что-нибудь кодируют/разбирают. Ну например, мы бы могли тестировать функции json_encode/json_decode, пропуская разные данные через обе эти функции и проверяя, что ничего не теряется.
Вообще, важно учитывать такие вещи:
- тесты должны быть максимально устойчивые, чтобы их не надо было переделывать при любых мелких правках кода. Не надо закладывать в них слишком много деталей
- тесты должны использовать классы/функции так же, как их использует обычный код, не должны полагаться на знание их внутреннего устройства. То есть лучше искать добавленную запись с помощью того же класса, чем лезть в обход него в БД напрямую.
> Где нет методов возведения числа в квадрат, а есть только получение данных из базы и вывод в html.
тестируй, что методы получения данных что-нибудь возвращают.
Также, заметь что есть еще тестирование на уровне контроллеров, что избавляет от необходимости поднимать сервер и эмулировать браузер.
> Что тут тестить? Ок, допустим у меня на главной странице выводятся 5 последних записей.
Ты закладываешь слишком много деталей в тест. Что, если ты поменяешь число выводимых записей? Я бы просто проверял, что они возвращаются.
> И как указать базу для тестирования? В симфони почему-то есть отдельные конфиги для dev и prod, но я не вижу для test.
Надо смотреть мануалы, для начала, можно попробовать создать конфиг для test самому и выставить соответствующее окружение.
Вообще, тестирование само по себе отдельная творческая задача, не проще программирования, и иногда надо поломать голову. Если хочешь, можем разобрать конкретные примеры кода, а без кода я могу только общими словами описать.
Что такое моки и стабы (англ): https://www.google.ru/search?q=mock+stubs&newwindow=1&gbv=1&sei=5CKeWdriB-Kb6ATuj5CwAg
Мок это поддельный класс, который совместим с исходным, но возвращает заранее заложенные в него данные. Мок репозитория не лезет в базу, а просто возвращает положенные в него ранее объекты.
Вообще, я бы советовал избегать делать моки репозитория. Так как в большинстве случаев это делает плохие тесты, полагающиеся на знание внутреннего устройства класса. Ну и сделать имитацию базы данных довольно сложно.
Вот есть у тебя функция, возвращающая 5 последних новостей:
function getLastNews(EntityManagerInterface $em)
Ты можешь передать вместо $em мок, в который положить мок репозитория NewsRepository. Но погоди! А откуда ты знаешь, что функция getlastNews будет обращаться к этому репозиторию? Ты закладываешь в тест знание того, как работает функция внутри и такой тест легко будет ломаться при любых изменениях в ней. Ну допустим, завтра кто-то добавит вывод рядом с новостью аватарки автора, а твой тест не предусмотрел мок репозитория аватарок.
Для такой функции, которая завязана намертво на БД, лучше всего просто сделать тестовую БД с известными новостями, и проверять:
- что она возвращает новости
Если хочется проверить, что возвращаются самые новые новости, можно вставить новую новость перед тестом и проверить, что она будет первой.
> Чет я не понял, они предлагают проверять не результат запроса (набор объектов), а сам запрос (sql строку)?
Это какой-то очень странный пример, согласен. Ведь код-пользователь тестируемого метода createSearchByNameQueryBuilder($name) не будет анализировать SQL код, который он генерирует. А что он будет делать? Скорее всего, он использует этот query bulder для поиска данных в БД. Ну значит, тут нет смысла писать юнит-тест и надо делать функциональный тест, который проверяет, что полученный query builder ищет записи, подходящие под условие и не ищет неподходящие (такие записи можно вставить прямо перед тестом).
Вот я такие ошибки часто вижу, некоторые люди плохо знакомы с тестированием, возможно, только читали про него, но не применяли на практике и что-то не понимают.
Что такое моки и стабы (англ): https://www.google.ru/search?q=mock+stubs&newwindow=1&gbv=1&sei=5CKeWdriB-Kb6ATuj5CwAg
Мок это поддельный класс, который совместим с исходным, но возвращает заранее заложенные в него данные. Мок репозитория не лезет в базу, а просто возвращает положенные в него ранее объекты.
Вообще, я бы советовал избегать делать моки репозитория. Так как в большинстве случаев это делает плохие тесты, полагающиеся на знание внутреннего устройства класса. Ну и сделать имитацию базы данных довольно сложно.
Вот есть у тебя функция, возвращающая 5 последних новостей:
function getLastNews(EntityManagerInterface $em)
Ты можешь передать вместо $em мок, в который положить мок репозитория NewsRepository. Но погоди! А откуда ты знаешь, что функция getlastNews будет обращаться к этому репозиторию? Ты закладываешь в тест знание того, как работает функция внутри и такой тест легко будет ломаться при любых изменениях в ней. Ну допустим, завтра кто-то добавит вывод рядом с новостью аватарки автора, а твой тест не предусмотрел мок репозитория аватарок.
Для такой функции, которая завязана намертво на БД, лучше всего просто сделать тестовую БД с известными новостями, и проверять:
- что она возвращает новости
Если хочется проверить, что возвращаются самые новые новости, можно вставить новую новость перед тестом и проверить, что она будет первой.
> Чет я не понял, они предлагают проверять не результат запроса (набор объектов), а сам запрос (sql строку)?
Это какой-то очень странный пример, согласен. Ведь код-пользователь тестируемого метода createSearchByNameQueryBuilder($name) не будет анализировать SQL код, который он генерирует. А что он будет делать? Скорее всего, он использует этот query bulder для поиска данных в БД. Ну значит, тут нет смысла писать юнит-тест и надо делать функциональный тест, который проверяет, что полученный query builder ищет записи, подходящие под условие и не ищет неподходящие (такие записи можно вставить прямо перед тестом).
Вот я такие ошибки часто вижу, некоторые люди плохо знакомы с тестированием, возможно, только читали про него, но не применяли на практике и что-то не понимают.
Начинать можно со smole test - просто проверить, то код не падает при вызове функции/контроллера/запросе страницы.
Также, если надо, могу нескромно дать пример своего проекта с тестами. Вот тут есть smoke test например (testPagesAreWorking): https://github.com/codedokode/task-checker/blob/master/tests/Web/ControllersTest.php
https://github.com/telepok/php-test/blob/master/oop4.php
Я правильно понимаю, что если элемент массива содержит объект, то после удаления этого элемента массива объект автоматически уничтожится? Иди там более сложный механизм, сборщик мусора и все такое? В офф.доке не нашел, но сильно не искал, признаюсь.
Получилось много методов вида getFoo и setFoo, может имеет смысл заменить на один метод Get и Set с параметрами? Как вообще правильно это делать?
Когда ты куда-то передаешь объект, сохраняешь в переменную,в массив - везде передается только ссылка на объект.
Когда ни одной ссылки на объект не остается, объект становится недоступным (то есть обратиться к нему больше невозможно) и будет удален сборщиком мусора рано или поздно. Соответственно, специально делать ничего не требуется.
if (первое условие && второе условие)
В PHP операторы сравнения вроде < или == возвращают специальное логическое значение true(истина) или false (ложь). Например var_dump(2 < 5); выведет true, а 2 > 5 вернет false.
Над логическими значениями можно делать операции:
&& значит "и", то есть оба условия должны выполняться
|| значит "или", то есть выполняться должно хотя бы одно из условий
!(условие) значит "не", то есть меняет результат проверки на протьивоположный
Подробнее советую почитать в мануале
http://php.net/manual/ru/language.types.boolean.php
http://php.net/manual/ru/control-structures.if.php
http://php.net/manual/ru/language.operators.logical.php
Изучи булев тип и обрати внимание, как изящно решена проблема объединения разных условий.
Использую $diff = array_diff($array1, $array2);
И по нему выходит, что разницы нет, хотя массивы отличаются. Специально сделал вывод - ну отличаются же.
$array1
Array (
[0] => Array (
[id] => 1 )
[1] => Array (
[id] => 2 )
[2] => Array (
[id] => 3 )
[3] => Array (
[id] => 4 )
)
$array2
Array (
[0] => Array (
[id] => 1 )
[3] => Array (
[id] => 4 )
)
Что за ерунда? Ну не может же так быть. Перед diff я создал второй массив, как копию первого и выкусил у него через unset($array2[$value]); пару строчек. Может diff работает со старой версийй массива? Бред какой-то.
Использую $diff = array_diff($array1, $array2);
И по нему выходит, что разницы нет, хотя массивы отличаются. Специально сделал вывод - ну отличаются же.
$array1
Array (
[0] => Array (
[id] => 1 )
[1] => Array (
[id] => 2 )
[2] => Array (
[id] => 3 )
[3] => Array (
[id] => 4 )
)
$array2
Array (
[0] => Array (
[id] => 1 )
[3] => Array (
[id] => 4 )
)
Что за ерунда? Ну не может же так быть. Перед diff я создал второй массив, как копию первого и выкусил у него через unset($array2[$value]); пару строчек. Может diff работает со старой версийй массива? Бред какой-то.
Мануал открыв, ты увидишь http://php.net/manual/ru/function.array-diff.php
> Замечание:
> Два элемента считаются одинаковыми тогда и только тогда, если (string) $elem1 === (string) $elem2. Другими словами, когда их строковое представление идентично.
> Замечание:
> Обратите внимание, что эта функция обрабатывает только одно измерение n-размерного массива. Естественно, вы можете обрабатывать и более глубокие уровни вложенности, например, используя array_diff($array1[0], $array2[0]);.
Задача про кредитный калькулятор и веб-сервер. Все работает. Но одна строчка очень тупит. Понять не могу что не так.
https://pastebin.com/eX1ATeNW
И второй вопрос про кукисы. Задача:
"Сделай скрипт, запоминающий сколько раз пользователь заходил на страницу и показывающий ему это число: "добро пожаловать - в N-й раз". Для хранения надо использовать куки, чтобы у каждого пользователя был свой счетчик."
Ничего лучше чем setcookie($login."count",($_COOKIE[$login."count"] + 1)) я не придумал.
Поясни пожалуйста, что значит "очень тупит"? Выдает ошибку? Долго выполняется? Если долго выполняется, то какая строчка и как ты определил, что проблема именно в ней?
По поводу кода: к сожалению, в нем логика расчетов и HTML-код перемешаны. Такой код трудно читать и поддерживать. Их надо разделить - либо так, поместив все в один файл:
в начале только php-код, сохраняющий результат в переменные
далее только HTML-код с выводом переменных
Либо на 2 отдельных файла: в одном логика, в другом HTML-код. Первый файл вычисляет значения, сохраняет в переменные и подключает через require второй файл.
Подробнее про шаблоны http://web.archive.org/web/20161119062218/http://www.phpinfo.su/articles/practice/shablony_v_php.html
> if (((int)$_GET[$key]==false)
Это условие бессмысленно так как в $_GET могут быть только строки или массивы строк. false там быть не может. Так как они берутся из URL (query string) который сам является строкой.
(int)$_GET[$key] дает int, который опять же не может быть равен false.
Чтобы твой HTML код всегда корректно отображался, добавь в начало тег meta с указанием кодировки: http://htmlbook.ru/html/meta/charset
Также, твой код сейчас написан стеной. Это неудобно, так как ты например не можешь выйти из него при отсутствии каких-то данных, да и разбирать такой код тяжело. Нужно вынести из него действия в отдельные функции, ну например:
- сделать функцию проверки правильности входных данных
- сделать функцию расчета кредита
При этом надо тщательно продумать, что функции получают на вход, что выдают на выходе.
Также, может быть тебе поможет урок про обработку форм: https://github.com/codedokode/pasta/blob/master/forms.md (там упоминается редирект, это нужно для защиты от повторной отправки при обновлении страницы, у тебя такой проблемы нет и редирект не требуется).
Если что-то непонятно - спрашивай.
> Ничего лучше чем setcookie($login."count",($_COOKIE[$login."count"] + 1)) я не придумал.
Это подойдет, только надо делать проверку, есть ли такая кука, чтобы не было обращения к несуществующему элементу массива. И хорошо бы и тут код разбить на функции, просто чтобы учиться писать в таком стиле, а не стеной.
$login там не нужен.
Поясни пожалуйста, что значит "очень тупит"? Выдает ошибку? Долго выполняется? Если долго выполняется, то какая строчка и как ты определил, что проблема именно в ней?
По поводу кода: к сожалению, в нем логика расчетов и HTML-код перемешаны. Такой код трудно читать и поддерживать. Их надо разделить - либо так, поместив все в один файл:
в начале только php-код, сохраняющий результат в переменные
далее только HTML-код с выводом переменных
Либо на 2 отдельных файла: в одном логика, в другом HTML-код. Первый файл вычисляет значения, сохраняет в переменные и подключает через require второй файл.
Подробнее про шаблоны http://web.archive.org/web/20161119062218/http://www.phpinfo.su/articles/practice/shablony_v_php.html
> if (((int)$_GET[$key]==false)
Это условие бессмысленно так как в $_GET могут быть только строки или массивы строк. false там быть не может. Так как они берутся из URL (query string) который сам является строкой.
(int)$_GET[$key] дает int, который опять же не может быть равен false.
Чтобы твой HTML код всегда корректно отображался, добавь в начало тег meta с указанием кодировки: http://htmlbook.ru/html/meta/charset
Также, твой код сейчас написан стеной. Это неудобно, так как ты например не можешь выйти из него при отсутствии каких-то данных, да и разбирать такой код тяжело. Нужно вынести из него действия в отдельные функции, ну например:
- сделать функцию проверки правильности входных данных
- сделать функцию расчета кредита
При этом надо тщательно продумать, что функции получают на вход, что выдают на выходе.
Также, может быть тебе поможет урок про обработку форм: https://github.com/codedokode/pasta/blob/master/forms.md (там упоминается редирект, это нужно для защиты от повторной отправки при обновлении страницы, у тебя такой проблемы нет и редирект не требуется).
Если что-то непонятно - спрашивай.
> Ничего лучше чем setcookie($login."count",($_COOKIE[$login."count"] + 1)) я не придумал.
Это подойдет, только надо делать проверку, есть ли такая кука, чтобы не было обращения к несуществующему элементу массива. И хорошо бы и тут код разбить на функции, просто чтобы учиться писать в таком стиле, а не стеной.
$login там не нужен.
Внешний вид элементов задается в CSS. С помощью HTML разметки мы задаем не вид, а назначение элемента, чем он является - ссылкой, заголовком, иллюстрацией, абзацем текста и тд.
Просто в задании про куки нужно чтобы счетчик считался для разных пользователей. Вот я и подумал добавлять логин к имени куки, чтобы имя куки было уникальным.
А в первом задании 47 строка как раз и вызывает сообщение "что-то там дольше 30 секунд".
А ты понимаешь, как работают куки? Они хранятся в браузере и у каждого пользователя свои куки.
"дольше 30 секунд" - значит у тебя неправильно написан алгоритм, и получился вечный цикл, который никогда не закончится.
У каждого пользователя свои куки.
Ну вот если на одном компьютере заходят на сайт разные люди. Чтобы счетчик был у каждого свой. Я добавляю имя пользователя в имя счетчика куки.
https://github.com/pricklynut/testhub
Прилинкую пасту с вопросами, чтобы не потерять >>1049105
По тестированию.
Как в постгрес создать бд для тестов в памяти, как в mysql? Ну или хотя бы просто создать бд из консоли.
doctrine:database:create выдает ошибку, что база данных не существует. Конечно не существует, я же
как раз этой командой хочу ее создать!
Если без шуток, я понимаю что проблема в этой "оригинальной" системе ролей и прав постгреса, он просто
не разрешает создать базу нерутовому пользователю. Вопрос как быть в такой ситуации? Я пока использую mysql для тестов, он разрешает создавать базу.
Пытаюсь в setUp запустить миграции и фикстуры через консольную команду, вроде тесты выполняются, но напоследок
ругается нотисами
The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead: 3x
2x in ApplicationAvailabilityFunctionalTest::setUpBeforeClass from Tests\AppBundle
1x in ApplicationAvailabilityFunctionalTest::tearDownAfterClass from Tests\AppBundle
Я вроде никаких ConsoleEvents не трогал, там приблизительно такой код:
$app = new Application(static::createClient()->getKernel());
$app->run(new StringInput("doctrine:create:database"));
В мануале вроде так рекомендуют вызывать консольные команды, не знаю, может я что-то не так делаю.
https://symfony.com/doc/current/console/command_in_controller.html
При установке symfony standard edition в папке с тестами был хелловорлд с ассертами
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$this->assertContains('Welcome to Symfony', $crawler->filter('#container h1')->text());
Нужно проверять именно на 200 статус? Часто ли на практике встречаются другие 2xx статусы?
Просто в других примерах чаще используют response->isSuccessful().
По второму ассерту: логично ли проверять контент страницы, в данном случае содержимое h1?
А вдруг верстальщик его поменяет, или заменит h1 на див?
Короче я у себя проверил только наличие элементов с классом .item (на странице выводится список последних тестов),
и что у каждого из них есть заголовок (ну то есть что не выводятся пустые контейнеры).
В составе symfony standard edition идет в комплекте некий simple-phpunit.
Что это за зверь и в чем его особенности? Ну например я phpstorm под него не могу настроить, чтобы тесты запускались
из ide, пишет Class 'PHPUnit_TextUI_ResultPrinter' not found. Погуглил на stackoverflow, кажется phpstorm хочет старую
версию phpunit (без неймспейсов, с подчеркиваниями)? Или это связано с simple-phpunit, может у него чего-то не хватает?
Что делать с приватными методами? Как их тестить в смысле. И нужно ли тестить. Приватные методы используются внутри тестируемых, просто чтобы избежать дублирования или нагромождения кода.
https://github.com/pricklynut/testhub
Прилинкую пасту с вопросами, чтобы не потерять >>1049105
По тестированию.
Как в постгрес создать бд для тестов в памяти, как в mysql? Ну или хотя бы просто создать бд из консоли.
doctrine:database:create выдает ошибку, что база данных не существует. Конечно не существует, я же
как раз этой командой хочу ее создать!
Если без шуток, я понимаю что проблема в этой "оригинальной" системе ролей и прав постгреса, он просто
не разрешает создать базу нерутовому пользователю. Вопрос как быть в такой ситуации? Я пока использую mysql для тестов, он разрешает создавать базу.
Пытаюсь в setUp запустить миграции и фикстуры через консольную команду, вроде тесты выполняются, но напоследок
ругается нотисами
The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead: 3x
2x in ApplicationAvailabilityFunctionalTest::setUpBeforeClass from Tests\AppBundle
1x in ApplicationAvailabilityFunctionalTest::tearDownAfterClass from Tests\AppBundle
Я вроде никаких ConsoleEvents не трогал, там приблизительно такой код:
$app = new Application(static::createClient()->getKernel());
$app->run(new StringInput("doctrine:create:database"));
В мануале вроде так рекомендуют вызывать консольные команды, не знаю, может я что-то не так делаю.
https://symfony.com/doc/current/console/command_in_controller.html
При установке symfony standard edition в папке с тестами был хелловорлд с ассертами
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$this->assertContains('Welcome to Symfony', $crawler->filter('#container h1')->text());
Нужно проверять именно на 200 статус? Часто ли на практике встречаются другие 2xx статусы?
Просто в других примерах чаще используют response->isSuccessful().
По второму ассерту: логично ли проверять контент страницы, в данном случае содержимое h1?
А вдруг верстальщик его поменяет, или заменит h1 на див?
Короче я у себя проверил только наличие элементов с классом .item (на странице выводится список последних тестов),
и что у каждого из них есть заголовок (ну то есть что не выводятся пустые контейнеры).
В составе symfony standard edition идет в комплекте некий simple-phpunit.
Что это за зверь и в чем его особенности? Ну например я phpstorm под него не могу настроить, чтобы тесты запускались
из ide, пишет Class 'PHPUnit_TextUI_ResultPrinter' not found. Погуглил на stackoverflow, кажется phpstorm хочет старую
версию phpunit (без неймспейсов, с подчеркиваниями)? Или это связано с simple-phpunit, может у него чего-то не хватает?
Что делать с приватными методами? Как их тестить в смысле. И нужно ли тестить. Приватные методы используются внутри тестируемых, просто чтобы избежать дублирования или нагромождения кода.
Ну.Мануал. Открывал и читал. Вот пусть первый уровень вложденности и обрабатывает. В одном это строки [0] => Array и в другом тоже. Что не так-то?
Можешь дать пример, как эти массивы правильно сравнивать, чтобы получить разницу?
>Сравнивает array1 с одним или несколькими другими массивами и возвращает значения из array1, которые отсутствуют во всех других массивах.
Толсто.
https://ideone.com/WhnvHU
А разгадка проста: у тебя везде значение Array, поэтому и одинаковые.
Смысл в том, что array_diff работает только с массивами строк или чисел. С массивами массивов она не работает. Нужно либо писать свое решение, либо гуглить готовое (например по словам php array diff recursive).
> Где можно почитать про кеши в симфони? (точное место в документации, я там пока не нашел ответа на свой вопрос)
Если в документации нет, открывай исходники и изучай - PHP ведь ты читать умеешь? Поначалу будет непросто, но в ходе изучения кода ты сможешь изучить какие-то приемы, и лучше будешь понимать, как Симфони устроена внутри. Ни разу не заглядывая в код, наверно, в Симфони не разобраться.
Прежде чем ковырять исходники, стоит также почитать про бандлы и компоненты в Симфони. Компоненты - это независимые части, которые можно использовать отдельно, а бандлы - это части фреймворка Симфони.
Я попробовал вспомнить, какие кеши я знаю:
1) opcache - это не часть Симфони, а часть PHP5.5+. Он кеширует в shared memory (разделяемой памяти) скомпилированные PHP скрипты. Так как Симфони в ходе работы подключает много классов, на их компиляцию будет уходить заметное время без opcache. opcache на дев-сервере обычно настраивают, чтобы он проверял дату модификации файла, и компилировал его заново при изменении. На продакшене обычно эту проверку отключают, а для сброса кеша либо явно вызвыают opcache_clear() при деплое, либо делают новый деплой в новую папку, чтобы изменились путь к файлам (это позволяет сделать безшовное переключение между версиями).
Доки:
- https://symfony.com/doc/current/performance.html#use-a-byte-code-cache-e-g-opcache
- http://php.net/manual/en/book.opcache.php
Ты можешь себе также поставить https://github.com/amnuts/opcache-gui и посмотреть с его помощью, как у тебя работает этот кеш.
opache недоступен в командной строке, точнее, его можно включить, но смысла нет, так как он уничтожится при завершении процесса (update: в PHP7 кое-что доработали для командной строки, но пока плохо: https://stackoverflow.com/questions/25044817/zend-opcache-opcache-enable-cli-1-or-0-what-does-it-do/35880017#35880017 ).
2) bootstrap cache - это кеш, который содержит в одном файле классы, которые нужны на каждом запросе. Он описан тут https://symfony.com/doc/current/performance.html#use-bootstrap-files
Он создается автоматически после установки/обновления пакетов композером, его вызов виден тут:
- https://github.com/symfony/symfony-standard/blob/3.3/composer.json#L36
- https://github.com/sensiolabs/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php#L59
- информация по событиям композера https://getcomposer.org/doc/articles/scripts.md
В его основе лежит идея, что подключить один файл быстрее, чем много мелких. Я не знаю, насколько это верно в текущей версии PHP, тем более при наличии opcache. Судя по тому, что классов там немного, видимо это не дает особой выгоды.
Раньше там было больше классов: https://github.com/sensiolabs/SensioDistributionBundle/blob/0db645c7e01286d7ca8fa79b61539f6aa644b25c/Composer/ScriptHandler.php#L158
Разумеется, ты можешь вызвать эту команду вручную, как именно, предлагаю выяснить самому (ибо я не помню).
3) Кеш (сборка) конфига, DI контейнера и роутера. Надеюсь, ты знаешь что такое DI, и что в Симфони есть DI контейнер, который конфигурируется с помощью YML файлов. А также роутер, который конфигурируется либо yml-файлами, либо аннотациями в контроллерах. А также куча файлов конфигов и параметров.
Вдобавок, каждый бандл может добавлять свлои роуты, DI сервисы и параметры конфига, а также содержать код разбора и валидации этих параметров. И бандлов могут быть подключены десятки. И делать все это надо на каждый запрос заново.
Очевидно, что этот процесс очень небыстрый (я помню, как-то обнаружил, как в моем первом самописном фреймворке самой тяжелой частью был разбор yml-конфига), особенно если бандлов много. Потому Симфони, прочитав конфиги и провалидировав их, генерирует (компилирует) класс, содержащий код создания сервисов и параметры конфига. Ну например, если у тебя описан сервис вида
my_service:
class: MyClass
arguments: ['yes', @dependency1, @dependency2]
То из него будет сгенерирован примерно такой код:
public function get_my_service()
{
return new MyClass('yes', $this->get_dependency1(), $this->get_dependency2());
}
Параметры конфигурации также "впекаются" в этот класс в виде массива.
После генерации кеш сохраняется куда-то в файл вроде var/cache/prod/scontainer.php, обязательно найди его у себя и изучи.
На dev дополнительно генерируется файл со списком исходных файлов (конфигов) и проверяется, не обновились ли они, и не надо ли перегенерировать кеш. Эта проверка, кстати, тоже не бесплатная. На продакшене проверки нет и ты должен явно генерировать кеш при деплое.
Аналогично из конфигов роутера генерируются 2 php класса - для разбора URL (UrlMatcher) и для генерации (UrlGenerator).
Генерация этих кешей запускается командой cache:clear/cache:warmup, на dev запускается автоматически при отстутвии или устаревашии кеша относительно исходных файлов.
Документация:
- https://symfony.com/doc/current/components/dependency_injection/compilation.html
- https://symfony.com/doc/current/components/dependency_injection/workflow.html
- https://symfony.com/doc/current/components/config/caching.html
- https://blog.whiteoctober.co.uk/2014/02/25/symfony2-cache-warmup-explained/
- https://symfony.com/doc/current/deployment.html
- https://symfony.com/blog/new-in-symfony-3-3-deprecated-cache-clear-with-warmup
В исходном коде проверки и вызов генерации кеша делаются для контейнера в Kernel, а для роутера в Router:
- https://github.com/symfony/http-kernel/blob/master/Kernel.php#L102
- https://github.com/symfony/http-kernel/blob/master/Kernel.php#L484
- https://github.com/symfony/routing/blob/master/Router.php#L269
Изучи код, изучи сгенерированные у себя файлы.
Заметь, что ты можешь добавлять свои CompilationPasses в процесс сборки контейнера и роутера. Также, в своем бандлы ты можешь добавлять произвольные роуты, параметры конфига, код для их валидации.
Эти все сложности возникают из-за того, что PHP приложение перезапускается при каждом запросе. В других языках, где приложение не умирает после обработки запроса, эти кеши не нужны и скомпилированный контейнер просто держат в памяти (в PHP такой режим тоже возможен, если хочешь, можем попробовать сделать его для твоего приложения и сравнить производительность, правда придется немного переделать внутренности Симфони).
4) Кеш шаблонов твига. Шаблоны твига компилируются в PHP-код перед выполнением, и этот PHP-код сохраняется на диск (а также кешируется в opache). На dev проверяется дата модификации исходного шаблона, на продакшене, разумеется, нет. Судя по документации, команда cache:warmup как-то находит (как?) все шаблоны и компилирует их в PHP-код. Разумно.
Вот, кстати, код команд cache:clear и warmup:
- https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
- https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php
5) Кеши метаданных Доктрины, кеш DQL, прокси-классы сущностей Доктрины.
Доктрина читает метаданные сущностей из аннотаций. В результате чтения для каждой аннотации создается объект (пример объекта, соответствующего аннотации ORM\Column: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Mapping/Column.php ). Эти объекты кешируются, можно выбрать куда. На продакшене, по умолчанию они сохраняются в php-файлы, и соответственно в opcache, на dev - не помню, по моему аналогично. При желании это меняется через конфиг. Поищи эти файлы у себя в var/cache/doctrine/
Код, кеширующий аннотации: https://github.com/doctrine/annotations/blob/master/lib/Doctrine/Common/Annotations/CachedReader.php
Кстати, аннотации расширяемые, и ты можешь добавить свои аннотации. Например, ты можешь написать конвертор сущностей в JSON на основе аннотаций.
Далее, Доктрина тратит много времени на разбор DQL запросов, потому результаты разбора тоже кешируются, аналогичным образом. Код: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Query.php#L237
Далее, есть еще прокси-классы. Для ленивой загрузки (разберись, что это и как работает) необходимо для каждой сущности Доктрины создать прокси-класс-наследник (кстати, почему наследник?). На продакшене они генерируются и сохраняются в кеш при warmup, а вот на dev по моему просто генерируются на лету в памяти при первом обращении к сущности, потому на dev доктрина тормозит. Почему так? Разберись, почему, и можно ли в доктрине на dev кешировать прокси, проверять дату модификации файлов и автоматически их обновлять. Код:
- https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Proxy/ProxyGenerator.php
- https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Proxy/ProxyFactory.php
- https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php
Настройки кеширования в Симфони задаются либо в DoctrineBundle, либо используются те, что по умолчанию в Доктрине, можешь поискать самостоятельно.
Другие кеши я пока вспомнить не могу. Думаю, тебе пока хватит этой информации, читай код, изучай. Сам понимаешь, делая хелло ворлды по туториалам, знатоком Симфони и Доктрины ты не станешь.
Разобравшись с этим, надеюсь, ты сможешь понять, почему у тебя кеш не так генерируется. Я думаю, у тебя кривые конфиги или криво выбрано окружение, из-за того, что ты не разбирался, как все внутри устроено. Если ты используешь линукс, ты также можешь запустить cache:clear под strace, чтобы увидеть, какие именно файлы она генерирует.
> Где можно почитать про кеши в симфони? (точное место в документации, я там пока не нашел ответа на свой вопрос)
Если в документации нет, открывай исходники и изучай - PHP ведь ты читать умеешь? Поначалу будет непросто, но в ходе изучения кода ты сможешь изучить какие-то приемы, и лучше будешь понимать, как Симфони устроена внутри. Ни разу не заглядывая в код, наверно, в Симфони не разобраться.
Прежде чем ковырять исходники, стоит также почитать про бандлы и компоненты в Симфони. Компоненты - это независимые части, которые можно использовать отдельно, а бандлы - это части фреймворка Симфони.
Я попробовал вспомнить, какие кеши я знаю:
1) opcache - это не часть Симфони, а часть PHP5.5+. Он кеширует в shared memory (разделяемой памяти) скомпилированные PHP скрипты. Так как Симфони в ходе работы подключает много классов, на их компиляцию будет уходить заметное время без opcache. opcache на дев-сервере обычно настраивают, чтобы он проверял дату модификации файла, и компилировал его заново при изменении. На продакшене обычно эту проверку отключают, а для сброса кеша либо явно вызвыают opcache_clear() при деплое, либо делают новый деплой в новую папку, чтобы изменились путь к файлам (это позволяет сделать безшовное переключение между версиями).
Доки:
- https://symfony.com/doc/current/performance.html#use-a-byte-code-cache-e-g-opcache
- http://php.net/manual/en/book.opcache.php
Ты можешь себе также поставить https://github.com/amnuts/opcache-gui и посмотреть с его помощью, как у тебя работает этот кеш.
opache недоступен в командной строке, точнее, его можно включить, но смысла нет, так как он уничтожится при завершении процесса (update: в PHP7 кое-что доработали для командной строки, но пока плохо: https://stackoverflow.com/questions/25044817/zend-opcache-opcache-enable-cli-1-or-0-what-does-it-do/35880017#35880017 ).
2) bootstrap cache - это кеш, который содержит в одном файле классы, которые нужны на каждом запросе. Он описан тут https://symfony.com/doc/current/performance.html#use-bootstrap-files
Он создается автоматически после установки/обновления пакетов композером, его вызов виден тут:
- https://github.com/symfony/symfony-standard/blob/3.3/composer.json#L36
- https://github.com/sensiolabs/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php#L59
- информация по событиям композера https://getcomposer.org/doc/articles/scripts.md
В его основе лежит идея, что подключить один файл быстрее, чем много мелких. Я не знаю, насколько это верно в текущей версии PHP, тем более при наличии opcache. Судя по тому, что классов там немного, видимо это не дает особой выгоды.
Раньше там было больше классов: https://github.com/sensiolabs/SensioDistributionBundle/blob/0db645c7e01286d7ca8fa79b61539f6aa644b25c/Composer/ScriptHandler.php#L158
Разумеется, ты можешь вызвать эту команду вручную, как именно, предлагаю выяснить самому (ибо я не помню).
3) Кеш (сборка) конфига, DI контейнера и роутера. Надеюсь, ты знаешь что такое DI, и что в Симфони есть DI контейнер, который конфигурируется с помощью YML файлов. А также роутер, который конфигурируется либо yml-файлами, либо аннотациями в контроллерах. А также куча файлов конфигов и параметров.
Вдобавок, каждый бандл может добавлять свлои роуты, DI сервисы и параметры конфига, а также содержать код разбора и валидации этих параметров. И бандлов могут быть подключены десятки. И делать все это надо на каждый запрос заново.
Очевидно, что этот процесс очень небыстрый (я помню, как-то обнаружил, как в моем первом самописном фреймворке самой тяжелой частью был разбор yml-конфига), особенно если бандлов много. Потому Симфони, прочитав конфиги и провалидировав их, генерирует (компилирует) класс, содержащий код создания сервисов и параметры конфига. Ну например, если у тебя описан сервис вида
my_service:
class: MyClass
arguments: ['yes', @dependency1, @dependency2]
То из него будет сгенерирован примерно такой код:
public function get_my_service()
{
return new MyClass('yes', $this->get_dependency1(), $this->get_dependency2());
}
Параметры конфигурации также "впекаются" в этот класс в виде массива.
После генерации кеш сохраняется куда-то в файл вроде var/cache/prod/scontainer.php, обязательно найди его у себя и изучи.
На dev дополнительно генерируется файл со списком исходных файлов (конфигов) и проверяется, не обновились ли они, и не надо ли перегенерировать кеш. Эта проверка, кстати, тоже не бесплатная. На продакшене проверки нет и ты должен явно генерировать кеш при деплое.
Аналогично из конфигов роутера генерируются 2 php класса - для разбора URL (UrlMatcher) и для генерации (UrlGenerator).
Генерация этих кешей запускается командой cache:clear/cache:warmup, на dev запускается автоматически при отстутвии или устаревашии кеша относительно исходных файлов.
Документация:
- https://symfony.com/doc/current/components/dependency_injection/compilation.html
- https://symfony.com/doc/current/components/dependency_injection/workflow.html
- https://symfony.com/doc/current/components/config/caching.html
- https://blog.whiteoctober.co.uk/2014/02/25/symfony2-cache-warmup-explained/
- https://symfony.com/doc/current/deployment.html
- https://symfony.com/blog/new-in-symfony-3-3-deprecated-cache-clear-with-warmup
В исходном коде проверки и вызов генерации кеша делаются для контейнера в Kernel, а для роутера в Router:
- https://github.com/symfony/http-kernel/blob/master/Kernel.php#L102
- https://github.com/symfony/http-kernel/blob/master/Kernel.php#L484
- https://github.com/symfony/routing/blob/master/Router.php#L269
Изучи код, изучи сгенерированные у себя файлы.
Заметь, что ты можешь добавлять свои CompilationPasses в процесс сборки контейнера и роутера. Также, в своем бандлы ты можешь добавлять произвольные роуты, параметры конфига, код для их валидации.
Эти все сложности возникают из-за того, что PHP приложение перезапускается при каждом запросе. В других языках, где приложение не умирает после обработки запроса, эти кеши не нужны и скомпилированный контейнер просто держат в памяти (в PHP такой режим тоже возможен, если хочешь, можем попробовать сделать его для твоего приложения и сравнить производительность, правда придется немного переделать внутренности Симфони).
4) Кеш шаблонов твига. Шаблоны твига компилируются в PHP-код перед выполнением, и этот PHP-код сохраняется на диск (а также кешируется в opache). На dev проверяется дата модификации исходного шаблона, на продакшене, разумеется, нет. Судя по документации, команда cache:warmup как-то находит (как?) все шаблоны и компилирует их в PHP-код. Разумно.
Вот, кстати, код команд cache:clear и warmup:
- https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
- https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php
5) Кеши метаданных Доктрины, кеш DQL, прокси-классы сущностей Доктрины.
Доктрина читает метаданные сущностей из аннотаций. В результате чтения для каждой аннотации создается объект (пример объекта, соответствующего аннотации ORM\Column: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Mapping/Column.php ). Эти объекты кешируются, можно выбрать куда. На продакшене, по умолчанию они сохраняются в php-файлы, и соответственно в opcache, на dev - не помню, по моему аналогично. При желании это меняется через конфиг. Поищи эти файлы у себя в var/cache/doctrine/
Код, кеширующий аннотации: https://github.com/doctrine/annotations/blob/master/lib/Doctrine/Common/Annotations/CachedReader.php
Кстати, аннотации расширяемые, и ты можешь добавить свои аннотации. Например, ты можешь написать конвертор сущностей в JSON на основе аннотаций.
Далее, Доктрина тратит много времени на разбор DQL запросов, потому результаты разбора тоже кешируются, аналогичным образом. Код: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Query.php#L237
Далее, есть еще прокси-классы. Для ленивой загрузки (разберись, что это и как работает) необходимо для каждой сущности Доктрины создать прокси-класс-наследник (кстати, почему наследник?). На продакшене они генерируются и сохраняются в кеш при warmup, а вот на dev по моему просто генерируются на лету в памяти при первом обращении к сущности, потому на dev доктрина тормозит. Почему так? Разберись, почему, и можно ли в доктрине на dev кешировать прокси, проверять дату модификации файлов и автоматически их обновлять. Код:
- https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Proxy/ProxyGenerator.php
- https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Proxy/ProxyFactory.php
- https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php
Настройки кеширования в Симфони задаются либо в DoctrineBundle, либо используются те, что по умолчанию в Доктрине, можешь поискать самостоятельно.
Другие кеши я пока вспомнить не могу. Думаю, тебе пока хватит этой информации, читай код, изучай. Сам понимаешь, делая хелло ворлды по туториалам, знатоком Симфони и Доктрины ты не станешь.
Разобравшись с этим, надеюсь, ты сможешь понять, почему у тебя кеш не так генерируется. Я думаю, у тебя кривые конфиги или криво выбрано окружение, из-за того, что ты не разбирался, как все внутри устроено. Если ты используешь линукс, ты также можешь запустить cache:clear под strace, чтобы увидеть, какие именно файлы она генерирует.
>>1051679
> И как правильно объявить переменную среды?
В Апаче и в настройках пользователя (.bashrc например). Вообще, идея задавать настройки через переменные окружения - описана в 12-factor app: https://12factor.net/ru/config и мне не нравится. Она хорошо работает только на хостингах с read-only файловой системой, и в докере. Представь, что у тебя несколько приложений с разными конфигурациями - замучаешься переменные переключать. Отчасти эту проблему можно решить, выделив на каждое приложение своего пользователя.
> Может в /etc/environment?
Это задает переменную для всех пользователей в системе и всех проектов, что плохо.
> Да, только в документации везде пишут пути типа {% javascripts '@AppBundle/Resources/public/js/*' %}
> а я теперь не могу понять как прописать путь к папке app/Resorces.
Ну это надо смотреть документацию либо код assetic, это тег из него. Ты смотрел? Если в документации нет, открой код assetic и найди там класс вроде TwigExtension и там код этого тега.
> Это composer.json который идет с фреймворком, я туда не заглядывал даже,
Это файл из шаблона (заготовки) приложения. То что идет с фреймворком, и что править нельзя, находится в vendor.
> И на что это влияет кстати? Композер просто откажется устанавливать зависимости, если версия php ниже требуемой?
Документирует зависимость явно, в машинно-читаемом виде. Да, композер это проверяет, изучи мануал: https://getcomposer.org/doc/01-basic-usage.md#platform-packages
> По доктрине, непонятен смысл этих owning и inverse side.
У нас есть 2 объекта и связь между ними, описанная в каждом из них. Представим, что A ссылается на B, но B не ссылается на A. Кому верить? owning side - это сторона, по которой определяется, есть связь или нет. inverse side не проверяется, связь в ней только для удобства использования.
В отношениях 1-1, 1-M owning side - это сторона, где хранится поле-внешний ключ.
> Doctrine will only check the owning side of an association for changes.
> ManyToOne is always the owning side of a bidirectional association.
> The owning side of a OneToOne association is the entity with the table containing the foreign key.
> Что такое bidirectional associations?
Это когда в A объявления связь с B, а в B - обратная связь с A. Обратная связь не обязательна.
> Как правильно настроить эти связи, где писать mappedBy, где inverseBy,
Как описано в мануале, mappedBy - в owning side, inverseBy - в inverse side, если она тебе нужна.
> Как сохраняются связанные сущности? Сохранять (persist) нужно только одну сторону, или обе?
persist ничего не сохраняет. persist передает сущность под управление Доктрины. У Доктрины есть список всех сущностей, которыми она управляет. Это загруженные из БД сущности и добавленные в список через persist(). Когда ты делаешь flush(), Доктрина обходит список, вычисляет изменения и генерирует SQL запросы на обновление БД в соответствии с сизменениями.
Если ты не сделаешь persist, то разумеется, Доктрина твою сущность не увидит и в БД не сохранит. Если ты добавишь в отношение не управляемую доктриной сущность, то есть 2 варианта:
- по умолчанию, это вызовет ошибку
- если у тебя в owning сущности стоит параметр cascade=persist, то Доктрина автоматически сделает persist всем добавленным сущностям при вызове persist для родителя
Типичный пример - ты создал Статью, создал и добавил в нее Комментарий, вызвал persist на статье и flush. Это вызовет ошибку.
Аналогично, кстати, remove() на Статье без вызова remove() для вложенных в нее сущностей (Комментариев) может вызвать аналогичную ошибку, так как ты удаляешь владельца, а сущности, которым он владеет, остаются сиротами.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations
> Enum и доктрина. Доктрина всячески побуждает отказываться от enum в пользу единичек/ноликов.
все прекрасно работает если выставить тип поля string.
> Почему так, неужели enum так плох?
Есть мнение (я с ним согласен только частично): http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/
> Просто если у меня поле типа is_correct('yes', 'no'), отказываются работать чекбоксы, они
ждут true/false.
Тогда надо использовать маппинг boolean, если у тебя true/false.
> Варианты, которые с вопросов в связи M-1, не удаляются.
По какой причине не удаляются? Может быть, нужен cascade remove в сущности?
Тут надо помнить, что формы по идее ничего про базу данных и доктрину не знают. Они работают с моделью. Они только могут вызывать у сущности removeItem(). И задача сущности и доктрины это корректно увидеть и сохранить.
Сделай тест: убери/добавь вручную вариант ответа к вопросу и проверь, сохраняется ли все. Может быть, сначала стоит написать тесты на модель данных (сущности), что любые их изменения корректно сохраняются в БД. Серьезно, попробуй для начала в тесте phpunit создать тест с вопросами и вариантами ответов и сохранить его, отредактировать, удалить итд. Протестируй, что все возможные операции работают.
Вообще, если ты почитаешь документацию и исходный код форм, ты узнаешь, что они взаимодействуют с моделью с помощью специального компонента property-access: https://symfony.com/doc/current/components/property_access.html
В документации этого нет, но в коде мы видим, что там есть специальный метод для обновления коллекции через вызовы add/remove: https://github.com/symfony/property-access/blob/master/PropertyAccessor.php#L568 и константа ACCESS_TYPE_ADDER_AND_REMOVER.
Мне кажется, что перенос данных между моделью и формой (которая представляет собой дерево объектов Form) может делаться тут: https://github.com/symfony/form/blob/master/Extension/Core/DataMapper/PropertyPathMapper.php
Попробуй изучить, в каких условиях вызвыается этот метод и как он работает.
Вообще, изучи компонент Form получше. Он сложный, но если ты возьмешь личточек и нарисуешь связи между объектами, то можно разобраться.
> Кстати, в некоторых связях в этих методах типа add вызывают аналогичный метод у связанного объекта,
Это можно делать, но только на одной стороне, чтобы не получился цикл.
> Только почему доктрина не может это сделать сама?
Как? Не забывай, что доктрина - это маппер, она находится сбоку от твоих сущностей и не может перехватить вызов setTask(). Вообще, ее задача просто синхронизировать данные в БД и в твоих сущностях.
Она может нормализовывать связи только при flush, если этого не делает, не знаю, почему, может быть из соображений производительности, а может это не ее задача. Ей ведь интересна только owning side.
Можешь попробовать написать расширение для этого.
> И на какой стороне (возвращаясь к этим owning/inverse side) нужно прописывать это повторное линкование (или как его вообще назвать)?
Не знаю, на практике обычно ты будешь делать его только на одной стороне, на ней и прописывай. ну то есть обычно мы делаем $task->addTag(), и почти никогда нам не нужно $tag->setTask (тег ведь не может поменять владельца). Исходя из этого и выбираешь сторону. Вообще, часто обратная связь вообще не нужна. Если у тебя миллион task, то при вызове tag->getTasks у тебя просто памяти не хватит. То есть ты должен подумать, как ты используешь свои модели и исходя из этого делать или не делать ассоциации, а не добавлять все связи подряд просто чтобы они "были".
> Исчерпывающее объяснение. Добавьте два метода (правило именования которых непонятно), и какую-то опцию в конфиг, и будет вам "easier". И потом кто-то удивляется, что люди копируют код не понимая что он делает. Может, потому что никто не объясняет?
Изменение очень логичное. Когда было так:
$task->getTags()->add($tag)
было явное нарушение инкапсуляции - формы вытягивали из модели коллекцию сущностей, делали что-то с ней в обход владельца и клали обратно. Это явно было заточено под доктрину и значит, что ты был обязан испоьзовать ее коллекции или что-то аналогичное.
Представь, что у тебя там есть например код, считающий число тегов или историю их изменения - в таком варианте изменения делались в обход этого кода.
В новом варианте форма просто вызывает add/remove, а твоя модель уже решает, как их внутри и куда сохранять. Логично.
> Может, потому что никто не объясняет?
Я объяснил выше - теперь твоя задача изучить код форм и код PropertyAccess. Начать можно с PropertyAccess, так как он простой, можешь даже сделать небольшой пример кода с ним и вручную попробовать заставить его обновлять список чего-нибудь.
>>1051679
> И как правильно объявить переменную среды?
В Апаче и в настройках пользователя (.bashrc например). Вообще, идея задавать настройки через переменные окружения - описана в 12-factor app: https://12factor.net/ru/config и мне не нравится. Она хорошо работает только на хостингах с read-only файловой системой, и в докере. Представь, что у тебя несколько приложений с разными конфигурациями - замучаешься переменные переключать. Отчасти эту проблему можно решить, выделив на каждое приложение своего пользователя.
> Может в /etc/environment?
Это задает переменную для всех пользователей в системе и всех проектов, что плохо.
> Да, только в документации везде пишут пути типа {% javascripts '@AppBundle/Resources/public/js/*' %}
> а я теперь не могу понять как прописать путь к папке app/Resorces.
Ну это надо смотреть документацию либо код assetic, это тег из него. Ты смотрел? Если в документации нет, открой код assetic и найди там класс вроде TwigExtension и там код этого тега.
> Это composer.json который идет с фреймворком, я туда не заглядывал даже,
Это файл из шаблона (заготовки) приложения. То что идет с фреймворком, и что править нельзя, находится в vendor.
> И на что это влияет кстати? Композер просто откажется устанавливать зависимости, если версия php ниже требуемой?
Документирует зависимость явно, в машинно-читаемом виде. Да, композер это проверяет, изучи мануал: https://getcomposer.org/doc/01-basic-usage.md#platform-packages
> По доктрине, непонятен смысл этих owning и inverse side.
У нас есть 2 объекта и связь между ними, описанная в каждом из них. Представим, что A ссылается на B, но B не ссылается на A. Кому верить? owning side - это сторона, по которой определяется, есть связь или нет. inverse side не проверяется, связь в ней только для удобства использования.
В отношениях 1-1, 1-M owning side - это сторона, где хранится поле-внешний ключ.
> Doctrine will only check the owning side of an association for changes.
> ManyToOne is always the owning side of a bidirectional association.
> The owning side of a OneToOne association is the entity with the table containing the foreign key.
> Что такое bidirectional associations?
Это когда в A объявления связь с B, а в B - обратная связь с A. Обратная связь не обязательна.
> Как правильно настроить эти связи, где писать mappedBy, где inverseBy,
Как описано в мануале, mappedBy - в owning side, inverseBy - в inverse side, если она тебе нужна.
> Как сохраняются связанные сущности? Сохранять (persist) нужно только одну сторону, или обе?
persist ничего не сохраняет. persist передает сущность под управление Доктрины. У Доктрины есть список всех сущностей, которыми она управляет. Это загруженные из БД сущности и добавленные в список через persist(). Когда ты делаешь flush(), Доктрина обходит список, вычисляет изменения и генерирует SQL запросы на обновление БД в соответствии с сизменениями.
Если ты не сделаешь persist, то разумеется, Доктрина твою сущность не увидит и в БД не сохранит. Если ты добавишь в отношение не управляемую доктриной сущность, то есть 2 варианта:
- по умолчанию, это вызовет ошибку
- если у тебя в owning сущности стоит параметр cascade=persist, то Доктрина автоматически сделает persist всем добавленным сущностям при вызове persist для родителя
Типичный пример - ты создал Статью, создал и добавил в нее Комментарий, вызвал persist на статье и flush. Это вызовет ошибку.
Аналогично, кстати, remove() на Статье без вызова remove() для вложенных в нее сущностей (Комментариев) может вызвать аналогичную ошибку, так как ты удаляешь владельца, а сущности, которым он владеет, остаются сиротами.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations
> Enum и доктрина. Доктрина всячески побуждает отказываться от enum в пользу единичек/ноликов.
все прекрасно работает если выставить тип поля string.
> Почему так, неужели enum так плох?
Есть мнение (я с ним согласен только частично): http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/
> Просто если у меня поле типа is_correct('yes', 'no'), отказываются работать чекбоксы, они
ждут true/false.
Тогда надо использовать маппинг boolean, если у тебя true/false.
> Варианты, которые с вопросов в связи M-1, не удаляются.
По какой причине не удаляются? Может быть, нужен cascade remove в сущности?
Тут надо помнить, что формы по идее ничего про базу данных и доктрину не знают. Они работают с моделью. Они только могут вызывать у сущности removeItem(). И задача сущности и доктрины это корректно увидеть и сохранить.
Сделай тест: убери/добавь вручную вариант ответа к вопросу и проверь, сохраняется ли все. Может быть, сначала стоит написать тесты на модель данных (сущности), что любые их изменения корректно сохраняются в БД. Серьезно, попробуй для начала в тесте phpunit создать тест с вопросами и вариантами ответов и сохранить его, отредактировать, удалить итд. Протестируй, что все возможные операции работают.
Вообще, если ты почитаешь документацию и исходный код форм, ты узнаешь, что они взаимодействуют с моделью с помощью специального компонента property-access: https://symfony.com/doc/current/components/property_access.html
В документации этого нет, но в коде мы видим, что там есть специальный метод для обновления коллекции через вызовы add/remove: https://github.com/symfony/property-access/blob/master/PropertyAccessor.php#L568 и константа ACCESS_TYPE_ADDER_AND_REMOVER.
Мне кажется, что перенос данных между моделью и формой (которая представляет собой дерево объектов Form) может делаться тут: https://github.com/symfony/form/blob/master/Extension/Core/DataMapper/PropertyPathMapper.php
Попробуй изучить, в каких условиях вызвыается этот метод и как он работает.
Вообще, изучи компонент Form получше. Он сложный, но если ты возьмешь личточек и нарисуешь связи между объектами, то можно разобраться.
> Кстати, в некоторых связях в этих методах типа add вызывают аналогичный метод у связанного объекта,
Это можно делать, но только на одной стороне, чтобы не получился цикл.
> Только почему доктрина не может это сделать сама?
Как? Не забывай, что доктрина - это маппер, она находится сбоку от твоих сущностей и не может перехватить вызов setTask(). Вообще, ее задача просто синхронизировать данные в БД и в твоих сущностях.
Она может нормализовывать связи только при flush, если этого не делает, не знаю, почему, может быть из соображений производительности, а может это не ее задача. Ей ведь интересна только owning side.
Можешь попробовать написать расширение для этого.
> И на какой стороне (возвращаясь к этим owning/inverse side) нужно прописывать это повторное линкование (или как его вообще назвать)?
Не знаю, на практике обычно ты будешь делать его только на одной стороне, на ней и прописывай. ну то есть обычно мы делаем $task->addTag(), и почти никогда нам не нужно $tag->setTask (тег ведь не может поменять владельца). Исходя из этого и выбираешь сторону. Вообще, часто обратная связь вообще не нужна. Если у тебя миллион task, то при вызове tag->getTasks у тебя просто памяти не хватит. То есть ты должен подумать, как ты используешь свои модели и исходя из этого делать или не делать ассоциации, а не добавлять все связи подряд просто чтобы они "были".
> Исчерпывающее объяснение. Добавьте два метода (правило именования которых непонятно), и какую-то опцию в конфиг, и будет вам "easier". И потом кто-то удивляется, что люди копируют код не понимая что он делает. Может, потому что никто не объясняет?
Изменение очень логичное. Когда было так:
$task->getTags()->add($tag)
было явное нарушение инкапсуляции - формы вытягивали из модели коллекцию сущностей, делали что-то с ней в обход владельца и клали обратно. Это явно было заточено под доктрину и значит, что ты был обязан испоьзовать ее коллекции или что-то аналогичное.
Представь, что у тебя там есть например код, считающий число тегов или историю их изменения - в таком варианте изменения делались в обход этого кода.
В новом варианте форма просто вызывает add/remove, а твоя модель уже решает, как их внутри и куда сохранять. Логично.
> Может, потому что никто не объясняет?
Я объяснил выше - теперь твоя задача изучить код форм и код PropertyAccess. Начать можно с PropertyAccess, так как он простой, можешь даже сделать небольшой пример кода с ним и вручную попробовать заставить его обновлять список чего-нибудь.
>>1051679
> Что за ерунда с прототипами коллекций (т.е. шаблончиков, которые вставляет js)? Зачем их пихают в data-атрибут?
> При просмотре исходного кода выглядит жутко.
Я обычно такие шаблоны кладу в тег <script> с специальным типом, а в новом HTML добавили еще тег <template>, который может пригодиться.
> И как их кастомизировать? Может я в див хочу обернуть инпут, или класс добавить?
Изучай компонент форм, и темизацию (документация малополезна, придется код смотреть):
- https://symfony.com/doc/current/form/form_customization.html
- https://symfony.com/doc/current/form/form_themes.html
> По поводу тайпхинтов, а как прописать, чтобы они работали с null? Добавить по дефолту setTask(Task $task = null)?
Да.
> и полетели ошибки при пустом имени, например если создать объект через new и передать в форму.
Если у тебя в модели поле может иметь значение null - геттер должен иметь возможность вернуть null, смотри мануал http://php.net/manual/ru/migration71.new-features.php
Если поле не может быть null - нужно задать значение по умолчанию.
> AccessDeniedException почему-то дает 500 статус.
Надо смотреть код. Симфони генерирует событие KERNEL_EXCEPTION и в ответ на него обработчик должен сгенерировать объект REsponse с нужным кодом.
> Как работать с assetic? Почему-то в главном шаблоне получается подключать файлы через его теги, а во вложенном пишет
> "An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "_assetic_2e1bfbf_0" as such route does not exist.")."
Боюсь, нужно смотреть исходный код тега в бандле assetic.
> В дев-режиме их типа отдают контроллеры симфони. Вроде так?
Да
> Ассетик почему-то дублирует файлы. Сохраняет оригинальный файл и его копию с другим именем.
Нужно смотреть код, я не знаю почему.
> Как сделать свой валидатор со сложной логикой?
Придется изучать компонент Validator, там есть такая статья: https://symfony.com/doc/current/validation/custom_constraint.html
Обрати внимание, что там надо специально заморочиться, чтобы получить возможность передать зависимости в валидатор (нужно указать тег - и компонент validator запросит список сервисов и найдет в нем сервис-валидатор из контейнера).
Также, нужно делать валидатор не к форме, а к модели, ведь модель можно создать не только через форму.
> К тому же важно чтобы сообщения моего валидатора транслировались именно в форму, чтобы под определенным
> вопросом вывелась ошибка о том что у него должно быть не меньше 2 вариантов ответа
Нужно возвращать ошибку валидации стандартным способом через $this->context->buildViolation()
> Там еще какой-то ад с формами ответа, когда приходит несколько ответов (при чекбоксах), смотри
AttemptService::populateAndPersistAnswers
Может быть там стоит создать 2 разных типа форм, для формы с единичным и множественным ответаим? При этом ты можешь использовать один Builder для них, просто добавить в нем параметр, задающий множественность ответов. И в зависимости от этого параметра в buildForm() что-то менять.
> Кстати по поводу автоматизированного тестирования, этот проект наглядно показывает зачем они нужны.
Если ты поддерживаешь 2 разных БД, то тесты точно нужны, так как иначе ты замучаешься вручную каждую фичу проверять по 2 раза. А если не проверять - будет ломаться.
> Как в постгрес создать бд для тестов в памяти, как в mysql?
Судя по гуглу, никак, единственный вариант - создать файловую систему tmpfs (это как RAM диск), и создать базу на ней. Постгрес конечно не знает, что это память, потому работать будет не так идеально, но быстрее чем с диском. Насчет mysql - будь осторожен, таблицы типа MEMORY могут иметь отличия от InnoDB, например, есть ли там внешние ключи и транзакции?
in-memory режим есть у sqlite кстати.
> Ну или хотя бы просто создать бд из консоли.
есть запрос https://www.postgresql.org/docs/9.1/static/sql-createdatabase.html который можно выполнить в консоли и есть команда https://www.postgresql.org/docs/9.1/static/app-createdb.html
Вообще, настройку тестового окружения, возможно, стоит сделать каким-то скриптом. Если ты будешь использовать CI сервер, ты ведь руками его настраивать не сможешь. Или если захочешь запускать тесты в докере для изоляции. Вариантов много - начиная от shell скрипта и заканчивая системами вроде ansible.
> Если без шуток, я понимаю что проблема в этой "оригинальной" системе ролей и прав постгреса, он просто
не разрешает создать базу нерутовому пользователю.
Наверняка ты просто криво ее настроил. Вот я нагуглил например, это не то?
https://dba.stackexchange.com/questions/33285/granting-a-user-account-permission-to-create-databases-in-postgresql/33291#33291
> The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead: 3x
Может где-то что-то старое установлено? надо будет composer.lock изучить. Я бы погуглил текст ошибки, а также посмотрел список issues в симфони на гитхабе, не ты один это видишь.
> Я вроде никаких ConsoleEvents не трогал, там приблизительно такой код:
Application наверно и использует эти events.
> Нужно проверять именно на 200 статус? Часто ли на практике встречаются другие 2xx статусы?
в REST API бывает 201 Created, но ты должен проверять тот статус, который ожидаешь, то есть 200.
> Просто в других примерах чаще используют response->isSuccessful().
В приципе тоже можно. Вообще, есть мнение, что в UI тестах лучше избегать того, что пользователь не видит - например, проверки HTTP заголовков, а проверять то, что видит ползователь. Но конечно код ошибки слишком удобная вещь, чтобы от него отказываться.
>>1051679
> Что за ерунда с прототипами коллекций (т.е. шаблончиков, которые вставляет js)? Зачем их пихают в data-атрибут?
> При просмотре исходного кода выглядит жутко.
Я обычно такие шаблоны кладу в тег <script> с специальным типом, а в новом HTML добавили еще тег <template>, который может пригодиться.
> И как их кастомизировать? Может я в див хочу обернуть инпут, или класс добавить?
Изучай компонент форм, и темизацию (документация малополезна, придется код смотреть):
- https://symfony.com/doc/current/form/form_customization.html
- https://symfony.com/doc/current/form/form_themes.html
> По поводу тайпхинтов, а как прописать, чтобы они работали с null? Добавить по дефолту setTask(Task $task = null)?
Да.
> и полетели ошибки при пустом имени, например если создать объект через new и передать в форму.
Если у тебя в модели поле может иметь значение null - геттер должен иметь возможность вернуть null, смотри мануал http://php.net/manual/ru/migration71.new-features.php
Если поле не может быть null - нужно задать значение по умолчанию.
> AccessDeniedException почему-то дает 500 статус.
Надо смотреть код. Симфони генерирует событие KERNEL_EXCEPTION и в ответ на него обработчик должен сгенерировать объект REsponse с нужным кодом.
> Как работать с assetic? Почему-то в главном шаблоне получается подключать файлы через его теги, а во вложенном пишет
> "An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "_assetic_2e1bfbf_0" as such route does not exist.")."
Боюсь, нужно смотреть исходный код тега в бандле assetic.
> В дев-режиме их типа отдают контроллеры симфони. Вроде так?
Да
> Ассетик почему-то дублирует файлы. Сохраняет оригинальный файл и его копию с другим именем.
Нужно смотреть код, я не знаю почему.
> Как сделать свой валидатор со сложной логикой?
Придется изучать компонент Validator, там есть такая статья: https://symfony.com/doc/current/validation/custom_constraint.html
Обрати внимание, что там надо специально заморочиться, чтобы получить возможность передать зависимости в валидатор (нужно указать тег - и компонент validator запросит список сервисов и найдет в нем сервис-валидатор из контейнера).
Также, нужно делать валидатор не к форме, а к модели, ведь модель можно создать не только через форму.
> К тому же важно чтобы сообщения моего валидатора транслировались именно в форму, чтобы под определенным
> вопросом вывелась ошибка о том что у него должно быть не меньше 2 вариантов ответа
Нужно возвращать ошибку валидации стандартным способом через $this->context->buildViolation()
> Там еще какой-то ад с формами ответа, когда приходит несколько ответов (при чекбоксах), смотри
AttemptService::populateAndPersistAnswers
Может быть там стоит создать 2 разных типа форм, для формы с единичным и множественным ответаим? При этом ты можешь использовать один Builder для них, просто добавить в нем параметр, задающий множественность ответов. И в зависимости от этого параметра в buildForm() что-то менять.
> Кстати по поводу автоматизированного тестирования, этот проект наглядно показывает зачем они нужны.
Если ты поддерживаешь 2 разных БД, то тесты точно нужны, так как иначе ты замучаешься вручную каждую фичу проверять по 2 раза. А если не проверять - будет ломаться.
> Как в постгрес создать бд для тестов в памяти, как в mysql?
Судя по гуглу, никак, единственный вариант - создать файловую систему tmpfs (это как RAM диск), и создать базу на ней. Постгрес конечно не знает, что это память, потому работать будет не так идеально, но быстрее чем с диском. Насчет mysql - будь осторожен, таблицы типа MEMORY могут иметь отличия от InnoDB, например, есть ли там внешние ключи и транзакции?
in-memory режим есть у sqlite кстати.
> Ну или хотя бы просто создать бд из консоли.
есть запрос https://www.postgresql.org/docs/9.1/static/sql-createdatabase.html который можно выполнить в консоли и есть команда https://www.postgresql.org/docs/9.1/static/app-createdb.html
Вообще, настройку тестового окружения, возможно, стоит сделать каким-то скриптом. Если ты будешь использовать CI сервер, ты ведь руками его настраивать не сможешь. Или если захочешь запускать тесты в докере для изоляции. Вариантов много - начиная от shell скрипта и заканчивая системами вроде ansible.
> Если без шуток, я понимаю что проблема в этой "оригинальной" системе ролей и прав постгреса, он просто
не разрешает создать базу нерутовому пользователю.
Наверняка ты просто криво ее настроил. Вот я нагуглил например, это не то?
https://dba.stackexchange.com/questions/33285/granting-a-user-account-permission-to-create-databases-in-postgresql/33291#33291
> The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead: 3x
Может где-то что-то старое установлено? надо будет composer.lock изучить. Я бы погуглил текст ошибки, а также посмотрел список issues в симфони на гитхабе, не ты один это видишь.
> Я вроде никаких ConsoleEvents не трогал, там приблизительно такой код:
Application наверно и использует эти events.
> Нужно проверять именно на 200 статус? Часто ли на практике встречаются другие 2xx статусы?
в REST API бывает 201 Created, но ты должен проверять тот статус, который ожидаешь, то есть 200.
> Просто в других примерах чаще используют response->isSuccessful().
В приципе тоже можно. Вообще, есть мнение, что в UI тестах лучше избегать того, что пользователь не видит - например, проверки HTTP заголовков, а проверять то, что видит ползователь. Но конечно код ошибки слишком удобная вещь, чтобы от него отказываться.
>>1051679
> По второму ассерту: логично ли проверять контент страницы, в данном случае содержимое h1?
> А вдруг верстальщик его поменяет, или заменит h1 на див?
Это извечная проблема. Я уже писал, что тестирование - отдельная задача, которая часто сравнима по сложности с разработкой. Нужно проявлять изобретательность и смекалку и придумывать способы покрыть максимум фич минимумом кода за минимум времени, и чтобы это все было максимально устойчиво к правкам и рефакторингам. Неудивительно, что во многих компаниях тестов либо не делают, либо делают вручную.
Тесты надо делать максимально устойчивыми, если они будут ломаться при каждой правке верстки, ими никто не захочет пользоваться.
Проверять тексты на человеческом языке конечно неудобно - легко опечататься, они могут меняться, переводиться на другие языки.
Один из вариантов, которые мне известны - добавлять или использовать микроразметку, которая указывает на нужные элементы и сохраняется при правках верстки - например, классы с префиксом test- (которые должны использоваться только для тестов, не использоваться в CSS/JS), data-атрибуты. При желании такую разметку можно вырезать на продакшене. Также, есть стандартные виды микроразметки для некоторых сущностей, которые тебя скорее всего попросят сделать ради SEO оптимизации, можно использовать их:
- https://yandex.ru/support/webmaster/schema-org/what-is-schema-org.html
- https://yandex.ru/support/webmaster/microformats/what-is-microformat.html
Если ты используешь БЭМ, то классы элементов в нем можно считать относительно стабильными. Также, имена полей в формах обычно не меняются.
Есть и другие подходы. В этом плане интересно читать блог яндекса (и некоторых других компаний) на хабре - вот тут например они описывают тестирование через скриншоты (ад в плане поддержки конечно):
- https://habrahabr.ru/company/yandex/blog/200968/
- https://habrahabr.ru/company/2gis/blog/246831/
Эти блоги очень интересные, так как про GUI тестирование информации в сети в принципе мало.
Мне в голову пришла мысль, что вместо скриншотов можно делать HTML-дампы или дампы дерева DOM (после изменения скриптами). Профит в том, что на задачу проверки/создания дампов можно посадить неквалифицированного (=дешево!) тестировщика, а также, можно как-то автоматизированно убирать мелкие изменения (вроде изменений в тегах) или скрывать невидимые узлы DOM перед сравнением. Но этот вариант наверно для больших проектов, у которых есть отдельная команда тестировщиков.
Еще у меня есть такая идея - преобразовывать DOM в формат, в котором страница представлена как набор элементов, у которых есть текст и координаты. И проверять, что под заголовком с таким-то текстом есть абзац с таким-то текстом.
Еще, я подумал, можно использовать уникальные слова вместо поиска в дереве DOM. То есть чтобы протестировать страницу просмотра поста в блоге, достаточно создать пост с уникальными ключевыми словами и проверить наличие их на ней. Этот вариант наверно максимально устойчив к изменениям верстки. Какой я сообразительный
Там есть еще варианты вроде Selenium IDE, в которой ты кликаешь на элеимент, а она создает Xpath для него, но код на выходе она генерирует адский и неподдерживаемый. Это для тех, кто хочет получить низкокачественные тесты без использования программистов.
Есть еще паттерн Page Object - это объект, который инкапсулирует поиск элементов на странице. При смене верстки ты только обновляешь Page Object (внезапно, этот паттерн тоже описан у Фаулера, какой он умный: https://martinfowler.com/bliki/PageObject.html ). Для каждой страницы сайта - свой Page Object:
- https://habrahabr.ru/company/yandex/blog/158787/
Page Object мне напоминают MVC, когда мы отделяем вывод данных в View, только тут мы выделяем ввод данных со страницы, а не вывод.
Я также попробовал гуглить "habr gui тестирование", "ui tests stability".
То есть я могу дать список возможных подходов, а ты должен подумать, какой вариант будет оптимальным.
Вообще, веб еще относительно легко тестировать - есть HTML, DOM, есть эмуляторы и headless browsers, есть стандарты, а ты попробуй-ка протестируй десктопное или мобильное нативное приложение!
Если хороший сценарий тестирования придумать трудно, написание тестов трудоемко, можно уменьшить объем и упростить GUI тесты, компенсируя это более тщательными тестами сервисов (из соображений отношения затрат/выгоды). Вот статья Фаулера с пирамидой тестов: https://martinfowler.com/bliki/TestPyramid.html
Вот статья гугла: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html
К примеру, если ты тестируешь валидацию, может быть дешевле тестировать различные варианты ошибок на сервисе валидатора программно, а в GUI тестировать только 2 простых сценария - форма заполнена верно и неверно (чтобы проверить вывод ошибок и обработку отправки формы). Это тоже важно, не тестировать одну и ту же фичу несколькими тестами на разных уровнях.
Но конечно GUI тесты нужны, особенно тесты различных flow - зайти на эту страницу, нажать кнопку, перейти на другую страницу, увидеть текст.
Насчет тестов, ты не хочешь подключить CI сервис, чтобы он автоматически прогонял тесты при каждом коммите? Вот есть список сервисов, не все бесплатные конечно:
- https://github.com/marketplace/category/continuous-integration
- https://github.com/ligurio/Continuous-Integration-services/blob/master/continuous-integration-services-list.md
Я у себя подключал Travis CI тут, правда пришлось немного помучаться, чтобы уведомления и расылки по email отключить: https://github.com/codedokode/task-checker/blob/master/.travis.yml
Разумеется, вместо коммерческих сервисов ты можешь установить Jenkins на своем сервере и получить полностью подконтрольное тебе окружение. Идея CI в том, что тесты прогоняются автоматически, и при ошибке команда получает уведомление в чат.
> В составе symfony standard edition идет в комплекте некий simple-phpunit.
> Что это за зверь и в чем его особенности?
Тут описаны отличия https://symfony.com/doc/current/components/phpunit_bridge.html вроде бы он идет как отдельный скрипт, не конфликтующий и не мешающий использованию phpunit. Он просто интегрирован с Симфони чуть плотнее. Можешь его использовать, если хочешь.
> Ну например я phpstorm под него не могу настроить, чтобы тесты запускались
из ide, пишет Class 'PHPUnit_TextUI_ResultPrinter' not found.
Это у тебя что-то криво настроено, проверь настройки своей IDE, что она запускает именно нужный скрипт, а не команду phpunit.
> Что делать с приватными методами? Как их тестить в смысле. И нужно ли тестить. Приватные методы используются внутри тестируемых, просто чтобы избежать дублирования или нагромождения кода.
Есть 2 подхода, "метод черного ящика" - когда ты не знаешь, что внутри класса и не видишь приватные методы, и "белого ящика" - когда ты опираешься на знание внутреннего устройства класса. Мне кажется,что лучше использовать "черный ящик", он более устойчив к изменениям, соответтвенно тестировать только публичные интерфейсы и только на соответствие ожидаемому от них поведению. Тесты должны делать примерно то же, что и код, который использует этот класс.
Если что-то непонятно - спрашивай.
Код пока не смотрел, позже посмотрю.
>>1051679
> По второму ассерту: логично ли проверять контент страницы, в данном случае содержимое h1?
> А вдруг верстальщик его поменяет, или заменит h1 на див?
Это извечная проблема. Я уже писал, что тестирование - отдельная задача, которая часто сравнима по сложности с разработкой. Нужно проявлять изобретательность и смекалку и придумывать способы покрыть максимум фич минимумом кода за минимум времени, и чтобы это все было максимально устойчиво к правкам и рефакторингам. Неудивительно, что во многих компаниях тестов либо не делают, либо делают вручную.
Тесты надо делать максимально устойчивыми, если они будут ломаться при каждой правке верстки, ими никто не захочет пользоваться.
Проверять тексты на человеческом языке конечно неудобно - легко опечататься, они могут меняться, переводиться на другие языки.
Один из вариантов, которые мне известны - добавлять или использовать микроразметку, которая указывает на нужные элементы и сохраняется при правках верстки - например, классы с префиксом test- (которые должны использоваться только для тестов, не использоваться в CSS/JS), data-атрибуты. При желании такую разметку можно вырезать на продакшене. Также, есть стандартные виды микроразметки для некоторых сущностей, которые тебя скорее всего попросят сделать ради SEO оптимизации, можно использовать их:
- https://yandex.ru/support/webmaster/schema-org/what-is-schema-org.html
- https://yandex.ru/support/webmaster/microformats/what-is-microformat.html
Если ты используешь БЭМ, то классы элементов в нем можно считать относительно стабильными. Также, имена полей в формах обычно не меняются.
Есть и другие подходы. В этом плане интересно читать блог яндекса (и некоторых других компаний) на хабре - вот тут например они описывают тестирование через скриншоты (ад в плане поддержки конечно):
- https://habrahabr.ru/company/yandex/blog/200968/
- https://habrahabr.ru/company/2gis/blog/246831/
Эти блоги очень интересные, так как про GUI тестирование информации в сети в принципе мало.
Мне в голову пришла мысль, что вместо скриншотов можно делать HTML-дампы или дампы дерева DOM (после изменения скриптами). Профит в том, что на задачу проверки/создания дампов можно посадить неквалифицированного (=дешево!) тестировщика, а также, можно как-то автоматизированно убирать мелкие изменения (вроде изменений в тегах) или скрывать невидимые узлы DOM перед сравнением. Но этот вариант наверно для больших проектов, у которых есть отдельная команда тестировщиков.
Еще у меня есть такая идея - преобразовывать DOM в формат, в котором страница представлена как набор элементов, у которых есть текст и координаты. И проверять, что под заголовком с таким-то текстом есть абзац с таким-то текстом.
Еще, я подумал, можно использовать уникальные слова вместо поиска в дереве DOM. То есть чтобы протестировать страницу просмотра поста в блоге, достаточно создать пост с уникальными ключевыми словами и проверить наличие их на ней. Этот вариант наверно максимально устойчив к изменениям верстки. Какой я сообразительный
Там есть еще варианты вроде Selenium IDE, в которой ты кликаешь на элеимент, а она создает Xpath для него, но код на выходе она генерирует адский и неподдерживаемый. Это для тех, кто хочет получить низкокачественные тесты без использования программистов.
Есть еще паттерн Page Object - это объект, который инкапсулирует поиск элементов на странице. При смене верстки ты только обновляешь Page Object (внезапно, этот паттерн тоже описан у Фаулера, какой он умный: https://martinfowler.com/bliki/PageObject.html ). Для каждой страницы сайта - свой Page Object:
- https://habrahabr.ru/company/yandex/blog/158787/
Page Object мне напоминают MVC, когда мы отделяем вывод данных в View, только тут мы выделяем ввод данных со страницы, а не вывод.
Я также попробовал гуглить "habr gui тестирование", "ui tests stability".
То есть я могу дать список возможных подходов, а ты должен подумать, какой вариант будет оптимальным.
Вообще, веб еще относительно легко тестировать - есть HTML, DOM, есть эмуляторы и headless browsers, есть стандарты, а ты попробуй-ка протестируй десктопное или мобильное нативное приложение!
Если хороший сценарий тестирования придумать трудно, написание тестов трудоемко, можно уменьшить объем и упростить GUI тесты, компенсируя это более тщательными тестами сервисов (из соображений отношения затрат/выгоды). Вот статья Фаулера с пирамидой тестов: https://martinfowler.com/bliki/TestPyramid.html
Вот статья гугла: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html
К примеру, если ты тестируешь валидацию, может быть дешевле тестировать различные варианты ошибок на сервисе валидатора программно, а в GUI тестировать только 2 простых сценария - форма заполнена верно и неверно (чтобы проверить вывод ошибок и обработку отправки формы). Это тоже важно, не тестировать одну и ту же фичу несколькими тестами на разных уровнях.
Но конечно GUI тесты нужны, особенно тесты различных flow - зайти на эту страницу, нажать кнопку, перейти на другую страницу, увидеть текст.
Насчет тестов, ты не хочешь подключить CI сервис, чтобы он автоматически прогонял тесты при каждом коммите? Вот есть список сервисов, не все бесплатные конечно:
- https://github.com/marketplace/category/continuous-integration
- https://github.com/ligurio/Continuous-Integration-services/blob/master/continuous-integration-services-list.md
Я у себя подключал Travis CI тут, правда пришлось немного помучаться, чтобы уведомления и расылки по email отключить: https://github.com/codedokode/task-checker/blob/master/.travis.yml
Разумеется, вместо коммерческих сервисов ты можешь установить Jenkins на своем сервере и получить полностью подконтрольное тебе окружение. Идея CI в том, что тесты прогоняются автоматически, и при ошибке команда получает уведомление в чат.
> В составе symfony standard edition идет в комплекте некий simple-phpunit.
> Что это за зверь и в чем его особенности?
Тут описаны отличия https://symfony.com/doc/current/components/phpunit_bridge.html вроде бы он идет как отдельный скрипт, не конфликтующий и не мешающий использованию phpunit. Он просто интегрирован с Симфони чуть плотнее. Можешь его использовать, если хочешь.
> Ну например я phpstorm под него не могу настроить, чтобы тесты запускались
из ide, пишет Class 'PHPUnit_TextUI_ResultPrinter' not found.
Это у тебя что-то криво настроено, проверь настройки своей IDE, что она запускает именно нужный скрипт, а не команду phpunit.
> Что делать с приватными методами? Как их тестить в смысле. И нужно ли тестить. Приватные методы используются внутри тестируемых, просто чтобы избежать дублирования или нагромождения кода.
Есть 2 подхода, "метод черного ящика" - когда ты не знаешь, что внутри класса и не видишь приватные методы, и "белого ящика" - когда ты опираешься на знание внутреннего устройства класса. Мне кажется,что лучше использовать "черный ящик", он более устойчив к изменениям, соответтвенно тестировать только публичные интерфейсы и только на соответствие ожидаемому от них поведению. Тесты должны делать примерно то же, что и код, который использует этот класс.
Если что-то непонятно - спрашивай.
Код пока не смотрел, позже посмотрю.
> Что такое настройка by_reference=false у коллекции? Что она делает?
Нужно смотреть код класса CollectionType в symfony/form.
Там есть описание в документации, но оно не очень понятное http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
Лучше изучить код.
> public function addDepartment($department)
Тут нужен тайп-хинт
> Получилось много методов вида getFoo и setFoo, может имеет смысл заменить на один метод Get и Set с параметрами?
Нет, не имеет, это только сохздаст путаницу. Есть в общем-то 2 варианта, как делать:
- публично доступные свойства
- приватные свойства + сеттеры/геттеры
Плюс первого подхода в краткости, плюс второго подхода в возможности контролировать доступ к свойству. Например, ты можешь запретить задавать определенные значения или вообще запретить менять свойство.
> array_splice($this->employees, array_search($employee, $this->employees), 1);
Тут нет проверки, что сотрудник найден. array_search может вернуть false, и ты просто удалишь другого сотрудника.
> $employee->getiSBoss()
Было бы красивее конечно isBoss(). Но тогда придется свойство назвать boss.
Если объекта нет, обычно возвращают null, который традиционно обозначает отсутствие объекта.
> class Employee
> //Сотрудник владеет полной информацией о себе
Комментарий нужно писать надо словом class, а не под
> $this->isBoss = 1;
Вот тут как раз стоило использовать true/false.
> public function getCoffee()
> public function setCoffee($coffee)
Здесь не очень удачно названы функции, так как они задают и возвращают разные значения. Одна - базовое потребление кофе, другая - итоговое.
Профессии следовало бы обозначить константами (Employee::ROLE_MANAGER), если ты хочешь для них делать проверки в коде (например, проверка, является ли сотрудник менеджером). Так как "Менеджер" - это лишь представление профессии для вывода (оно нужно только при выводе данных и не нужно при расчетах). А у тебя есть сравнения вроде if ($profession == "Аналитик"). Использовать тут строки плохо, так как там легко опечататься или что-то перепутать, плюс неясно, какие вообще варианты профессий есть. Плюс при смене языка или написания придется лазать менять названия по всей программе, а не в одном месте. Константы с цифрами или латинскими идентификаторами лишены этих недостатков. Заодно, IDE умеет делать автодополнение при наборе константы.
$staffList можно было оформить в виде маленького класса, чтобы была возможность передавать его куда-то и где-то повторно использовать.
Антикризисные функции надо сделать в классе (или классах). У нас же задача на ООП.
В функциях optimize1, optimize2 есть много похожего кода. Часть этого кода можно вынести в отдельные методы, а не копипастить. Ну например, поиск работников по критериям - чем не метод? Или замена босса - почему не сделать метод для этого?
При этом важно, чтобы методы в Сотруднике, Компании или Департаменте относились к этим сущностям и могли пригодиться где-то еще, а не занимающиеся только антикризисными мерами. Метод заменитьБосса - ок, метод уволитьNпроцентовИнженеров - не ок.
> $selectedEmployees[$i]->setRank($selectedEmployees[$i]->getRank() + 1);
Слишком длинно и тяжело читается, нужно что-то вынести в переменную (например, employee).
Вместо очистки компании лучше было бы сделать метод для ее клонирования. Изучи оператор clone.
> public function addDepartment($department)
Тут нужен тайп-хинт
> Получилось много методов вида getFoo и setFoo, может имеет смысл заменить на один метод Get и Set с параметрами?
Нет, не имеет, это только сохздаст путаницу. Есть в общем-то 2 варианта, как делать:
- публично доступные свойства
- приватные свойства + сеттеры/геттеры
Плюс первого подхода в краткости, плюс второго подхода в возможности контролировать доступ к свойству. Например, ты можешь запретить задавать определенные значения или вообще запретить менять свойство.
> array_splice($this->employees, array_search($employee, $this->employees), 1);
Тут нет проверки, что сотрудник найден. array_search может вернуть false, и ты просто удалишь другого сотрудника.
> $employee->getiSBoss()
Было бы красивее конечно isBoss(). Но тогда придется свойство назвать boss.
Если объекта нет, обычно возвращают null, который традиционно обозначает отсутствие объекта.
> class Employee
> //Сотрудник владеет полной информацией о себе
Комментарий нужно писать надо словом class, а не под
> $this->isBoss = 1;
Вот тут как раз стоило использовать true/false.
> public function getCoffee()
> public function setCoffee($coffee)
Здесь не очень удачно названы функции, так как они задают и возвращают разные значения. Одна - базовое потребление кофе, другая - итоговое.
Профессии следовало бы обозначить константами (Employee::ROLE_MANAGER), если ты хочешь для них делать проверки в коде (например, проверка, является ли сотрудник менеджером). Так как "Менеджер" - это лишь представление профессии для вывода (оно нужно только при выводе данных и не нужно при расчетах). А у тебя есть сравнения вроде if ($profession == "Аналитик"). Использовать тут строки плохо, так как там легко опечататься или что-то перепутать, плюс неясно, какие вообще варианты профессий есть. Плюс при смене языка или написания придется лазать менять названия по всей программе, а не в одном месте. Константы с цифрами или латинскими идентификаторами лишены этих недостатков. Заодно, IDE умеет делать автодополнение при наборе константы.
$staffList можно было оформить в виде маленького класса, чтобы была возможность передавать его куда-то и где-то повторно использовать.
Антикризисные функции надо сделать в классе (или классах). У нас же задача на ООП.
В функциях optimize1, optimize2 есть много похожего кода. Часть этого кода можно вынести в отдельные методы, а не копипастить. Ну например, поиск работников по критериям - чем не метод? Или замена босса - почему не сделать метод для этого?
При этом важно, чтобы методы в Сотруднике, Компании или Департаменте относились к этим сущностям и могли пригодиться где-то еще, а не занимающиеся только антикризисными мерами. Метод заменитьБосса - ок, метод уволитьNпроцентовИнженеров - не ок.
> $selectedEmployees[$i]->setRank($selectedEmployees[$i]->getRank() + 1);
Слишком длинно и тяжело читается, нужно что-то вынести в переменную (например, employee).
Вместо очистки компании лучше было бы сделать метод для ее клонирования. Изучи оператор clone.
Да, PHP написан на Си, исходники открыты. Вот пример функции сортировки: https://github.com/php/php-src/blob/master/ext/standard/array.c#L922
>>1048584
Тебе надо разобраться с библиотекой, я не видел код, но предполагаю, что long - это численное представление IP-адреса. Ну то есть число, которое получается, если взять 4 байта из IP адреса и представить как число.
Сторонние библиотеки лучше ставить через композер, на худой конец вручную в отдельную папку вне codeigniter. Подключать через автозагрузку или реквайрами.
Ну и справедливости ради, для преобразования IP сложный ООП не нужен, хватит статической функции.
>>1048364
Потому что там несколько категорий и несколько раз выполняется код.
>>1048278
В чем выгода? Для цен иногда используют числа с фиксированной запятой (DECIMAL в БД).
>>1046988
> почему функция называется persistence? Что это означает?
У тебя же есть гугл под рукой, анон.
>>1046810
Обычно используют Sublime, Atom, Brackets, Notepad++, Netbeans, Eclipse PDT, PhpStorm.
Да, PHP написан на Си, исходники открыты. Вот пример функции сортировки: https://github.com/php/php-src/blob/master/ext/standard/array.c#L922
>>1048584
Тебе надо разобраться с библиотекой, я не видел код, но предполагаю, что long - это численное представление IP-адреса. Ну то есть число, которое получается, если взять 4 байта из IP адреса и представить как число.
Сторонние библиотеки лучше ставить через композер, на худой конец вручную в отдельную папку вне codeigniter. Подключать через автозагрузку или реквайрами.
Ну и справедливости ради, для преобразования IP сложный ООП не нужен, хватит статической функции.
>>1048364
Потому что там несколько категорий и несколько раз выполняется код.
>>1048278
В чем выгода? Для цен иногда используют числа с фиксированной запятой (DECIMAL в БД).
>>1046988
> почему функция называется persistence? Что это означает?
У тебя же есть гугл под рукой, анон.
>>1046810
Обычно используют Sublime, Atom, Brackets, Notepad++, Netbeans, Eclipse PDT, PhpStorm.
>>1045048
Надо сохранять эти данные на клиенте и передавать вместе со следующим запросом.
Это копия, сохраненная 7 сентября 2017 года.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.