Вы видите копию треда, сохраненную 12 февраля 2017 года.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.
Это не чат! Также, перед Новым годом ОП довольно занят, а на праздниках будет появляться нечасто.
Это тред для начинающих. Не написал за свою жизнь ни одной программы и имеешь тройку по математике? Ты наш человек.
Устанавливать пока что ничего не требуется, разве что редактор кода вроде Sublime Text 3, Notepad++, Netbeans PHP или PhpStorm (с ним будет удобнее).
Предыдущий тред был тут: >>880700 (OP)
Мейлач лежит? Есть запасной тред: http://dobrochan.org/s/res/23225.xhtml#i46467
Что самое главное для программиста? Умение аккуратно оформлять код (читай второй пост, прежде чем писать код).
Правила: ведем себя воспитанно, помогаем новичкам, постим ссылки на решения задачек, ОП их проверяет и дает советы и замечания. ОП заходит редко, где-то раз в 2-3 дня, у него мало времени, не жди его, решай задачки дальше. ОП отвечает на все вопросы по его задачкам и учебнику, а вот насчет каких-то других вещей - только если останется время. Но в треде немало анонимных экспертов разного уровня, так что вряд ли вопрос останется без ответа.
У нас есть уроки по основам PHP, они собраны и выложены по адресу http://archive-ipq-co.narod.ru/ Это учебник для изучающих с нуля, то есть если ты вообще ничего не знаешь, то надо начать с него. Он простой и понятный (по крайней мере в начале). Там есть задачи, их надо решать обязательно (чтобы стать программистом, надо писать код — иначе никак). Пости ссылки на решения в тред, мы их проверим, напишем замечания и дадим советы по улучшению.
Если не знаешь как решать, запости код, напиши в каком месте остановился и попроси подсказку.
Ты прошел весь учебник? Молодец, но это были лишь основы языка PHP, этого недостаточно. Вот что в идеале надо изучить еще: ООП, как работает веб-сервер, HTML/CSS, SQL, PDO, работа с таблицами в БД, работа с формами, MVC, git, composer, JS, фреймворки, автоматизированное тестирование.
Надо переходить к более серьезным задачкам, которые научат тебя всему этому.
- для начала прочти урок https://github.com/codedokode/pasta/blob/master/soft/web-server.md
- установи Апач + PHP (советы выше и ниже) и читай туториал http://php.net/manual/ru/tutorial.php
- Учи HTML/CSS и SQL, PDO, хотя бы основы
- Далее простая, но полезная задача сделать список студентов, в ней много полезных советов: https://github.com/codedokode/pasta/blob/master/student-list.md
- Более сложная задача сделать файлообменник на микрофреймворке Slim: https://gist.github.com/codedokode/9424217
- Еще более сложная и долгая задача на Yii/Symfony: https://gist.github.com/codedokode/8733007
- После нее можно изучать автоматизированное тестирование https://gist.github.com/codedokode/a455bde7d0748c0a351a
- Если ты все решил, переходи к Symfony 2/Doctrine 2
- Почитать про паттерны http://designpatternsphp.readthedocs.org/ru/latest/README.html (если ты не изучил ни одного фреймворка, то это будет рановато), тут с примерами кода http://designpatternsphp.readthedocs.org/ru/latest/README.html . Имей в виду что без примеров использования их учить бесполезно - не поймешь, хочешь увидеть примеры использования паттернов - ковыряй исходники Симфони, например Symfony Forms. Не заучивай паттерны - смотри код и думай, зачем тут они использованы.
Чтобы делать эти задания, тебе надо установить Апач + PHP (можно заодно сразу и MySQL) на компьютер. Вот полезные инструкции:
https://github.com/codedokode/pasta/blob/master/soft/php-install.md
https://github.com/codedokode/pasta/blob/master/soft/apache-install.md
Может тебе понадобится пользоваться командной строкой, вот гайд https://github.com/codedokode/pasta/blob/master/soft/cli.md
Решения задач лучше показать мне, особенно на ООП,так как сам ты вряд ли увидишь все ошибки. Пости свой код на гитхаб и вкидывай ссылку в тред по мере решения. Я прокомментирую и укажу на ошибки.
Также, у нас есть задачи которые позволят тебе изучить или подтянуть до нормального уровня знания JS/HTML/CSS/SQL. Решай их параллельно с задачами выше.
- HTML/CSS: https://github.com/codedokode/pasta/blob/master/html/html.md
- JS: https://gist.github.com/codedokode/ce30e7a036f18f416ae0
- SPA (сложно): https://github.com/codedokode/pasta/blob/master/js/spa.md
- Проверялка решений на JS: http://dkab.github.io/jasmine-tests/
- MySQL: https://github.com/codedokode/pasta/blob/master/db/databases.md
Что почитать
- Мануал по PHP — http://www.php.net/manual/ru/langref.php
- Сайт phptherightway (перевод на русский: http://getjump.me/ru-php-the-right-way/ )
- По PHP: Профессиональное программирование на PHP Джордж Шлосснейгл
- По PHP: Мэтт Зандстра — PHP: Объекты, шаблоны, методики программирования
- JS: learn.javascript.ru
- Про Git: https://git-scm.com/book/ru/v1
Оформляй код аккуратно!!! — например пропусти через phpformatter.com . Также, если ты пользуешься IDE вроде PhpStorm, Netbeans, Eclipse, то в них эта опция встроена, подробнее: https://gist.github.com/codedokode/8759492
У ОПа нет аккаунтов и групп вконтакте, в фейсбуке, в твиттере, все "пхп-треды" там поддельные.
Платиновые вопросы
- Почему PHP? Потому что фейсбук и википедия на нем написаны, и вакансий море, и учить легко.
- Сайт опять упал!!!!! — Не паникуй, а открой http://rghost.ru/6bfCY9lfl и получи личную немного устаревшую оффлайновую копию сайта (можно читать хоть на андроиде без интернета)
- Что надо знать чтобы найти работу - разработчику: PHP, SQL, HTML/CSS, JS, ООП, Git, композер, MVC, фреймворк. Верстальщику - HTML/CSS, JS, jQuery
- Можно подробнее про поиск работы, собеседования - нет, ОП писать не будет, но может кто из анонов захочет рассказать. Поищите тред перезвонивших, а также раздел /wrk/.
- Сколько времени надо изучать все это? - все зависит от тебя, но не меньше 6-8 месяцев
- Посоветуйте редактор кода - Sublime Text 3, Notepad++, PhpStorm
- Нужен ли ООП, фреймворки, MVC, git, composer? — Да, однозначно. Посмотри любую вакансию.
- Что самое главное для программиста? Умение аккуратно оформлять код.
- ОП, сделай за меня мою работу или домашнее задание? — Это конечно, хорошая идея, но нет.
- Подскажи сайты для поиска работы, я не умею гуглить? — hh.ru, geekjob.ru, moikrug.ru (склеен с brainstorage.me), fl.ru, upwork.com (бывший одеск). Имей в виду, что кроме фриланса есть еще постоянная удаленная работа (remote job) когда тебе не надо тратить время на поиск заказов и переговоры с неадекватными заказчиками.
Если тебе лень выравнивать код руками, закачай его на http://beta.phpformatter.com/ и нажми «format». Робот исправит выравнивание и отступы в мгновение ока (да, прогресс не стоит на месте). Если ты используешь мощную IDE вроде PhpStorm, там тоже есть функция форматирования кода.
Горячие клавиши для форматирования кода в разных IDE: https://gist.github.com/codedokode/8759492
Вообще, в PHP долгое время не было единого стандарта оформления кода, все писали как попало и было много бардака, но сейчас дело лучше — есть стандарты PSR-1 и 2. Вот как надо оформлять код:
- переменные и функции пишутся с маленькой буквы, подчеркивание не используется, используется camelCase, пример: $x, $numberOfPeople, printResults()
- Название функции начинается с глагола, в стиле «сделайЧтоТо»
- не знаешь английский? Не беда, в 21 веке есть решение этой проблемы. Не пиши транслитом, открой лучше Гугл Транслейт или slovari.yandex.ru и найди название для переменной там
- в именах классов используется CamelCase, первая буква большая, «_» может использоваться
- мы предпочитаем подстановку переменных вместо конкатенации строк: "I am $age years old" — хорошо, 'I am ' . $age . ' years old' — плохо из-за обилия точек и кавычек
- мы используем для отступов 4 пробела (можно настроить редактор, чтобы при нажатии Tab он вставлял 4 пробела)
Вот ссылка на стандарты, где все это описано подробнее и даны примеры оформления:
PSR-1: https://github.com/samdark/fig-standards-ru/blob/master/accepted/ru/PSR-1-basic-coding-standard.md
PSR-2: https://github.com/samdark/fig-standards-ru/blob/master/accepted/ru/PSR-2-coding-style-guide.md
------------------
Итак, ты зашел в тред и решил помочь какому-то анону, дав ему совет или подсказку. Спасибо! Но прочти сначала эти напоминания, чтобы твоя помощь действительно была полезной.
Давай удочку, а не рыбу
Лучше не давать готовое решение проблемы, а рассказать как его искать. Может дать ключевые слова для гугла или ссылку. Но помогай, а не пытайся показать превосходство. Если даешь ссылки на нерусскоязычные статьи, упомяни об этом.
Будь доброжелателен
Не годится: «Ты мануал хоть раз в жизни открывал, обезьяна?»
Не годится: «В гугле забанили?»
Не годится: «Твой код плохой»
Хорошо: «Вот, как можно улучшить этот код: ...»
Хорошо: «Ты неправильно используешь функцию abc(). Вот ее описание: ссылка, и как видишь ей надо передать строку, а не массив»
Не придирайся к знанию английского или русского языка.
Объясняй
Не очень хорошо: «сделай как в этом коде»
Хорошо: «если ты вставляешь текст от пользователя в SQL запрос, то получается SQl-инъекция, которая позволяет взломать твой сервер (ссылки). Чтобы этого избежать, надо вставлять данные с помощью плейсхолдеров (ссылки)»
Хорошо: «Помни, что код пишется для людей. Если писать такие большие функции, то в них становится трудно разобраться...»
Не проповедуй
Мы учим использованию самых распространненных подходов, стандартов, библиотеки фреймворков. Если ты не любишь ООП, пробелы в коде, jQuery, сам PHP, то рассказать об этом стоит в каком-нибудь другом треде.
Не придирайся к знанию английского языка, анон пишет как умеет.
Ах да. Если тебе кажется, что что-то в учебнике или задачах можно сделать лучше — пиши, обратная связь всегда очень полезна.
Если тебе лень выравнивать код руками, закачай его на http://beta.phpformatter.com/ и нажми «format». Робот исправит выравнивание и отступы в мгновение ока (да, прогресс не стоит на месте). Если ты используешь мощную IDE вроде PhpStorm, там тоже есть функция форматирования кода.
Горячие клавиши для форматирования кода в разных IDE: https://gist.github.com/codedokode/8759492
Вообще, в PHP долгое время не было единого стандарта оформления кода, все писали как попало и было много бардака, но сейчас дело лучше — есть стандарты PSR-1 и 2. Вот как надо оформлять код:
- переменные и функции пишутся с маленькой буквы, подчеркивание не используется, используется camelCase, пример: $x, $numberOfPeople, printResults()
- Название функции начинается с глагола, в стиле «сделайЧтоТо»
- не знаешь английский? Не беда, в 21 веке есть решение этой проблемы. Не пиши транслитом, открой лучше Гугл Транслейт или slovari.yandex.ru и найди название для переменной там
- в именах классов используется CamelCase, первая буква большая, «_» может использоваться
- мы предпочитаем подстановку переменных вместо конкатенации строк: "I am $age years old" — хорошо, 'I am ' . $age . ' years old' — плохо из-за обилия точек и кавычек
- мы используем для отступов 4 пробела (можно настроить редактор, чтобы при нажатии Tab он вставлял 4 пробела)
Вот ссылка на стандарты, где все это описано подробнее и даны примеры оформления:
PSR-1: https://github.com/samdark/fig-standards-ru/blob/master/accepted/ru/PSR-1-basic-coding-standard.md
PSR-2: https://github.com/samdark/fig-standards-ru/blob/master/accepted/ru/PSR-2-coding-style-guide.md
------------------
Итак, ты зашел в тред и решил помочь какому-то анону, дав ему совет или подсказку. Спасибо! Но прочти сначала эти напоминания, чтобы твоя помощь действительно была полезной.
Давай удочку, а не рыбу
Лучше не давать готовое решение проблемы, а рассказать как его искать. Может дать ключевые слова для гугла или ссылку. Но помогай, а не пытайся показать превосходство. Если даешь ссылки на нерусскоязычные статьи, упомяни об этом.
Будь доброжелателен
Не годится: «Ты мануал хоть раз в жизни открывал, обезьяна?»
Не годится: «В гугле забанили?»
Не годится: «Твой код плохой»
Хорошо: «Вот, как можно улучшить этот код: ...»
Хорошо: «Ты неправильно используешь функцию abc(). Вот ее описание: ссылка, и как видишь ей надо передать строку, а не массив»
Не придирайся к знанию английского или русского языка.
Объясняй
Не очень хорошо: «сделай как в этом коде»
Хорошо: «если ты вставляешь текст от пользователя в SQL запрос, то получается SQl-инъекция, которая позволяет взломать твой сервер (ссылки). Чтобы этого избежать, надо вставлять данные с помощью плейсхолдеров (ссылки)»
Хорошо: «Помни, что код пишется для людей. Если писать такие большие функции, то в них становится трудно разобраться...»
Не проповедуй
Мы учим использованию самых распространненных подходов, стандартов, библиотеки фреймворков. Если ты не любишь ООП, пробелы в коде, jQuery, сам PHP, то рассказать об этом стоит в каком-нибудь другом треде.
Не придирайся к знанию английского языка, анон пишет как умеет.
Ах да. Если тебе кажется, что что-то в учебнике или задачах можно сделать лучше — пиши, обратная связь всегда очень полезна.
На многие посты я написал ответы, проверил задачки. Зайдите, проверьте.
Но не на все. Можете напомнить о себе в этом треде, если я вам не ответил.
Кажется я начинаю всё понимать
>Изучив теорию, советую ответить на вопрос: чему будет равен (или на какой объект будет указывать) this внутри функции в следующих случаях:
>1)
>function x() { }; x();
this будет в контексте объекта-функции x()
>2)
>var z = {};
>z.test = function () {};
>z.test();
this будет в контексте объекта z
>3)
>var z = {};
>z.test = function() {};
>var fn = z.test;
>fn();
this будет в контексте объекта fn, т.е. функции-объекта z.test
Ещё можно добавить четвертый пример:
4)
var z = {};
z.test = function() {};
var fn = z.test.call(z);
fn();
В котором this будет в контексте объекта z
>Лучше не ужасаться, а написать, что именно непонятно, и попросить совет. В нашем треде обычно всегда что-нибудь посоветуют.
Нужно стараться решать самому
>>897643
>По поводу скуки. Эти задачи - это основы, которые надо пройти, чтобы перейти к более интересным вещам вроде добавления интерактивных элементов на страницу. Если не понимать, как работают замыкания, как определяется this, то сложно будет разбраться в коде, где это используется.
>Если тебе не очень нравятся абстрактные задачи, ты ведь знаешь HTML, ты можешь попробовать начинать параллельно делать что-нибудь посложнее, можно игру в сапера, можно игру "арканоид" ( https://gist.github.com/codedokode/9933897 ) или, например, какое-нибудь простое приложение, если тебе не нравятся игры (например, написать простую электронную таблицу вроде экселя). Главное, чтобы это было на какую-то интересную тебе тему.
Мне интересно как делается динамическая подгрузка контента. Думаю, я мог бы сделать страницу с простым чатиком в задаче с Студентами, но я не знаю с чего начать. Это же ajax или уже есть что-то получше?
Управление элементами html, я думаю, это что на уровне css если нет, то я разберусь и с этим, так что пока я не буду на этом заострять внимание, но до этого тоже дойдет.
Кажется я начинаю всё понимать
>Изучив теорию, советую ответить на вопрос: чему будет равен (или на какой объект будет указывать) this внутри функции в следующих случаях:
>1)
>function x() { }; x();
this будет в контексте объекта-функции x()
>2)
>var z = {};
>z.test = function () {};
>z.test();
this будет в контексте объекта z
>3)
>var z = {};
>z.test = function() {};
>var fn = z.test;
>fn();
this будет в контексте объекта fn, т.е. функции-объекта z.test
Ещё можно добавить четвертый пример:
4)
var z = {};
z.test = function() {};
var fn = z.test.call(z);
fn();
В котором this будет в контексте объекта z
>Лучше не ужасаться, а написать, что именно непонятно, и попросить совет. В нашем треде обычно всегда что-нибудь посоветуют.
Нужно стараться решать самому
>>897643
>По поводу скуки. Эти задачи - это основы, которые надо пройти, чтобы перейти к более интересным вещам вроде добавления интерактивных элементов на страницу. Если не понимать, как работают замыкания, как определяется this, то сложно будет разбраться в коде, где это используется.
>Если тебе не очень нравятся абстрактные задачи, ты ведь знаешь HTML, ты можешь попробовать начинать параллельно делать что-нибудь посложнее, можно игру в сапера, можно игру "арканоид" ( https://gist.github.com/codedokode/9933897 ) или, например, какое-нибудь простое приложение, если тебе не нравятся игры (например, написать простую электронную таблицу вроде экселя). Главное, чтобы это было на какую-то интересную тебе тему.
Мне интересно как делается динамическая подгрузка контента. Думаю, я мог бы сделать страницу с простым чатиком в задаче с Студентами, но я не знаю с чего начать. Это же ajax или уже есть что-то получше?
Управление элементами html, я думаю, это что на уровне css если нет, то я разберусь и с этим, так что пока я не буду на этом заострять внимание, но до этого тоже дойдет.
>Что касается задачи:
>
>> var args = [].slice.call(arguments);
>> args.shift();
>Было бы хорошо, если бы ты понял, как сделать это без shift(). call тут по сути вызывает функцию [].slice, подсовывая в this ей вместо массива переменную arguments. Надо подумать, как тут передать в эту функцию еще и аргумент, чтобы она сделала копию, начиная с 1-го, а не с 0-го элемента.
>
>> for(var i = 0; i < args.length; i++) {
>> oldArgs = args;
>Скопировать массив можно методом slice: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Забыл исправить замечания...
https://jsfiddle.net/92nc4fj2/1/
Увы, пока неправильно. Вот разберем например это:
>>function x() { }; x();
> this будет в контексте объекта-функции x()
Что значит "будет в контексте"? Не очень понятная фраза. Давай попробуем проверить экспериментально. Открой отладчик браузера и введи туда
function x() { console.log(this); }
x();
И посмотри, что выведется в консоль.
Аналогично и с другими примерами.
Это все-таки основы языка, без них трудно будет что-то писать.
> Нужно стараться решать самому
Да, верно, нужно самому гуглить, искать документацию, так как например на работе никто не будет за тобой по пятам ходить и решать проблемы за тебя. Но если ты не смог сам найти решение, то наверно смысла дальше ждать нет. Можно попросить подсказку или спросить, в каком направлении дальше гуглить. Мы тут все-таки не злодеи, поможем, тем более если человек сам старается найти решение.
> Мне интересно как делается динамическая подгрузка контента.
Да, это надо изучать аякс и XMLHttpRequest ( https://developer.mozilla.org/ru/docs/Web/API/XMLHttpRequest - тут правда не очень подробно описано, так что придется еще погуглить). И разумеется, надо знать основы протокола HTTP, что такое методы, заголовки, Content-Type и тд. У меня есть недописанный урок: https://github.com/codedokode/pasta/blob/master/network/http.md
И разумеется, надо изучить DOM, чтобы знать, как например вставить на страницу новое сообщение.
Чат - хорошая идея, можешь попробовать сделать.
Увы, пока неправильно. Вот разберем например это:
>>function x() { }; x();
> this будет в контексте объекта-функции x()
Что значит "будет в контексте"? Не очень понятная фраза. Давай попробуем проверить экспериментально. Открой отладчик браузера и введи туда
function x() { console.log(this); }
x();
И посмотри, что выведется в консоль.
Аналогично и с другими примерами.
Это все-таки основы языка, без них трудно будет что-то писать.
> Нужно стараться решать самому
Да, верно, нужно самому гуглить, искать документацию, так как например на работе никто не будет за тобой по пятам ходить и решать проблемы за тебя. Но если ты не смог сам найти решение, то наверно смысла дальше ждать нет. Можно попросить подсказку или спросить, в каком направлении дальше гуглить. Мы тут все-таки не злодеи, поможем, тем более если человек сам старается найти решение.
> Мне интересно как делается динамическая подгрузка контента.
Да, это надо изучать аякс и XMLHttpRequest ( https://developer.mozilla.org/ru/docs/Web/API/XMLHttpRequest - тут правда не очень подробно описано, так что придется еще погуглить). И разумеется, надо знать основы протокола HTTP, что такое методы, заголовки, Content-Type и тд. У меня есть недописанный урок: https://github.com/codedokode/pasta/blob/master/network/http.md
И разумеется, надо изучить DOM, чтобы знать, как например вставить на страницу новое сообщение.
Чат - хорошая идея, можешь попробовать сделать.
> console.log(target);
> var s = getComputedStyle(target);
Этих строк у меня не должно быть в конечном варианте. Не хочет jsfiddle сохранить мой отредактированный код.
>>888015 4.12 github.com/fidnex/filehost (анончик, подожди еще немного, а лучше не жди, а делай следующее задание)
>>890416 8.12 https://github.com/never3ver/fileshare + тесты
>>890782 https://github.com/someApprentice/Students
>>891479 10.12 maintaskforlayout
>>893571 13.12 https://github.com/never3ver/students_list
>Что самое главное для программиста? Умение аккуратно оформлять код
Будете учится у того кто дрочит на табы пробелы красивости, так вечно на параше вилкой ковыряться.
мимоадекват
$prop {
get {
return $this->prop;
}
set {
$this->prop *=2;
}
}
Т.е. можно всё хуярить через конструктор, ну или на крайняк магию, но мне это кажется не шибко элегантным способом. Идея в том, что бы свойство получало значение только если к нему обращаются.
Не совсем. Короче делаю я допустим свойство length, которое равно strlength($this->str). Но я хочу, что бы значение свойства расчитывалось только если к нему обращаются. Такое можно сделать?
Ну, так это оно же и есть (только не надо так делать).
function __get($name) {
if ('length' == $name) {
return strlen ($this->str);
}
}
Лучше сделай православную функцию-геттер. GetLength(), и всё. А данные спрячь в private.
>Лучше сделай православную функцию-геттер. GetLength(), и всё. А данные спрячь в private.
Да я понимаю. Просто я хочу немного потренироваться в ООП, до этого немного читал c#. Просто там намного удобней получать некоторые данные, типа сделал строку да и получай себе ее длинну через точку, там свойство само просчитывается. Хочу типа такой класс сделать, который по факту строка, а все методы для работы со строками были бы включены в него, лол. Алсо, почему функция strlen определяет длинну одного кирилического символа = 2, а символ латиницы равно 1?
Ниразу. Ни одного аккуратно офрмленного поста.
>Алсо, почему функция strlen определяет длинну одного кирилического символа = 2, а символ латиницы равно 1?
strlen возвращает длинну в байтах. Кириличный символ кодируется 2 байтами (в utf-8). Для мультибайтовых кодировок используют mb_ функции. mb_strlen() в твоем случае. В шапке, в учебнике ОПа, в статье про строки есть шпаргалка.
Еще спрашивают почему пхп хуита, сука требуется длинна строки, а оно байты блять возвращает. Только отбитый будет прогать на этой хуите.
мимос++байтоеб
Сишная функция strlen тоже возвращает количество байт, для тебя и Си - "хуита", байтоёб?
Юникод это больное место скриптовых языков, у руби например до 2007-го года не было его поддержки. Алсо всё самое уродливое в PHP (проблемы с юникодом, функции возвращающие false вместо выбрасывания исключений) унаследованы от Си. Для поддержки юникода в PHP давно используются mb_* функции, для исключений - ООП обёртки.
ПОэтому поебываю байты на ++ а не на бесмысленом и беспощадном с
Тебе нужно не свойство, а метод-геттер. Магя тут не нужна, так как только усложнит понимание кода.
>>899488
Не надо паттерн из других языков тащить в PHP. Вот сделают у нас такие свойства - тогда и будешь использовать. А пока поддержки на уровне языка нет, это только усложняет код без надобности.
> хочу немного потренироваться в ООП
Это к ООП мало отношения имеет. В ООП принято для вычисляемых величин использовать методы, свойства из C# это по сути просто синтаксический сахар для вызова метода.
> Хочу типа такой класс сделать, который по факту строка, а все методы для работы со строками были бы включены в него, лол
Хорошая идея, сделай, я прокомментирую. Я уже на старте вижу несколько принципиальных вопросов:
- будет ли объект иммутабельным или нет?
- будет ли для строки использоваться какая-то кодровка или это будет набор байт?
Тут однозначного ответа нет, в каждом случае есть свои преимущества и недостатки.
Поэтому будет полезно, если ты поломаешь себе голову и попробуешь спроектировать класс для строк (хотя на практике я не вижу особого смысла его использовать конечно, только потери в производительности).
> Алсо, почему функция strlen определяет длинну одного кирилического символа = 2, а символ латиницы равно 1?
Потому что она считает длину в байтах, а не в символах. В PHP строка это набор байт, а не символов. Подробнее в моем уроке: https://github.com/codedokode/pasta/blob/master/cs/strings.md
Ну так в PHP тоже есть mb_strlen. Ты мануал или мой урок хотя бы открой и прочитай прежде чем критиковать. В Си strlen считает байты, почему она в PHP должна что-то другое считать?
>>899500
Ты мой урок читал ? Если бы читал, то знал бы почему это самая глупая идея, которую только можно придумать: https://gist.github.com/codedokode/ff99e357e9860ea169b8
>>899522
Кстати, Майкрософт героически внедрила utf-16 в винду аж в 1995 году по моему. Им пришлось сделать по 2 экземляра каждой системной функции.
Ну например CreateFileA создает файл и принимает имя файла в 8-битной системной кодировке (win-1251 для российской версии винды), а CreateFileW - в utf-16
Но позже в Юникод начали добавлять новые символы и 16 бит уже стало не хватать.
В линуксе никто не захотел заморачиваться и там используется другой подход, такой же как в PHP: любые строки рассматриваются как последовательности байт. Системный вызов вроде fopen никак не интерпретирует имя файла, а просто передает его файловой системе. Что, если записать файл в одной системе и подключить жесткий диск к системе с другой кодировкой?
http://unix.stackexchange.com/questions/2089/what-charset-encoding-is-used-for-filenames-and-paths-on-linux
Java и JS используют utf-16 с самого начала.
>будет ли объект иммутабельным или нет?
Иммутабельным - это как?
>хотя на практике я не вижу особого смысла его использовать конечно, только потери в производительности
Это да. Просто сам язык было бы проще учить, если все хотя бы самые необходимые функции были бы как-то инкапсулированы в один класс, или например если строка это объект, то легче к примеру вычленять из него отдельные символы и т.д. Но да, такое точно будет потреблять больше ресурсов.
Ты из тех самых поехавших, у которых векторы - это связанные списки? От того, что ты не используешь оверлоад, у тебя ucfirst для utf-8 не прибавится.
Иммутабельные объекты нельзя изменять, при любых операциях создается новый объект. Мутальные - можно.
У иммутабельных объектов есть преимущества и недостатки:
- иммутабельные объекты создаются при любом изменении, больше расход ресурсов. Особенно неэффективно, например, циклом менять строку по 1 символу - каждый раз создается новый объект
- с другой стороны, иммутабельность позволяет для 2 одинаковых строк не создавать 2 объекта, а использовать один и тот же: $a = $b = new String(..). В случае с мутабельными объектами так делать нельзя, так как при изменении строки в одной переиенной она поменяется и в другой.
- с другой стороны, ты знаешь, что если ты передал куда-то такой объект, никто его не поменяет. Это позволяет так же иногда делать какие-то оптимизации. Ну например если ты взял из объекта длину строки, ты можешь сохранить ее и не бояться что длина поменяется.
- в многопоточных программах параллельный доступ из нескольких потоков к таким объектам не требует блокировки (в случае мутабельных объектов в один момент времени с ним должен работать только один поток и нужна блокировка)
- очень легко проверить, что строка изменилась с помощью $s1 !== $s2: если строка изменилась, то там будет другой экземпляр объекта. В случае мутабельных объектов, нам надо сохранять копию объекта и позже сравнивать каждое свойство. Вот пример кода:
$s1 = new String(...);
$s2 = someFunction($s1);
if ($s2=== $s1) { echo "Строка не изменилась"; } else { echo "Возможно, это другая строка"; }
- ну и иногда логика работы программы подразумевает, что объект неизменяемый. Например, банковские транзакции или записи в реестре собственности на имущество - их должно быть нельзя поменять.
- в некоторых паттернах программирования, в асинхронном программировании иногда наличие иммутабельных объектов делает программу более надежной, так как мы знаем, что никто случайно их не поменяет в промежутке между асинхронными операциями.
https://habrahabr.ru/company/mailru/blog/301004/
https://ru.wikipedia.org/wiki/Неизменяемый_объект
https://ru.wikipedia.org/wiki/Неизменяемый_интерфейс
В PHP есть неизменяемые объекты даты/времен: http://php.net/manual/en/class.datetimeimmutable.php
Есть предложение добавить синтаксис для таких классов: https://wiki.php.net/rfc/immutability
Иммутабельность в яваскрипте: https://habrahabr.ru/company/devexpress/blog/302118/
Прочитай внимательно, сравни преимущества и недостатки. А ты думал, ты строку за 5 минут спроектируешь?
Иммутабельные объекты нельзя изменять, при любых операциях создается новый объект. Мутальные - можно.
У иммутабельных объектов есть преимущества и недостатки:
- иммутабельные объекты создаются при любом изменении, больше расход ресурсов. Особенно неэффективно, например, циклом менять строку по 1 символу - каждый раз создается новый объект
- с другой стороны, иммутабельность позволяет для 2 одинаковых строк не создавать 2 объекта, а использовать один и тот же: $a = $b = new String(..). В случае с мутабельными объектами так делать нельзя, так как при изменении строки в одной переиенной она поменяется и в другой.
- с другой стороны, ты знаешь, что если ты передал куда-то такой объект, никто его не поменяет. Это позволяет так же иногда делать какие-то оптимизации. Ну например если ты взял из объекта длину строки, ты можешь сохранить ее и не бояться что длина поменяется.
- в многопоточных программах параллельный доступ из нескольких потоков к таким объектам не требует блокировки (в случае мутабельных объектов в один момент времени с ним должен работать только один поток и нужна блокировка)
- очень легко проверить, что строка изменилась с помощью $s1 !== $s2: если строка изменилась, то там будет другой экземпляр объекта. В случае мутабельных объектов, нам надо сохранять копию объекта и позже сравнивать каждое свойство. Вот пример кода:
$s1 = new String(...);
$s2 = someFunction($s1);
if ($s2=== $s1) { echo "Строка не изменилась"; } else { echo "Возможно, это другая строка"; }
- ну и иногда логика работы программы подразумевает, что объект неизменяемый. Например, банковские транзакции или записи в реестре собственности на имущество - их должно быть нельзя поменять.
- в некоторых паттернах программирования, в асинхронном программировании иногда наличие иммутабельных объектов делает программу более надежной, так как мы знаем, что никто случайно их не поменяет в промежутке между асинхронными операциями.
https://habrahabr.ru/company/mailru/blog/301004/
https://ru.wikipedia.org/wiki/Неизменяемый_объект
https://ru.wikipedia.org/wiki/Неизменяемый_интерфейс
В PHP есть неизменяемые объекты даты/времен: http://php.net/manual/en/class.datetimeimmutable.php
Есть предложение добавить синтаксис для таких классов: https://wiki.php.net/rfc/immutability
Иммутабельность в яваскрипте: https://habrahabr.ru/company/devexpress/blog/302118/
Прочитай внимательно, сравни преимущества и недостатки. А ты думал, ты строку за 5 минут спроектируешь?
Ну вот пример где пригодилсиь бы иммутабьельные объекты. Допустим, у нас есть приложение на JS и в нем объект-модель, и мы передаем эту модель в функцию работы с API, чтобы она сохранила ее на сервер. Функция разумеется асинхронная, то есть она добавляет модель в очередь на сохранение и возвразщается, а когда именно она будет отправлена на сервер - неизвестно. И есть такой код:
var user = new UserModel(...);
saveToServer(user);
...
Вот здесь, если user мутабельный, то есть риск что код ниже поменяет его и на сервер отправятся не те данные, которые мы передали в saveToServer. Потому мы должны либо делать копию, либо использовать иммутабельную модель, которую мы разумеется назовем не UserModel, а UserState.
В общем-то кажись понял. Ну, мой тогда наверное будет мутабельным - как бы строки часто надо конкатенировать. Но тут я даже не знаю как сделать - самый простой сделать метод, типа concat($str) { $this->string += $str } но тут падает юзабельность, т.к. не выйдет просто две строчки склеивать. А в идеале я хочу, что бы объект можно было бы использовать просто как обычную переменную. Вот распечатывать ее легко, сделал _toString и все.
Если ты сам будешь делать класс-строку, то да, будет тратиться больше ресурсов. Если бы она была сделана на уровне самого языка, как в JS, то ее можно было бы сделать оптимизированной.
Ну тут тоже есть варианты:
$a->append($b);
$c = $a->concat($b);
$d = String::concat($a, $b, $c);
Выбирай.
Мне вот собственно интересно, почему такого не сделали? Вот есть же PDO например. Я думаю стильность-модность-молодежность языка сразу же повысилась бы, плюс не обязательно же заставлять всех это использовать, просто добавить как фичу.
Я взглянул на этот текст, там какой-то поехавший обращается ко мне на ты и расписывает налево-направо, что хорошо, а что плохо, как будто что-то знает. А сам в соседнем треде не может отличить связанный список от вектора.
Как дополнительную фичу делать нельзя, так как будет 2 вида строк и постоянные преобразования из одного в другой.
Ну вот например, в PHP есть ArrayObject - аналог массива, и что? В array_sum например его передать нельзя.
>>899594
Но ты ведь тоже не на "вы" обращаешься. И давай тогда не будем больше добавлять сообщения не по теме треда.
Когда обращаюсь к анонимусу на двачах, а не к читателю из широкого круга неизвестных лиц, это было бы пошлостью.
> давай
Давай тогда больше не будем думать, что векторы - это такие связанные списки.
Только если для ознакомления. Нейронные сети - это все-таки задачи на обработку больших объемов данных и лучше всего реализуются на языках вроде Си. Насколько я знаю, есть готовые библиотеки, но не знаю, можно ли их подключить из PHP. Обычно там используют сишную библиотеку и управляющий скрипт на скриптовом языке вроде Питона.
Погугли по "php neural network". Там на первом месте например такое расширение: http://php.net/manual/ru/book.fann.php
Я сам нейросети не использовал, потому предлагаю тебе изучить, какие есть библотеки, самостоятельно: https://www.google.ru/search?q=php+neural+network&newwindow=1&gbv=1&sei=4lVdWOnPEuvdgAbcp4D4CQ
http://pastebin.com/ibcht1NG
Да, только тут есть один момент. У DOM элемента внутреннее содержимое можно получить 2 способами:
- node.innerHTML (долгое время был нестандартным и работал только в ИЕ, потом вроде стандартизовали)
- node.textContent
Надо сравнить, как работают эти 2 свойства в разных браузерах при условии что в шаблоне содержатся теги, переносы строк и различные спецсимволы вроде & gt ;
Статья в MDN: https://developer.mozilla.org/ru/docs/Web/API/Node/textContent
Также важно помнить, что теги script и style имеют немного другое правило обработки содержимого: весь текст в них воспринимается как есть, html-мнемоники и теги не интерпретируются, в отличие от содержимого обычных элементов вроде div.
Таки сделал небольшой набросок класса для строк. Только не смогу решить проблемку с выводом неправильной кодировки в методе charAt, ОПчик, расскажи позяз как это делается.
Придется прочитать урок https://github.com/codedokode/pasta/blob/master/cs/strings.md
$s[0] простой берет 0-й байт из строки, а не 0-й символ так как символ не обязательно занимает 1 байт.
Нет, надо изучить как эти свойства ведут себя в разных браузерах и какое точнее возвращает содержимое тега при наличии разных спецсимволов.
Я не помню, какое именно надо использовать.
>>899674
> $chars = preg_split("//u", $this->string, null, PREG_SPLIT_NO_EMPTY);
> return $chars[$c];
Дороговато каждый раз создавать огромный массив символов, чтобы взять из него один. Лучше бы mb_substr использовать.
И еще, //u работает только со строками в utf-8. У тебя нигде не написано, в какой кодировке строку надо передавать в конструктор.
Ты должен определиться, как ты будешь воспринимать строки: как набор байт, как строка в жестко заданной кодировке, как строка, где кодировка указывается отдельно. Иначе нельзя гарантировать, что код работает корректно, если он не знает, в какой кодировке строка.
У аргумента в конструкторе надо бы сделать пустое знаечние по умолчанию.
Также, я бы тебе советовал изучить, как реализованы объекты строк в других языках, например:
- яваскрипт
- ява
- C#
Может можно взять что-то полезное.
>как строка в жестко заданной кодировке
Хорошо, буду делат в utf-8 по умолчанию с возможностью сменить кодировку.
>У аргумента в конструкторе надо бы сделать пустое знаечние по умолчанию.
А это зачем?
Чтобы можно было создать новую пустую строку не указывая аругменты. new String() смотрится лучше чем new String('');
Но ведь тогда придется еще один метод делать и вызывать потом, лишний код. Жаль нельзя необязательные параметры в конструктор давать.
Конструктор такая же функция и у нее могут быть значения аргументов по умолчанию. И давай-ка вместо маленьких постов каждые 5 минут писать большие со списком вопросов.
Спасибо
http://ideone.com/UoEV5H
Ну сегодня пока родил вот это вот чудо. Тогда вопросы, лол. Iconv громоздкая функция, зачем определять изначальную кодировку строки?
Загадка "почему шрифты не работают на гитхабе в ИЕ8" решена!
Ты используешь mb_detect_encoding. Как по твоему она работает?
Если ты читал мой урок, то знаешь что строка в памяти хранится как последовательность байт. Вот у тебя есть строка из 2 байт - 194 185 - как определить, в какой она кодировке? В одной кодировке это будут одни символы, в другой - другие. Как понять, какая была использована при кодировании строки?
> public function __construct()
Почему не __construct($string = '') ?
> changeEncod
Не надо так сокращать. И в чем смысл этой функции? Вот я создам строку с utf-8, сконвертирую ее в windows-1251 и что дальше? Твой класс будет работать корректно? charAt() например?
По моему тебе надо разрбраться, что такое кодировки и как строка кодируется в памяти.
> $this->string += $param;
+= это числовое сложение, а не строк
В общем, все пока неправильно.
application/views/[контроллера без приставки]/[action без приставки]
В итоге у меня такая проблема, например, для пути нужен main, а у меня в свойствах содержится MainController, будет правильно обрезать приставку Controller специальной функцией? Или же лучше создать специальные свойства у главного контроллера где хранить имя контроллера и экшена без приставов? Спрашиваю потому, что если воспользоваться первым вариантом, то получается что вначале получаешь имя контроллера из строки, потом дописываешь Controller, используешь его, а потом заново режешь, как-то не логично, лишнее действие. Если использовать второй вариант, то как-то тоже не логично, зачем плодить свойства у главного контроллера. Единственное что более менее логично выглядит это изначально хранить исходные данные в массиве без приставок, но так придётся плодить в контроллере много лишних переменных для временного значения.
Сделай класс Router, который будет парсить строку и методами выдавать имена с приставками и без приставок. И в контроллере храни объект этого класса. Например.
О, это идея, спасибо.
class Main extends Application {
public function indexAction() {
$model = new Model();
$posts = $model->findAll();
//делаем что-нибудь с переменной posts
//передаём переменные в вид
$this->set("posts", "title_post");
}
}
Меня в общем смущает, что в контроллере будет много кода, а в моделе его практически не будет вообще
Модель вообще не должна иметь публичных методов для доступа к базе данных. Вместо этого можно определить в ней пару конкретных методов типа getPosts(), возвращающие результат, удобный для отображения. Потом вызывать их из контроллера, а еще лучше прямо из представления. А в контроллере обрабатывать ввод.
http://ideone.com/GsWE9B клиент загрузчик
там в следующей задаче написано про многопоточность, я что-то не нашел ничего внятного про многопоточность на пхп, только открытие нескольких процессов вручную (зачем?).
Не понял чего ты хочешь от своей модели добиться. Модель это не какой-то конкретный класс, который умеет делать все, это набор классов для работы с данными. Если ты уж хочешь сделать некую модель, в которой будут базовые методы для работы с таблицами типа findAll(), insert() и т.д., короче CRUD, то сделай абстрактный класс с этой с этими методами, а уже от него наследуй нужные тебе вещи. Например, у тебя есть абстрактный класс Model с методами CRUD, и тебе надо сделать модель для работы со статьями, тогда просто делай класс Articles и наследуй его от Model. Правда я сам ньюфаг, но делал бы так, может ОП поправит меня.
https://jsfiddle.net/q2hsufgy/2/
Думаю, переделывать ли по моде это все в класс? Буду читать все про классы в javascript.
>Есть какие-нибудь примеры реализации нейронных сетей на ПХП?
Пример реализации персептрона на пыхе: http://xcont.com/perc/newperceptron/
Какая-то сложная конструкция. View обычно получает имя темплейта, плюс путь к css и js файлам, view рендерит темплейт, передает ему путь к css и скриптам, те грузятся из темплейта. Пути к css, js и темплейтам можно в ini хранить, да.
Алсо места где какой скрипт и css грузить, обычно намертво в темплейте забиты, подставляются только пути к директориям на серваке, полученные от view.
Htmlacademy хорошо зайдет для изучение базы и основ, дерзай. Для более серьезного изучения конечно же придется обмазываться изучениям доп.литературы. Алсо не научишься ты тупо читая теги.
fsdgs&erewwet&erewtrewt&sw&rewr
dr&erewwettwwtwte&
fgerer
И прочие подобные им. Количество символов может быть любым, наличие & не обязательно, как например в третьем варианте. Как проверить соответствие такой строки через регулярные выражения?
Сижу сейчас читаю на php.net инструкцию по настройке связи с PostgreSQL, и уже на первом мануале обосрался:
Для того, чтобы включить поддержку PostgreSQL необходимо скомпилировать PHP с директивой --with-pgsql[=DIR] . Параметр DIR определяет путь к установочной директории PostgreSQL,
скомпилировать с директивой - это как?
установочная директория - папка, где все файлы, связанные с postgresql лежат, или путь к папке с .exe, который как бе и есть сам сервер?
Алсо, сейчас хостую демонстрационный сайт через XAMPP. Хватит ли мне его, чтобы взаимодействовать с PostgreSQL, или нужно ставить специально сконфигурированный под него апач?
Обсуждать что-то о уроках, рассказывать впечатления о изученном, прогресс показывать, и т.п.
Раскомментируй
extension=php_pdo_pgsql.dll/.so
в php.ini
Это инструкции при сборке из исходников, у тебя, скорее всего, уже есть этот модуль.
Напиши свой, кто-то будут рады посидеть. Будь мужиком, есть идея - возьми и осуществи ее.
---
Офигенная библиотека для проверки корректности данных:
https://github.com/beberlei/assert
Ну, вообще, кому как.
- В конструкторе парсит строку, заносит в свойства класса значение контроллера, экшена и параметров
- В методе route вызывает запрашиваемый контроллер и его метод.
- После вызова текущего контролера и его метода, вызывает специальный метод главного контроллера getView, в котором создаётся объект класса главного View и уже в нём вызывает функция render с передаваемыми параметрами.
В итоге, большая часть всех функций у меня возложена на главный контроллер, это правильно вообще? Думал создать класс Router и переложить в него часть функций, но не вижу смысла передавать туда сюда одни и те же параметры постоянно, мне кажется что так как сейчас удобнее.
Прочитал Кантора с приложениями.
Досматриваю Борисова по пхп.
Только вот беда в том что в голове каша.
Я мало трогал лабы везде и нахватался по верхам.
Как перебороть неуверенность и начать дальше развиваться практикой, что написать можно?
А то в голову лезет только диплом - сайт на cms.
>>899152
База данных
Тут надо проставить внешние ключи (ADD CONSTRAINT FOREIGN KEY), для колонок вроде parent_id, file_id, ссылающихся на другие поля таблиц.
> `author` varchar(255) NOT NULL DEFAULT 'anonymous'
Лучше по умолчанию записывать null, а при выводе заменять. А то не отличить тех, кто не ввел имя, от тех, кто ввел anonymous.
Файлы настроек
У структуры кода, которая используется (с вынесенем файлов settings.php, dependencies.php), я вижу такой недостаток:
- нет bootstrap-файла. Если ты например захочешь добавить скрипт командной строки, и в нем исплоьзовать контейнер и настройки, то придется копипастить код создания $app из index.php.
Мне бы больше понравилась такая структура: файл bootstrap.php, который инициализирует приложение, задавая настройки и сервисы в контейнере. А роуты можно прописать либо в отдельном файле либо в самом index.php.
Как я понимаю, эту схемы ты не сам придумал, а взял из шаблона приложения на Slim (Slim-Skeleton). Я даже писал авторам этого шаблона, высказав те же мысли, но они написали, что их такая схема устраивает.
Также, в settings.php очень много настроек. Возможно (пользователям) было бы удобнее, если бы настройки, которые пользователь может менять, были бы вынесены в один конфиг, а которые не может - в другой. В более сложном приложении получится очень большой конфиг, в котором будет трудно разобраться. В Симфони например эта проблема решается тем, что есть файл конфига config.yml, и есть файл с изменяемыми параметрами parameters.yml.
В твоем случае изменяемые настройки - это например имя базы данных. А неизменяемые - это например драйвер базы данных или template_path.
Соответственно, неизменяемые параметры конфига вроде settings.resize, или templates_path, если они используются только 1 раз, можно даже прописать прямо в контейнере. А можно конечно и в конфиге.
DI контейнер
Еще мне не очень нравится такая штука:
> $view->addExtension(new \Slim\Views\TwigExtension(
> $c['router'],
> $c['request']->getUri()
> ));
Не очень хорошо, что экземпляр request используется в контейнере.
Сервисы - это классы, которые обычно существуют в одном экземпляре, и не представляют какую-то сущность, а содержат полезные методы. И обычно сервисы не должны иметь в зависимостях HTTP запрос, так как в теории они могут служить для обработки нескольких запросов. Или их можно вызывать из скриптов командной строки, где никакого запроса нет. У тебя twig нельзя использовать в скрипте для командной строки, так как там нет объекта request. А необходимость может быть, например, чтобы сгенерировать письмо по twig-шаблону.
Кстати, такие проблемы много где есть. В Симфони роутер (и генератор URL) в качестве зависимости получает HTTP-запрос. Если его вызвать из скрипта командной строки, он может генерировать URL вида http://localhost/... (так как он привык брать имя домена из заголовка запроса HTTP_HOST, а тут его нету). Это указывает на ошибку в проектировании (или в настройке) генератора URL в данном случае.
Более того, в гитхабе библиотеки Slim-Views в конструктор ничего не передается: https://github.com/slimphp/Slim-Views#twig-1
Это не ошибка? Или у тебя более старая версия?
> $capsule->setAsGlobal();
Вот тут ты делаешь экземпляр объекта доступным через статические методы (Capsule::table(..)). Но тут есть проблема, что мы не знаем точный момент, когда будет выполнена эта строчка (setAsGlobal), и, соответственно, не можем сказать, с какого момента можно использовать статические методы. Она ведь не сработает, пока кто-то не запросит экземпляр $container->db. Эту строку надо было помещать не в контейнер, а после него, чтобы объект был гарантированно создан в начале программы:
$container->db->setAsGlobal();
Либо же мы бы могли сделать функцию $container->initDatabase() и вызывать ее перед созданием сервисов, которым нужна база данных.
А сейчас у тебя все работает с расчетом на то, что кто-то создаст сервис db до того, как попытается воспользоваться какими-то методами Eloquent. Это ненадежно, надо явно создать нужный объект.
Вот этот код очень нелогичный:
> $container['filesModel'] = $container->factory(function ($c) {
> $c->db;
> return new App\Models\Files;
> });
Какой смысл в строчке $c->db? Никакого. Ты видимо полагаешься на побочные эффекты (вызов setAsGlobal) от создания сервиса db. Побочные эффекты это плохо, и они неочевидны при чтении кода. Не надо так, это как раз то, с чем борется dependency injection. Если бы сервис принрмал $db как аргумент, было бы так:
return new App\Model\FilesTable($c->db);
Но Eloquent вроде не позволяет передать объект БД в модели. Их схема с статическими методами не очень хорошо сочетается с DI контейнером. Это в общем-то проблема паттерна Active Record, потому что там возможен такой код:
$user = new User;
$user->save();
Или такой: $users = User::all();
И тут непонятно, где User берет соединение с БД. Эту проблему теоретически можно решить, если явно передавать объект БД в конструктор (new User($db)) или если создавать объекты не самому ($user = $db->createUser()), но обычно Active Record получает объект БД через статические методы со всеми вытекающими последствиями. Как я понимаю, причина в том, что laravel скопирован с ruby on rails, а там хотят минимум настроек и конфигурации и потому они хотят использовать статические методы вместо DI.
И еще, как я понимаю, в Eloquent объект App\Model\Files одновременно представляет и одну запись из базы, и объект, позволяющий делать операции над таблицей. И вдобавок еще можно использовать статические методы вроде Files::take(10). Потому код выглядит не очень логично: где-то ты получаешь Files из контейнера, а где-то создаешь через new.
Насчет контроллеров, я вижу, там много зависимостей, иногда для упрощения в конструктор контроллера просто передают сам контейнер. Но можно и сервисы по отдельности, конечно.
>>899152
База данных
Тут надо проставить внешние ключи (ADD CONSTRAINT FOREIGN KEY), для колонок вроде parent_id, file_id, ссылающихся на другие поля таблиц.
> `author` varchar(255) NOT NULL DEFAULT 'anonymous'
Лучше по умолчанию записывать null, а при выводе заменять. А то не отличить тех, кто не ввел имя, от тех, кто ввел anonymous.
Файлы настроек
У структуры кода, которая используется (с вынесенем файлов settings.php, dependencies.php), я вижу такой недостаток:
- нет bootstrap-файла. Если ты например захочешь добавить скрипт командной строки, и в нем исплоьзовать контейнер и настройки, то придется копипастить код создания $app из index.php.
Мне бы больше понравилась такая структура: файл bootstrap.php, который инициализирует приложение, задавая настройки и сервисы в контейнере. А роуты можно прописать либо в отдельном файле либо в самом index.php.
Как я понимаю, эту схемы ты не сам придумал, а взял из шаблона приложения на Slim (Slim-Skeleton). Я даже писал авторам этого шаблона, высказав те же мысли, но они написали, что их такая схема устраивает.
Также, в settings.php очень много настроек. Возможно (пользователям) было бы удобнее, если бы настройки, которые пользователь может менять, были бы вынесены в один конфиг, а которые не может - в другой. В более сложном приложении получится очень большой конфиг, в котором будет трудно разобраться. В Симфони например эта проблема решается тем, что есть файл конфига config.yml, и есть файл с изменяемыми параметрами parameters.yml.
В твоем случае изменяемые настройки - это например имя базы данных. А неизменяемые - это например драйвер базы данных или template_path.
Соответственно, неизменяемые параметры конфига вроде settings.resize, или templates_path, если они используются только 1 раз, можно даже прописать прямо в контейнере. А можно конечно и в конфиге.
DI контейнер
Еще мне не очень нравится такая штука:
> $view->addExtension(new \Slim\Views\TwigExtension(
> $c['router'],
> $c['request']->getUri()
> ));
Не очень хорошо, что экземпляр request используется в контейнере.
Сервисы - это классы, которые обычно существуют в одном экземпляре, и не представляют какую-то сущность, а содержат полезные методы. И обычно сервисы не должны иметь в зависимостях HTTP запрос, так как в теории они могут служить для обработки нескольких запросов. Или их можно вызывать из скриптов командной строки, где никакого запроса нет. У тебя twig нельзя использовать в скрипте для командной строки, так как там нет объекта request. А необходимость может быть, например, чтобы сгенерировать письмо по twig-шаблону.
Кстати, такие проблемы много где есть. В Симфони роутер (и генератор URL) в качестве зависимости получает HTTP-запрос. Если его вызвать из скрипта командной строки, он может генерировать URL вида http://localhost/... (так как он привык брать имя домена из заголовка запроса HTTP_HOST, а тут его нету). Это указывает на ошибку в проектировании (или в настройке) генератора URL в данном случае.
Более того, в гитхабе библиотеки Slim-Views в конструктор ничего не передается: https://github.com/slimphp/Slim-Views#twig-1
Это не ошибка? Или у тебя более старая версия?
> $capsule->setAsGlobal();
Вот тут ты делаешь экземпляр объекта доступным через статические методы (Capsule::table(..)). Но тут есть проблема, что мы не знаем точный момент, когда будет выполнена эта строчка (setAsGlobal), и, соответственно, не можем сказать, с какого момента можно использовать статические методы. Она ведь не сработает, пока кто-то не запросит экземпляр $container->db. Эту строку надо было помещать не в контейнер, а после него, чтобы объект был гарантированно создан в начале программы:
$container->db->setAsGlobal();
Либо же мы бы могли сделать функцию $container->initDatabase() и вызывать ее перед созданием сервисов, которым нужна база данных.
А сейчас у тебя все работает с расчетом на то, что кто-то создаст сервис db до того, как попытается воспользоваться какими-то методами Eloquent. Это ненадежно, надо явно создать нужный объект.
Вот этот код очень нелогичный:
> $container['filesModel'] = $container->factory(function ($c) {
> $c->db;
> return new App\Models\Files;
> });
Какой смысл в строчке $c->db? Никакого. Ты видимо полагаешься на побочные эффекты (вызов setAsGlobal) от создания сервиса db. Побочные эффекты это плохо, и они неочевидны при чтении кода. Не надо так, это как раз то, с чем борется dependency injection. Если бы сервис принрмал $db как аргумент, было бы так:
return new App\Model\FilesTable($c->db);
Но Eloquent вроде не позволяет передать объект БД в модели. Их схема с статическими методами не очень хорошо сочетается с DI контейнером. Это в общем-то проблема паттерна Active Record, потому что там возможен такой код:
$user = new User;
$user->save();
Или такой: $users = User::all();
И тут непонятно, где User берет соединение с БД. Эту проблему теоретически можно решить, если явно передавать объект БД в конструктор (new User($db)) или если создавать объекты не самому ($user = $db->createUser()), но обычно Active Record получает объект БД через статические методы со всеми вытекающими последствиями. Как я понимаю, причина в том, что laravel скопирован с ruby on rails, а там хотят минимум настроек и конфигурации и потому они хотят использовать статические методы вместо DI.
И еще, как я понимаю, в Eloquent объект App\Model\Files одновременно представляет и одну запись из базы, и объект, позволяющий делать операции над таблицей. И вдобавок еще можно использовать статические методы вроде Files::take(10). Потому код выглядит не очень логично: где-то ты получаешь Files из контейнера, а где-то создаешь через new.
Насчет контроллеров, я вижу, там много зависимостей, иногда для упрощения в конструктор контроллера просто передают сам контейнер. Но можно и сервисы по отдельности, конечно.
>>899152
MainController
Еще мне не нравятся такие вещи:
> foreach ($files as &$file) {
> $file->downloadUrl = $this->router->pathFor('downloadFile', array(
Это в общем плохая идея, добавлять свойства в объекты. Объекты тем и хороши, что они относятся к какому-то классу, и можно посмотреть, какие у них есть свойства. И ты знаешь, что если у тебя объект класа X, то у него есть свойства a, b, с. А если добавлять свойства динамически, то появляется путаница, где-то эти свойства доступны, где-то нет.
Мне кстати и в Eloquent не нравится, что в модели не объявлены свойства. Понятно, что это делается, чтобы экономить время на их написании, но в более-менее сложном приложении очень неудобно, когда не знаешь какие у объекта свойства. А если их добавляют динамически, то путаницы только больше становится.
Получать viewUrl/downloadUrl удобнее наверно в самом шаблоне.
Я бы тебе советовал для начала все же учиться писать обычный ООП код с минимумом магии и побочных эффектов, а когда освоишь, можешь пробовать другие подходы. Eloquent, раз ты уж его взял, пусть остается, но постарайся его интегрировать с минимумом побочных эффектов и без странных вызовов вроде $c->db;
> $files = $this->filesModel->take($this->appSettings['listFilesCount'])->orderBy('id', 'desc')->get();
Вот тут я вижу, что ты плохо понимаешь MVC. Ты пишешь много кода в контроллере, вместо того, чтобы разбивать код на контроллер (который разбирает HTTP-запрос) и модель (которая отвечает за логику обработки данных). В данном случае должно быть примерно так:
$files = $this->filesModel->getLatestFiles($count);
А у тебя получаются "толстые контроллеры", то есть ты весь код обработки запроса пишешь длинной стеной и кладешь его в контроллер, а надо учиться разбивать его на отдельные действия. Код держать в контроллере невыгодно, так как там он не повторно используемый.
Контроллер обычно только управляет процессом обработки запроса. Ему не надо знать как сортируются записи в базе и по каким условиям их надо выбирать.
То же самое касается обработчика запроса на загрузку файла. У тебя сейчас все в контроллере. А ведь сохранение нового файла - это часть модели. Чтобы ты лучше понял, как сделать код повторно используемым, сделай такую вещь:
Напиши скрипт командной строки, который принимает на вход имя 1 или более файлов, загружает их и выводит ссылки. Используется примерно так:
php upload.php file1.txt file2.jpg
php upload.php *.jpg
И на выходе получается что-то такое:
http://fileservice/id1 # file1.txt
http://fileservice/id2 # file2.jpg
Соответственно, тебе надо сделать методы загрузки (и может быть валидации) в модели. И придумать, как сделать код загрузки, не завязанный на массив $_FILES, которого не будет в консольном скрипте. Идея MVC как раз в том, что методы модели могут использоваться несколькими контроллерами и данные из нее выводиться несколькими представлениями.
Также, вот этот код непраильный:
> $newFile = $this->filesModel;
Это ведь не создает новый объект, а берет существующий из контейнера. Если второй раз выполнить эту строчку, то вернется тот же самый объект. Тут очевидно должно быть написано $x = new Files.
> $newFile->save();
> if($newFile->id) {
Вот это непонятно, а как тут может не быть id? В каком случае?
Далее, неправильно коммитить транзакцию до сохранения файла на диск так как в этом случае возможна ситуация, когда запись в базе уже есть, а файла на диске еще нет.
> return $response->withJson([
> 'text' => $file->getError(),
> 'status' => 'ERROR'
> ], 400);
Я не уверен что стоит использовать тут HTTP код 400, так как клиентские библиотеки вроде jquery с методом $.ajax не дадут тебе JSON-объект, если код ответа не равен 200. Хотя я видел случаи, когда HTTP-коды ошибок используются для сигнализирования об ошибках валидации, но это не всегда удобно.
Также, я дополнил комментарии к задаче: https://gist.github.com/codedokode/9424217 - внимательно перечитай их, ибо у тебя часть ошибок там уже описана.
В этом контроллере много кода, и он немного запутанный, надо его рефакторить: https://github.com/fidnex/filehost/blob/master/src/App/Controllers/ViewFileController.php
https://github.com/fidnex/filehost/blob/master/src/App/Views/home.twig#L14
тут SVG файл лучше вынести в отдельный файл и как-то инклудить, чтобы его можно было бы редактировать.
JS код
https://github.com/fidnex/filehost/blob/master/public/assets/file-view.js
Вот тут я вижу паттерны, которые мне не очень нравятся, а именно:
> (function(commentContainer, commentFormContainer) {
> ....
> })(document.querySelector('.addition-area'), document.getElementById('comment-form'))
Можешь объяснить, чем это лучше чем просто
var commentContainer = document.querySelector('.addition-area')
На мой взгляд, так будет проще и понятнее.
Также, мне не нравится паттерн, когда JS-файл содержит код вне функций, выполняющийся при подключении файла. Мне больше нравится идея, когда файл содержит только функции (или объекты), но ничего не делает сам при подключении. Так получается нагляднее,когда какая-то функция вызвыается явно. Хотя на практике часто пишут в внешнем файле $.ready(function () { .. }), мне это не нравится.
Если ты подключаешь JS файл в конце HTML-файла, то есть недостаток, что до момента загрузки этого файла кнопки и интерактивные элементы не работают. А вот если подключить файл в начале и использовать onclick="doSomething()" то такой проблемы нет. Хотя часто делают так, как ты сделал, но мне это не нравится.
Ты исплоьзуешь Element.closest(), но судя по https://developer.mozilla.org/ru/docs/Web/API/Element/closest это совсем новая технология и в не самых новых браузерах твой код просто не заработает.
> (function(f, w) {
По моему не очень удачные названия переменных.
Насчет перетаскивания и вставки файлов через Ctrl + V - а ты тестировал перетаскивание и копирование из разных источников? (папка, браузеры, офисные программы)? Если нет, советую протестировать, так как они часто передают информацю о файле в разных форматах.
> xhr.onreadystatechange = function() {
> if (xhr.readyState == 4) {
> if(xhr.status == 200) {
> var data = JSON.parse(xhr.responseText)
Вот тут тоже у тебя стена кода без разбиения на функции. Не надо напрямую использовать XMLHttpRequest, он неуклюжий, лучше написать отдельную функцию отправки запроса.
>>899152
MainController
Еще мне не нравятся такие вещи:
> foreach ($files as &$file) {
> $file->downloadUrl = $this->router->pathFor('downloadFile', array(
Это в общем плохая идея, добавлять свойства в объекты. Объекты тем и хороши, что они относятся к какому-то классу, и можно посмотреть, какие у них есть свойства. И ты знаешь, что если у тебя объект класа X, то у него есть свойства a, b, с. А если добавлять свойства динамически, то появляется путаница, где-то эти свойства доступны, где-то нет.
Мне кстати и в Eloquent не нравится, что в модели не объявлены свойства. Понятно, что это делается, чтобы экономить время на их написании, но в более-менее сложном приложении очень неудобно, когда не знаешь какие у объекта свойства. А если их добавляют динамически, то путаницы только больше становится.
Получать viewUrl/downloadUrl удобнее наверно в самом шаблоне.
Я бы тебе советовал для начала все же учиться писать обычный ООП код с минимумом магии и побочных эффектов, а когда освоишь, можешь пробовать другие подходы. Eloquent, раз ты уж его взял, пусть остается, но постарайся его интегрировать с минимумом побочных эффектов и без странных вызовов вроде $c->db;
> $files = $this->filesModel->take($this->appSettings['listFilesCount'])->orderBy('id', 'desc')->get();
Вот тут я вижу, что ты плохо понимаешь MVC. Ты пишешь много кода в контроллере, вместо того, чтобы разбивать код на контроллер (который разбирает HTTP-запрос) и модель (которая отвечает за логику обработки данных). В данном случае должно быть примерно так:
$files = $this->filesModel->getLatestFiles($count);
А у тебя получаются "толстые контроллеры", то есть ты весь код обработки запроса пишешь длинной стеной и кладешь его в контроллер, а надо учиться разбивать его на отдельные действия. Код держать в контроллере невыгодно, так как там он не повторно используемый.
Контроллер обычно только управляет процессом обработки запроса. Ему не надо знать как сортируются записи в базе и по каким условиям их надо выбирать.
То же самое касается обработчика запроса на загрузку файла. У тебя сейчас все в контроллере. А ведь сохранение нового файла - это часть модели. Чтобы ты лучше понял, как сделать код повторно используемым, сделай такую вещь:
Напиши скрипт командной строки, который принимает на вход имя 1 или более файлов, загружает их и выводит ссылки. Используется примерно так:
php upload.php file1.txt file2.jpg
php upload.php *.jpg
И на выходе получается что-то такое:
http://fileservice/id1 # file1.txt
http://fileservice/id2 # file2.jpg
Соответственно, тебе надо сделать методы загрузки (и может быть валидации) в модели. И придумать, как сделать код загрузки, не завязанный на массив $_FILES, которого не будет в консольном скрипте. Идея MVC как раз в том, что методы модели могут использоваться несколькими контроллерами и данные из нее выводиться несколькими представлениями.
Также, вот этот код непраильный:
> $newFile = $this->filesModel;
Это ведь не создает новый объект, а берет существующий из контейнера. Если второй раз выполнить эту строчку, то вернется тот же самый объект. Тут очевидно должно быть написано $x = new Files.
> $newFile->save();
> if($newFile->id) {
Вот это непонятно, а как тут может не быть id? В каком случае?
Далее, неправильно коммитить транзакцию до сохранения файла на диск так как в этом случае возможна ситуация, когда запись в базе уже есть, а файла на диске еще нет.
> return $response->withJson([
> 'text' => $file->getError(),
> 'status' => 'ERROR'
> ], 400);
Я не уверен что стоит использовать тут HTTP код 400, так как клиентские библиотеки вроде jquery с методом $.ajax не дадут тебе JSON-объект, если код ответа не равен 200. Хотя я видел случаи, когда HTTP-коды ошибок используются для сигнализирования об ошибках валидации, но это не всегда удобно.
Также, я дополнил комментарии к задаче: https://gist.github.com/codedokode/9424217 - внимательно перечитай их, ибо у тебя часть ошибок там уже описана.
В этом контроллере много кода, и он немного запутанный, надо его рефакторить: https://github.com/fidnex/filehost/blob/master/src/App/Controllers/ViewFileController.php
https://github.com/fidnex/filehost/blob/master/src/App/Views/home.twig#L14
тут SVG файл лучше вынести в отдельный файл и как-то инклудить, чтобы его можно было бы редактировать.
JS код
https://github.com/fidnex/filehost/blob/master/public/assets/file-view.js
Вот тут я вижу паттерны, которые мне не очень нравятся, а именно:
> (function(commentContainer, commentFormContainer) {
> ....
> })(document.querySelector('.addition-area'), document.getElementById('comment-form'))
Можешь объяснить, чем это лучше чем просто
var commentContainer = document.querySelector('.addition-area')
На мой взгляд, так будет проще и понятнее.
Также, мне не нравится паттерн, когда JS-файл содержит код вне функций, выполняющийся при подключении файла. Мне больше нравится идея, когда файл содержит только функции (или объекты), но ничего не делает сам при подключении. Так получается нагляднее,когда какая-то функция вызвыается явно. Хотя на практике часто пишут в внешнем файле $.ready(function () { .. }), мне это не нравится.
Если ты подключаешь JS файл в конце HTML-файла, то есть недостаток, что до момента загрузки этого файла кнопки и интерактивные элементы не работают. А вот если подключить файл в начале и использовать onclick="doSomething()" то такой проблемы нет. Хотя часто делают так, как ты сделал, но мне это не нравится.
Ты исплоьзуешь Element.closest(), но судя по https://developer.mozilla.org/ru/docs/Web/API/Element/closest это совсем новая технология и в не самых новых браузерах твой код просто не заработает.
> (function(f, w) {
По моему не очень удачные названия переменных.
Насчет перетаскивания и вставки файлов через Ctrl + V - а ты тестировал перетаскивание и копирование из разных источников? (папка, браузеры, офисные программы)? Если нет, советую протестировать, так как они часто передают информацю о файле в разных форматах.
> xhr.onreadystatechange = function() {
> if (xhr.readyState == 4) {
> if(xhr.status == 200) {
> var data = JSON.parse(xhr.responseText)
Вот тут тоже у тебя стена кода без разбиения на функции. Не надо напрямую использовать XMLHttpRequest, он неуклюжий, лучше написать отдельную функцию отправки запроса.
$myObj = new /app/MyNamespace/MyClass();
Или лучше везде прописывать using'и?
Ну не траль плез(9
$result = mysqli_query("SELECT * FROM news");
$data = mysqli_fetch_array($result);
do{
printf('
',$data["date"],$data["text"]);
}
while($date = mysqli_fetch_array($result));
?>
Такая ошибка " mysqli_query() expects at least 2 parameters, 1 given"
Помогите пожалуйста, какой параметр нужно добавить?
HELP HELP HELP!!!
<?php
$result = mysqli_query("SELECT * FROM news");
$data = mysqli_fetch_array($result);
do{
printf('
',$data["date"],$data["text"]);
}
while($date = mysqli_fetch_array($result));
?>
Такая ошибка " mysqli_query() expects at least 2 parameters, 1 given"
Помогите пожалуйста, какой параметр нужно добавить?
>>901544
>mysqli_query() expects at least 2 parameters, 1 given
Первая ссылка в гугле
http://stackoverflow.com/questions/19148407/warning-mysqli-query-expects-at-least-2-parameters-1-given-what
>First lesson in doing programming: If an error message says you're missing something, chances are you're missing that thing.
http://php.net/manual/en/mysqli.query.php
>mixed mysqli_query ( mysqli $link , string $query [, int $resultmode = MYSQLI_STORE_RESULT ] )
Иными словами, ты не сохраняешь нигде свое подключение, но пытаешься его использовать.
У тебя должно быть:
>$connection = mysqli_connect(...);
>$result = mysqli_query($connection, "SELECT * FROM news");
А вообще, проще использовать божественный PDO.
Спасибо вам Мистер Программист
Если вас пропустили - напомните о себе.
>>901544
Могу кое-что добавить в дополнение к написанному аноном выше. Прочитай примеры использования mysqli в мануале и обрати внимание на то, что после почти каждого вызова mysqli-функции стоит if с проверкой на ошибку. Дело в том, что mysqli сама не пишет причины ошибок, и надо ставить дополнительные if (или использовать PDO, который умеет выбрасывать исключения).
Вообще, эта проблема (скрытие информации об ошибках) есть не только в PHP. В C#, если я не путаю, если создать новый тред и в нем выбросить непойманное исключение, то тред молча завершается, а исключение теряется, если специально не проверять его наличие. Это конечно неправильно, всегда должно быть так что по умолчанию ошибка фиксруется в логи и завершает программу, а далее уже разработчик при желании может ее перехватывать.
>>901318
Можно, но с use наверно код будет красивее. А в чем проблема? Ты руками что ли use пишешь? В моем Sublime например есть плагин PHP Companion, который вставляет use при нажатии F5 на имени класса. И в любой нормальной IDE есть такая функция.
Также, неймспейсы по PSR-4 должны начинаться с большой буквы.
>>901073
Если ты пишешь что "нахватался по верхам", то это плохо. Ведь надо уверенно знать основы, чтобы изучать какие-то более сложные, высокоуровневые вещи. Прежде чем изучать фреймворк, надо знать ООП и MVC.
И соответственно, если ты захочешь сделать какой-то свой проект, ну например, чат какой-нибудь, то без базовых знаний будешь делать там элементарные ошибки.
По HTML/CSS я бы предложил решить наши задачи из ОП поста. Если ты хорошо знаешь HTML, ты их решишь за 1-2 дня все. По PHP - решить задачи на ООП (Вектор и кошки-мышки) из учебника из ОП поста, а затем например задачу про список студентов. Я вот не знаю, какой уровень в курсах, которые ты смотрел, и насколько ты их усвоил, а вот в своих задачах я уверен.
Если бы ты успешно решишь эти задачи, значит базовые знания ты освоил и можно браться за что-то сложнее - изучать фреймворки или писать какое-то приложение, которое ты хотел бы сделать. И я тогда бы мог его проверить и например дать какие-то советы.
Насчет "сайта на CMS", CMS обычно проектируются так, чтобы делать на них сайты без программирования, за счет настройки конфигурации в админке, в лучшем случае с правкой HTML шаблонов. Это очень низкий уровень и я не советую как-то на него ориентироваться. Если ты наши более сложные задачи пройдешь, в любой CMS ты разберешься без проблем.
Если вас пропустили - напомните о себе.
>>901544
Могу кое-что добавить в дополнение к написанному аноном выше. Прочитай примеры использования mysqli в мануале и обрати внимание на то, что после почти каждого вызова mysqli-функции стоит if с проверкой на ошибку. Дело в том, что mysqli сама не пишет причины ошибок, и надо ставить дополнительные if (или использовать PDO, который умеет выбрасывать исключения).
Вообще, эта проблема (скрытие информации об ошибках) есть не только в PHP. В C#, если я не путаю, если создать новый тред и в нем выбросить непойманное исключение, то тред молча завершается, а исключение теряется, если специально не проверять его наличие. Это конечно неправильно, всегда должно быть так что по умолчанию ошибка фиксруется в логи и завершает программу, а далее уже разработчик при желании может ее перехватывать.
>>901318
Можно, но с use наверно код будет красивее. А в чем проблема? Ты руками что ли use пишешь? В моем Sublime например есть плагин PHP Companion, который вставляет use при нажатии F5 на имени класса. И в любой нормальной IDE есть такая функция.
Также, неймспейсы по PSR-4 должны начинаться с большой буквы.
>>901073
Если ты пишешь что "нахватался по верхам", то это плохо. Ведь надо уверенно знать основы, чтобы изучать какие-то более сложные, высокоуровневые вещи. Прежде чем изучать фреймворк, надо знать ООП и MVC.
И соответственно, если ты захочешь сделать какой-то свой проект, ну например, чат какой-нибудь, то без базовых знаний будешь делать там элементарные ошибки.
По HTML/CSS я бы предложил решить наши задачи из ОП поста. Если ты хорошо знаешь HTML, ты их решишь за 1-2 дня все. По PHP - решить задачи на ООП (Вектор и кошки-мышки) из учебника из ОП поста, а затем например задачу про список студентов. Я вот не знаю, какой уровень в курсах, которые ты смотрел, и насколько ты их усвоил, а вот в своих задачах я уверен.
Если бы ты успешно решишь эти задачи, значит базовые знания ты освоил и можно браться за что-то сложнее - изучать фреймворки или писать какое-то приложение, которое ты хотел бы сделать. И я тогда бы мог его проверить и например дать какие-то советы.
Насчет "сайта на CMS", CMS обычно проектируются так, чтобы делать на них сайты без программирования, за счет настройки конфигурации в админке, в лучшем случае с правкой HTML шаблонов. Это очень низкий уровень и я не советую как-то на него ориентироваться. Если ты наши более сложные задачи пройдешь, в любой CMS ты разберешься без проблем.
>mysqli_query
Перво наперво - забудь это и юзай PDO, люк. Если тебя пугают оопэшные штуки - учи ООП. Второе уже поясняли - прочитай эрор меседж ты с вероятностью 90% сам пофиксишь проблему.
- задание на верстку https://github.com/someApprentice/maintaskforlayout/ >>899867 | http://arhivach.org/thread/216627/#899867
- файлообменник http://arhivach.org/thread/216627/#899139
- (22 дек) https://github.com/anotherCodeMunkey/fileshare/ http://arhivach.org/thread/216627/#898496
- JS задачи на ДОМ http://arhivach.org/thread/216627/#898484
- задачи с собеседований (угол между стрелками и тд ) http://arhivach.org/thread/216627/#898482
-
Главный контроллер называется Front Controller: http://design-pattern.ru/patterns/front-controller.html
> - После вызова текущего контролера и его метода, вызывает специальный метод главного контроллера getView, в котором создаётся объект класса главного View и уже в нём вызывает функция render с передаваемыми параметрами.
А ты можешь сформулровать, что должны делать контроллеры? Что они получают на вход, что выдают на выходе? В ООП ты должен для каждого класса знать, какая у него задача, что он получает и что может вернуть.
Как я понимаю из твоего описания, у тебя контроллер должен вернуть массив данных для шаблонизатора. Но это выглядит как ограничение. А что, если ты хочешь вернуть страницу с HTTP кодом 404 или 403? Что если ты хочешь вернуть не-HTML ответ (plain text или картинку или JSON)? Удобно ли это сделать в твоей архитектуре?
Сейчас я все чаще вижу такую реализацию контроллера: контроллер получает на вход объект HTTP запроса и отдает на выходе объект HTTP-ответа. Выглядит это так:
$response = $controller->indexAction($request);
или $controller->indexAction($request, $response);
В микрофреймворках контроллер может быть функцией: function (Request $req, Resonse $res).
Иногда Front Controller анализирует названия переменных в аргументах метода. Если есть роут /news/{date} и у метода контроллера есть аргумент с именем $date (newsAction($request, $date)) то в него передается значение date из URL. Для определения имени переменной используется Reflection.
Где взять объекты Request/Response? Раньше в каждом фреймворке были свои объекты, но сейчас группа PSR смогла выработать рекомендацию, и описать интерфейсы для таких классов в PSR-7: https://www.google.ru/search?q=psr-7&newwindow=1&gbv=1&sei=s3dhWKnlJMGqswHRoaKICw
Единый стандарт значит, что функцию, использующую эти объекты, можно будет использовать в любом фреймворке.
Также, есть популярная библиотека Symfony Http Foundation с такими объектами: http://symfony.com/doc/current/components/http_foundation.html (англ) - но она не совместима с PSR-7, потому, я думаю, лучше использовать что-нибудь совеместимое.
Я советую, если ты решишь делать эти объекты, использовать именно интерфейс PSR-7 и не изобретать очередной велосипед. Есть готовые реализации: https://packagist.org/providers/psr/http-message-implementation . Если решишь не делать эти объекты, то тогда можно брать данные напрямую из GET/POST/SERVER и самостоятельно выводить ответ.
Я бы тебе советовал посмотреть микрофреймворки вроде Slim, Silex, может что-то подчерпнешь.
> Думал создать класс Router и переложить в него часть функций
Задача роутера только проанализировать URL и выбрать контролер и метод.
>>900985
Можно запостить код, написать что именно непонятно и попросить подсказку.
>>900926
Только надо понимать, что это библиотека для ассертов (утверждений, которые всегда верны), а не проверки например пользовательских данных из формы.
Главный контроллер называется Front Controller: http://design-pattern.ru/patterns/front-controller.html
> - После вызова текущего контролера и его метода, вызывает специальный метод главного контроллера getView, в котором создаётся объект класса главного View и уже в нём вызывает функция render с передаваемыми параметрами.
А ты можешь сформулровать, что должны делать контроллеры? Что они получают на вход, что выдают на выходе? В ООП ты должен для каждого класса знать, какая у него задача, что он получает и что может вернуть.
Как я понимаю из твоего описания, у тебя контроллер должен вернуть массив данных для шаблонизатора. Но это выглядит как ограничение. А что, если ты хочешь вернуть страницу с HTTP кодом 404 или 403? Что если ты хочешь вернуть не-HTML ответ (plain text или картинку или JSON)? Удобно ли это сделать в твоей архитектуре?
Сейчас я все чаще вижу такую реализацию контроллера: контроллер получает на вход объект HTTP запроса и отдает на выходе объект HTTP-ответа. Выглядит это так:
$response = $controller->indexAction($request);
или $controller->indexAction($request, $response);
В микрофреймворках контроллер может быть функцией: function (Request $req, Resonse $res).
Иногда Front Controller анализирует названия переменных в аргументах метода. Если есть роут /news/{date} и у метода контроллера есть аргумент с именем $date (newsAction($request, $date)) то в него передается значение date из URL. Для определения имени переменной используется Reflection.
Где взять объекты Request/Response? Раньше в каждом фреймворке были свои объекты, но сейчас группа PSR смогла выработать рекомендацию, и описать интерфейсы для таких классов в PSR-7: https://www.google.ru/search?q=psr-7&newwindow=1&gbv=1&sei=s3dhWKnlJMGqswHRoaKICw
Единый стандарт значит, что функцию, использующую эти объекты, можно будет использовать в любом фреймворке.
Также, есть популярная библиотека Symfony Http Foundation с такими объектами: http://symfony.com/doc/current/components/http_foundation.html (англ) - но она не совместима с PSR-7, потому, я думаю, лучше использовать что-нибудь совеместимое.
Я советую, если ты решишь делать эти объекты, использовать именно интерфейс PSR-7 и не изобретать очередной велосипед. Есть готовые реализации: https://packagist.org/providers/psr/http-message-implementation . Если решишь не делать эти объекты, то тогда можно брать данные напрямую из GET/POST/SERVER и самостоятельно выводить ответ.
Я бы тебе советовал посмотреть микрофреймворки вроде Slim, Silex, может что-то подчерпнешь.
> Думал создать класс Router и переложить в него часть функций
Задача роутера только проанализировать URL и выбрать контролер и метод.
>>900985
Можно запостить код, написать что именно непонятно и попросить подсказку.
>>900926
Только надо понимать, что это библиотека для ассертов (утверждений, которые всегда верны), а не проверки например пользовательских данных из формы.
Читать надо было это: http://php.net/manual/ru/install.pecl.php - там установка делается по-разному в разных случаях.
Дело в том, что PHP, как и многие открытые проекты, распространяется в виде архива кода на Си. Процессор, разумеется, выполнять исходный код не способен, потому ты должен "скомпилировать" из исходного кода исполняемый файл (в случае Windows - php.exe), который можно будет запустить. Также, PHP использует сторонние библиотеки и имеет много настроек компиляции (какие возможности включать, какой каталог использовать для определенных целей итд), потому в общем процесс сборки выглядит так:
- скачиваем исходынй код
- устанавливаем исходный код нужных сторонних библиотек
- устанавливаем компилятор Си и утилиты для сборки вроде make
- запускаем скрипт конфигурации, задаем нужные нам настройки, а остальные скрипт выставит по умолчанию
- запускаем скрипт сборки, ждем пока компилируются все нужные файлы
- запускаем скрипт установки, который установит скопилированные файлы из временной папки в нужную
То есть задача проекта PHP - дать исходный код, а его сборка - задача пользователя. Этот подход имеет то преимущество, что пользователь может максимально гибко настраивать параметры сборки под свои нужды (например не включать ненужные ему модули) и даже при желании внести какие-то правки в код.
Это довольно долго и неудобно, потому под популярные ОС добрые люди собирают PHP и позволяют скачать уже скомпилированную версию. Под Windows можно скачать PHP с сайта, под Дебианом - установить через apt-get. Под маком есть программа homebrew, которая сильно упрощает процесс сборки и сама скачивает нужные библиотеки и настраивает конфигураци. А сборку вручную делают те, кто хочет каких-то нестандартных настроек.
Обрати внимание, что бинарные файлы привязаны к конкретной ОС и архитектуре процессора (32 или 64 бита). Нельзя взять бинарник от линукса и запустить на Windows.
Расширения распространяются аналогично - в виде исходных кодов, причем для их сборки нужны еще и часть исходников самого PHP нужной версии. И популярные расширения тоже можно скачать уже скопилированные (для windows это dll файлы, которые кладутся в папку к PHP и прописываются в конфиг php.ini). Более того, под Windows часть расширений идет в комплекте с PHP, надо только включить их загрузку в php.ini.
А если расширение малоизветсное, то придется собирать вручную, то есть идти тяжелым путем.
Теперь объясню фразу
> Для того, чтобы включить поддержку PostgreSQL необходимо скомпилировать PHP с директивой --with-pgsql[=DIR]
Исходный код расширения postgres включен в состав PHP и его не надо скачивать отдельно, достаточно при конфигурации сборки включить одну опцию. Если ты скачал скопилированный PHP, то скорее всего это расширение входит в его состав, и следующее предложение говорит об этом: "Если доступен динамический модуль, то он может быть включен директивой extension в php.ini, либо функцией dl()."
Тебе надо проверить что есть нужная dll (php_pgsql.dll) и прописать ее в php.ini.
Под Дебианом популярные расширения ставятся через apt-get, непопулярные компилируются утилитой pecl (вообще, там установка и сборка намного проще чем под виндой).
> установочная директория - папка, где все файлы, связанные с postgresql лежат, или путь к папке с .exe, который как бе и есть сам сервер?
Имеется в виду директория, где лежат заголовки postgres (заголовки - headers - это специальные исходные коды, которые позволяют исходному коду PHP узнать, как правильно вызывать функции из кода postgres. Если ты хочешь чтобы твой код мог вызывать методы сторонней библиотеки, тебе нужен либо полный исходный код этой библиотеки, либо скомпилированная библиотека + заголовки, описывающие имеющиеся в ней функции).
> Алсо, сейчас хостую демонстрационный сайт через XAMPP. Хватит ли мне его, чтобы взаимодействовать с PostgreSQL, или нужно ставить специально сконфигурированный под него апач?
Да, это вообще не важно, ты можешь даже использовать для разработки встроенный в PHP веб-сервер. Конфигурировать надо не веб-сервер, а PHP.
>>900769
Если давно не использовал, стоит повторить теорию? Есть глава (не очень хорошая) в моем учебнике в ОП посте, есть мануал http://php.net/manual/ru/pcre.pattern.php
>>900525
Смысл есть, но я бы советовал еще глянуть задачи на HTML из ОП поста - чтобы проверить, чему в итоге ты научился.
Читать надо было это: http://php.net/manual/ru/install.pecl.php - там установка делается по-разному в разных случаях.
Дело в том, что PHP, как и многие открытые проекты, распространяется в виде архива кода на Си. Процессор, разумеется, выполнять исходный код не способен, потому ты должен "скомпилировать" из исходного кода исполняемый файл (в случае Windows - php.exe), который можно будет запустить. Также, PHP использует сторонние библиотеки и имеет много настроек компиляции (какие возможности включать, какой каталог использовать для определенных целей итд), потому в общем процесс сборки выглядит так:
- скачиваем исходынй код
- устанавливаем исходный код нужных сторонних библиотек
- устанавливаем компилятор Си и утилиты для сборки вроде make
- запускаем скрипт конфигурации, задаем нужные нам настройки, а остальные скрипт выставит по умолчанию
- запускаем скрипт сборки, ждем пока компилируются все нужные файлы
- запускаем скрипт установки, который установит скопилированные файлы из временной папки в нужную
То есть задача проекта PHP - дать исходный код, а его сборка - задача пользователя. Этот подход имеет то преимущество, что пользователь может максимально гибко настраивать параметры сборки под свои нужды (например не включать ненужные ему модули) и даже при желании внести какие-то правки в код.
Это довольно долго и неудобно, потому под популярные ОС добрые люди собирают PHP и позволяют скачать уже скомпилированную версию. Под Windows можно скачать PHP с сайта, под Дебианом - установить через apt-get. Под маком есть программа homebrew, которая сильно упрощает процесс сборки и сама скачивает нужные библиотеки и настраивает конфигураци. А сборку вручную делают те, кто хочет каких-то нестандартных настроек.
Обрати внимание, что бинарные файлы привязаны к конкретной ОС и архитектуре процессора (32 или 64 бита). Нельзя взять бинарник от линукса и запустить на Windows.
Расширения распространяются аналогично - в виде исходных кодов, причем для их сборки нужны еще и часть исходников самого PHP нужной версии. И популярные расширения тоже можно скачать уже скопилированные (для windows это dll файлы, которые кладутся в папку к PHP и прописываются в конфиг php.ini). Более того, под Windows часть расширений идет в комплекте с PHP, надо только включить их загрузку в php.ini.
А если расширение малоизветсное, то придется собирать вручную, то есть идти тяжелым путем.
Теперь объясню фразу
> Для того, чтобы включить поддержку PostgreSQL необходимо скомпилировать PHP с директивой --with-pgsql[=DIR]
Исходный код расширения postgres включен в состав PHP и его не надо скачивать отдельно, достаточно при конфигурации сборки включить одну опцию. Если ты скачал скопилированный PHP, то скорее всего это расширение входит в его состав, и следующее предложение говорит об этом: "Если доступен динамический модуль, то он может быть включен директивой extension в php.ini, либо функцией dl()."
Тебе надо проверить что есть нужная dll (php_pgsql.dll) и прописать ее в php.ini.
Под Дебианом популярные расширения ставятся через apt-get, непопулярные компилируются утилитой pecl (вообще, там установка и сборка намного проще чем под виндой).
> установочная директория - папка, где все файлы, связанные с postgresql лежат, или путь к папке с .exe, который как бе и есть сам сервер?
Имеется в виду директория, где лежат заголовки postgres (заголовки - headers - это специальные исходные коды, которые позволяют исходному коду PHP узнать, как правильно вызывать функции из кода postgres. Если ты хочешь чтобы твой код мог вызывать методы сторонней библиотеки, тебе нужен либо полный исходный код этой библиотеки, либо скомпилированная библиотека + заголовки, описывающие имеющиеся в ней функции).
> Алсо, сейчас хостую демонстрационный сайт через XAMPP. Хватит ли мне его, чтобы взаимодействовать с PostgreSQL, или нужно ставить специально сконфигурированный под него апач?
Да, это вообще не важно, ты можешь даже использовать для разработки встроенный в PHP веб-сервер. Конфигурировать надо не веб-сервер, а PHP.
>>900769
Если давно не использовал, стоит повторить теорию? Есть глава (не очень хорошая) в моем учебнике в ОП посте, есть мануал http://php.net/manual/ru/pcre.pattern.php
>>900525
Смысл есть, но я бы советовал еще глянуть задачи на HTML из ОП поста - чтобы проверить, чему в итоге ты научился.
Есть разные варианты:
- просто прописать пути в шаблонах
- сделать простой класс ResourceManager, в который в контроллере мы добавляем пути и позже в шаблоне генерируем теги link/script
- пойти хардкорным путем, определять нужные скрипты/стили в шаблоне и как-то их сканировать/анализировать чтобы знать, кому что нужно. Если используется шаблонизатор, можно ввести свои теги вроде {{ require-script 'news.js' }}. Ведь этот скрипт нужен для элементов в шаблоне и логично там и прописывать зависимость от него, где он нужен.
- твой вариант с конфигом
Знание, где какие файлы нужны полезно если ты захочешь сделать пайплайн для склеивания/минификации файлов.
Надо учесть такие вещи:
- иногда надо подключить файл с стороннего домена (вроде google analytics)
- сторонние библиотеки обычно устанавливают копированием папки, и в ней есть свои подпапки вроде js/css
> ИМЯ_ШАБЛОНА(layout), ВИД_ПОДКЛЮЧЕНИЯ (CSS, JS и т.д.),
насчет варианта с отдельным конфигом - надо взвесить все за и против. С одной стороны, информация собрана в одном месте, с другой - как-то все сложно получается и информация отделена от контроллера и шаблона.
> Сам же метод надо будет вызывать в каждом layout, достаточно просто вызывать его не передавая никаких параметров, он уже сам будет решать куда что подключить, при помощи перебора свойств, если например КСС то вверх, если JS то в футер.
Подключение JS в футере имеет недостатки:
- нельзя вызывать JS функции в теле страницы из тега script
- пока футер не загрузится, все JS-кнопки не работают, так как не определена прописанная в их обработчике функция (или даже обработчик еще не повешен)
Ты наверно где-то увидел, что подключают JS в футере, и бездумно решил это скопировать. Зря. Надо проанализировать как преимущества, так и недостатки такого подхода. А ведь 99% просто копируют то, что делают популярные разработчики, а например мое мнение, которое им противоречит, почему-то никого не интересует. Такая вот у нас среда, вредные идеи хорошо распространяются среди малограмотных масс.
Мне например нравится когда JS файлы содержат только функции или классы и ничего не делают сами, пока мы не вызовем какую-то функцию. Но фронтендщики все как один пишут в них код вроде
$(document).ready(function() { инициализация })
А в чем проблема? А в том, что по мере развития сайта никто не удаляет старый код инициализации, и он только растет, более того, он запускается на каждой странице, даже если нужен только на одной. Я такое видел много раз. Этот подход с document.ready годится для сайта из нескольких страниц, а не для блоьшого развивающегося портала, но глупые верстальщитки везде как один копируют этот подход.
По моему это все пошло с моды на "unobtrusive javascript", описанный много лет назад в какой-то статье вроде такой:
- (англ) http://alistapart.com/article/behavioralseparation
- https://habrahabr.ru/post/25991/
- https://en.wikipedia.org/wiki/Unobtrusive_JavaScript
Сами-то идеи хорошие, но верстальщики уяснили из них ровно 2 вещи:
- активные элементы надо помечать классами
- в JS коде надо написать $(document).ready, ищущий элементы с этими классами и вешающий на них события.
А то, что на большом сайте этих ready будут десятки и они будут выполняться на каждой загрузке страницы, никого не беспокоит. Или например то, что оригинальная идея предполагала, что активные элементы могут работать и без яваскрипта, а яваскрипт лишь улучшает их.
Хуже того, иногда эти скрипты по document.ready меняют что-то в верстке, и верстка прыгает по этому событию. Это вообще ужасно.
То есть я хочу сказать, что надо:
- учитывать что на больших сайтах требуются какие-то принципы для организации кода
- надо думать, что будет происходить до инициализации JS кода и с какого момента нам доступны те или иные функции
Вот тут например человек тоже подозревает, что не все так хорошо: https://www.codeproject.com/articles/410948/how-jquery-and-unobtrusive-javascript-can-be-poiso
Есть разные варианты:
- просто прописать пути в шаблонах
- сделать простой класс ResourceManager, в который в контроллере мы добавляем пути и позже в шаблоне генерируем теги link/script
- пойти хардкорным путем, определять нужные скрипты/стили в шаблоне и как-то их сканировать/анализировать чтобы знать, кому что нужно. Если используется шаблонизатор, можно ввести свои теги вроде {{ require-script 'news.js' }}. Ведь этот скрипт нужен для элементов в шаблоне и логично там и прописывать зависимость от него, где он нужен.
- твой вариант с конфигом
Знание, где какие файлы нужны полезно если ты захочешь сделать пайплайн для склеивания/минификации файлов.
Надо учесть такие вещи:
- иногда надо подключить файл с стороннего домена (вроде google analytics)
- сторонние библиотеки обычно устанавливают копированием папки, и в ней есть свои подпапки вроде js/css
> ИМЯ_ШАБЛОНА(layout), ВИД_ПОДКЛЮЧЕНИЯ (CSS, JS и т.д.),
насчет варианта с отдельным конфигом - надо взвесить все за и против. С одной стороны, информация собрана в одном месте, с другой - как-то все сложно получается и информация отделена от контроллера и шаблона.
> Сам же метод надо будет вызывать в каждом layout, достаточно просто вызывать его не передавая никаких параметров, он уже сам будет решать куда что подключить, при помощи перебора свойств, если например КСС то вверх, если JS то в футер.
Подключение JS в футере имеет недостатки:
- нельзя вызывать JS функции в теле страницы из тега script
- пока футер не загрузится, все JS-кнопки не работают, так как не определена прописанная в их обработчике функция (или даже обработчик еще не повешен)
Ты наверно где-то увидел, что подключают JS в футере, и бездумно решил это скопировать. Зря. Надо проанализировать как преимущества, так и недостатки такого подхода. А ведь 99% просто копируют то, что делают популярные разработчики, а например мое мнение, которое им противоречит, почему-то никого не интересует. Такая вот у нас среда, вредные идеи хорошо распространяются среди малограмотных масс.
Мне например нравится когда JS файлы содержат только функции или классы и ничего не делают сами, пока мы не вызовем какую-то функцию. Но фронтендщики все как один пишут в них код вроде
$(document).ready(function() { инициализация })
А в чем проблема? А в том, что по мере развития сайта никто не удаляет старый код инициализации, и он только растет, более того, он запускается на каждой странице, даже если нужен только на одной. Я такое видел много раз. Этот подход с document.ready годится для сайта из нескольких страниц, а не для блоьшого развивающегося портала, но глупые верстальщитки везде как один копируют этот подход.
По моему это все пошло с моды на "unobtrusive javascript", описанный много лет назад в какой-то статье вроде такой:
- (англ) http://alistapart.com/article/behavioralseparation
- https://habrahabr.ru/post/25991/
- https://en.wikipedia.org/wiki/Unobtrusive_JavaScript
Сами-то идеи хорошие, но верстальщики уяснили из них ровно 2 вещи:
- активные элементы надо помечать классами
- в JS коде надо написать $(document).ready, ищущий элементы с этими классами и вешающий на них события.
А то, что на большом сайте этих ready будут десятки и они будут выполняться на каждой загрузке страницы, никого не беспокоит. Или например то, что оригинальная идея предполагала, что активные элементы могут работать и без яваскрипта, а яваскрипт лишь улучшает их.
Хуже того, иногда эти скрипты по document.ready меняют что-то в верстке, и верстка прыгает по этому событию. Это вообще ужасно.
То есть я хочу сказать, что надо:
- учитывать что на больших сайтах требуются какие-то принципы для организации кода
- надо думать, что будет происходить до инициализации JS кода и с какого момента нам доступны те или иные функции
Вот тут например человек тоже подозревает, что не все так хорошо: https://www.codeproject.com/articles/410948/how-jquery-and-unobtrusive-javascript-can-be-poiso
Они тебе с вероянтностью 99% и не нужны. Паттерны же используют не просто так, а дял какой-то цели.
Например фабрику для таких целей:
- если есть сторонняя библиоека, которую мы не можем менять, она создает какие-то объекты и мы бы хотели повлиять на процесс их создания - автор выносит его в фабрику, и теперь мы можем написать свою фабрику и передать в эту библиотеку
- если нам надо создавать однотипные объекты на основе какого-нибудь конфига. Ну например у нас есть XML-файл с описанием формы и полей в ней, мы пропускаем его через фабрику и получаем соответствующие элементам формы объекты
Ну то есть не очень часто встречающиеся задачи.
У меня пока много других тем, которые надо осветить, и эта не самая важная.
У меня есть уроки по DI и по паттернам работы с БД:
- https://github.com/codedokode/pasta/blob/master/arch/di.md
- https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
Есть книга Фаулера, тут отрывки из нее: http://design-pattern.ru/
Есть сайт, дающий код реализации паттернов, но особо не объясняющий когда они нужны: http://designpatternsphp.readthedocs.io/ru/latest/README.html
Могу пока посоветовать изучить Симфони и ее компоненты (например, Формы, Доктрину), там многие паттерны использованы, и можно попробовать подумать, почему сделано именно так, а не иначе, как бы эту задачу решал ты.
Если ты не понимаешь Симфони, то за паттерны браться рановато. Нужен определенный опыт.
Если тебе это нужно для собеседования (что довольно глупо спрашивать, на мой взгляд, если в компании не используют фреймворк уровня Симфони), в интернете есть полно статей, выучи какой-нибудь синглтон, если собеседующий сам не знает паттерны (скорее всего так и есть), то он не сможет тебя завалить. Я бы конечно, если бы мне начали рассказывать про паттерны, спросил где их используют, какие преимущества, а в конце спросил бы, а не проще ли это сделать без паттерна, по-простому? И какой тогда смысл его использовать?
По алгоритмам гугли "структуры данных и алогритмы". Есть книги, видеолекции,вот например: http://aliev.me/runestone/
В алгоритмах самое важное это уметь оценивать их сложность по времени и по памяти, (я тут немного написал: http://arhivach.org/thread/216627/#898481 ) . При изучении структур данных желательно иметь представление о том как устроена память компьютера (это набор пронумерованных 1-байтовых ячеек) и исходя из этого ты может быть например поймешь сильные и слабые стороны вектора и связанного списка. Если не поймешь - спрашивай.
Изучи векторы, связные списки, очереди, стеки, деревья, графы, хеш-таблицы (словари) для начала.
Самая большая (гигантская) книга по алгоритмам - это Кнут, "Искусство программирования". Ее тебе изучать лет 10 наверно придется. Там только про одни генераторы случайных чисел огромная глава.
Они тебе с вероянтностью 99% и не нужны. Паттерны же используют не просто так, а дял какой-то цели.
Например фабрику для таких целей:
- если есть сторонняя библиоека, которую мы не можем менять, она создает какие-то объекты и мы бы хотели повлиять на процесс их создания - автор выносит его в фабрику, и теперь мы можем написать свою фабрику и передать в эту библиотеку
- если нам надо создавать однотипные объекты на основе какого-нибудь конфига. Ну например у нас есть XML-файл с описанием формы и полей в ней, мы пропускаем его через фабрику и получаем соответствующие элементам формы объекты
Ну то есть не очень часто встречающиеся задачи.
У меня пока много других тем, которые надо осветить, и эта не самая важная.
У меня есть уроки по DI и по паттернам работы с БД:
- https://github.com/codedokode/pasta/blob/master/arch/di.md
- https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
Есть книга Фаулера, тут отрывки из нее: http://design-pattern.ru/
Есть сайт, дающий код реализации паттернов, но особо не объясняющий когда они нужны: http://designpatternsphp.readthedocs.io/ru/latest/README.html
Могу пока посоветовать изучить Симфони и ее компоненты (например, Формы, Доктрину), там многие паттерны использованы, и можно попробовать подумать, почему сделано именно так, а не иначе, как бы эту задачу решал ты.
Если ты не понимаешь Симфони, то за паттерны браться рановато. Нужен определенный опыт.
Если тебе это нужно для собеседования (что довольно глупо спрашивать, на мой взгляд, если в компании не используют фреймворк уровня Симфони), в интернете есть полно статей, выучи какой-нибудь синглтон, если собеседующий сам не знает паттерны (скорее всего так и есть), то он не сможет тебя завалить. Я бы конечно, если бы мне начали рассказывать про паттерны, спросил где их используют, какие преимущества, а в конце спросил бы, а не проще ли это сделать без паттерна, по-простому? И какой тогда смысл его использовать?
По алгоритмам гугли "структуры данных и алогритмы". Есть книги, видеолекции,вот например: http://aliev.me/runestone/
В алгоритмах самое важное это уметь оценивать их сложность по времени и по памяти, (я тут немного написал: http://arhivach.org/thread/216627/#898481 ) . При изучении структур данных желательно иметь представление о том как устроена память компьютера (это набор пронумерованных 1-байтовых ячеек) и исходя из этого ты может быть например поймешь сильные и слабые стороны вектора и связанного списка. Если не поймешь - спрашивай.
Изучи векторы, связные списки, очереди, стеки, деревья, графы, хеш-таблицы (словари) для начала.
Самая большая (гигантская) книга по алгоритмам - это Кнут, "Искусство программирования". Ее тебе изучать лет 10 наверно придется. Там только про одни генераторы случайных чисел огромная глава.
Что для тебя эффективность? Стоимость труда? Потребление памяти? Поддержка Windows? Наличие популярных CMS?
>>900050
> echo "Не удалось создать сокет, причина: " . socket_strerror(socket_last_error()) . "\n";
> die;
Прочти уже урок про исключения, если знаешь ООП: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
У тебя есть проблема в том, что ошибка не передается из socketWrite выше. Ты пишешь в консоль, закрываешь сокет, но вызывающий код об этом не знает и пытается дальше использовать этот закрытый сокет. Неправильно.
> $buf = trim($buf);
> ...
> if (preg_match('/\n/', $buf)) {
trim удаляет \n с краев и preg_match сработает только если он был в середине пакета данных. Странная логика, не находишь?
Я просил сделать следующее: функцию-генератор, которая при каждой иерации возвращает 1 строку из пришедших данных. Так как данные приходят пакетами незвестного размера, то может быть 1 пакет с несколькими строками, может быть несколько пакетов с 1 строкой. И при таком подходе нам нужен внутренний буфер для накопления данных. И удобнее всего это делать генератором. Вот алгоритм:
буфер = '';
вечный цикл {
- читаем пакет данных;
- проверяем ошибки;
- проверяем, не закрыл ли отправитель сокет, если да, то выплевываем остаток буфера, если там что-то есть и выходим;
- кладем пакет в буфер
- пока буфер содержит символ \n, цикл {
- вырезаем первую строку из буфера вместе с символом \n
- выплевываем ее
}
}
Вот примерно такая логика. Как видишь, без генератора обычной функцией реализовать такую логику сложно (разве что использовать глобальную переменную-буфер, но код будет гораздо запутаннее - можешь проверить и попробовать написать для сравнения, и из-за глобальной переменной нельзя работать более чем с 1 сокетом одновременно).
Генератор хорош тем, что позволяет как бы выполнять 2 цикла (цикл приема данных из сокета внутри функции и цикл генерации ответа на строку), переключаясь между ними с помощью yield.
Твой генератор работает как-то криво и через раз.
> там в следующей задаче написано про многопоточность, я что-то не нашел ничего внятного про многопоточность на пхп, только открытие нескольких процессов вручную (зачем?).
Многопоточность здесь значит возможность обрабатывать несколько потоков данных одновременно. Твой echo сервер например в один момент времени работает только с одним потоком: либо ждет пока пакет отправится, либо пишет в консоль, либо ждет пока пакет придет, либо ждет нового соединения. Это сильно нас ограничивает. Для чата даже из 2 человек нужна многопоточность, так сервер должен ждать входящее сообщение на 2 сокетах сразу. И отправлять 2 людям, не зависая если кто-то из них не принимает пакеты.
Реализовать многопоточность можно по-разному, вот мой урок: https://gist.github.com/codedokode/ffd520440a970c07c1c6
В твоем случае я предлагаю сделать асинхронную однопоточную архитектуру, но пока без системы событий, вручную. Идея такая:
- делаем операции на сокетах неблокирующими
- используем socket_select, чтобы ожидать событий на нескольких сокетах сразу
- как только произойдет какое-то событие (пришли новые данные, сокет готов к передаче новой порции данных) - обрабатываем это событие и снова вызваем socket_select в ожидании следюущего.
То есть освой работу с неблокирующими сокетами и socket_select.
Имей в виду что socket_select неэффективен при большом числе сокетов (десятки, сотни). Для них нужно что-то вроде libevent. Но ты пока об этом не беспокойся, у тебя их так много не будет.
В клиенте-загрузчике тут while (false !== ($out = socket_read($socket, 2048))) нет проверки, почему вернулся false - это ошибка или просто соединение закрыто сервером.
> $response = preg_split('/\r\n\r\n/', $response, 2)[1];
Тут ты предполагаешь что в ответе сервера есть эта последовательнсоть. Но что если он выдал ошибочный ответ? Также, советую сделать \r необязательным, чтобы работала и просто \n\n - так надежнее.
>>899904
> в её конструкторе заполняют pdo через синглетон, который находится в специальном отдельном классе Db.
Это плохая идея, почитай урок про DI: https://github.com/codedokode/pasta/blob/master/arch/di.md
> Далее в основной моделе описывают методы для работы с БД, типо findAll, findOne
Это просто класс для работы с базой данных. На практике тебе не нужны методы вроде findAll, а нужны getLatestNews, getNewsByYear и так далее.
> Казалось, всё бы нормально, но возникает проблема, при реальном использовании этой конструкции, в контроллере создаётся объекта класса модели, чтобы получить возможность использовать методы для работы в базе данных, в итоге и основной код, вроде манипуляции с различными данными из базы данных будет осуществляться в контроллере а не в моделе.
Либо сделать в "модели" дополнительные методы с бизнем-логикой, либо сделать слой классов-сервисов с бизнес-логикой, которые для работы с БД используют класс работы с БД.
Вообще, ты плохо понял MVC если думаешь:
- что модель это один класс или набор однотипных классов
- что назначение модели лишь работать с БД
Вот рассмотрим регистрацию пользователя. Что тут задача контроллера, а что модели? Задача контроллера это:
- принять данные из запроса
- попросить модель проверить их
- попросить модель зарегистрировать нового пользователя
- залогинить нового плоьзователя
Задача модели:
- проверка данных нового пользователя
- создание нужных записей в БД, их может быть больше одной если например данные хранятся в нескольких таблицах
- дополнительные задачи, вроде отправки письма подтверждения email
Потому модель может состоять из разных классов-сервисов.
Проверить, насколько верно у тебя реализовано разделение логики можно, попробовав написать альтернативный контроллер. Например, скрипт регистрации пользователя, который запускается из командной строки и должен либо вывести id нового пользователя либо сообщение об ошибке в данных. Или можно попробовать заменить хранение данных в базе на хранение в файле. Придется ли что-то менять, кроме модели?
Вообще, ты решал нашу задачу про студентов? Там многие такие вопросы разбираются.
> Создать в модели дополнительные свойства, в которых хранить определённые результаты манипуляция и получить к ним доступ из контроллера?
Не надо. Функция должна возвращать результат через return, а не обходным способом.
> class Main extends Application {
Странные названия. Main - это контроллер? А что такое Application?
> //передаём переменные в вид
> $this->set("posts", "title_post");
Странно, ты передаешь данные в вид, но метод вызываешь на контроллере. Зачем тут посредник и почему нельзя передать данные напрямую?
> Меня в общем смущает, что в контроллере будет много кода, а в моделе его практически не будет вообще
Это назывеатся "толстый контроллер" и это плохо, так как код в контроллере нельзя повторно использовать из других мест. Логика работы приложения должна быть в модели.
Ты кстати этот урок читал? https://github.com/codedokode/pasta/blob/master/arch/mvc.md
Там есть пример кода.
Что для тебя эффективность? Стоимость труда? Потребление памяти? Поддержка Windows? Наличие популярных CMS?
>>900050
> echo "Не удалось создать сокет, причина: " . socket_strerror(socket_last_error()) . "\n";
> die;
Прочти уже урок про исключения, если знаешь ООП: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
У тебя есть проблема в том, что ошибка не передается из socketWrite выше. Ты пишешь в консоль, закрываешь сокет, но вызывающий код об этом не знает и пытается дальше использовать этот закрытый сокет. Неправильно.
> $buf = trim($buf);
> ...
> if (preg_match('/\n/', $buf)) {
trim удаляет \n с краев и preg_match сработает только если он был в середине пакета данных. Странная логика, не находишь?
Я просил сделать следующее: функцию-генератор, которая при каждой иерации возвращает 1 строку из пришедших данных. Так как данные приходят пакетами незвестного размера, то может быть 1 пакет с несколькими строками, может быть несколько пакетов с 1 строкой. И при таком подходе нам нужен внутренний буфер для накопления данных. И удобнее всего это делать генератором. Вот алгоритм:
буфер = '';
вечный цикл {
- читаем пакет данных;
- проверяем ошибки;
- проверяем, не закрыл ли отправитель сокет, если да, то выплевываем остаток буфера, если там что-то есть и выходим;
- кладем пакет в буфер
- пока буфер содержит символ \n, цикл {
- вырезаем первую строку из буфера вместе с символом \n
- выплевываем ее
}
}
Вот примерно такая логика. Как видишь, без генератора обычной функцией реализовать такую логику сложно (разве что использовать глобальную переменную-буфер, но код будет гораздо запутаннее - можешь проверить и попробовать написать для сравнения, и из-за глобальной переменной нельзя работать более чем с 1 сокетом одновременно).
Генератор хорош тем, что позволяет как бы выполнять 2 цикла (цикл приема данных из сокета внутри функции и цикл генерации ответа на строку), переключаясь между ними с помощью yield.
Твой генератор работает как-то криво и через раз.
> там в следующей задаче написано про многопоточность, я что-то не нашел ничего внятного про многопоточность на пхп, только открытие нескольких процессов вручную (зачем?).
Многопоточность здесь значит возможность обрабатывать несколько потоков данных одновременно. Твой echo сервер например в один момент времени работает только с одним потоком: либо ждет пока пакет отправится, либо пишет в консоль, либо ждет пока пакет придет, либо ждет нового соединения. Это сильно нас ограничивает. Для чата даже из 2 человек нужна многопоточность, так сервер должен ждать входящее сообщение на 2 сокетах сразу. И отправлять 2 людям, не зависая если кто-то из них не принимает пакеты.
Реализовать многопоточность можно по-разному, вот мой урок: https://gist.github.com/codedokode/ffd520440a970c07c1c6
В твоем случае я предлагаю сделать асинхронную однопоточную архитектуру, но пока без системы событий, вручную. Идея такая:
- делаем операции на сокетах неблокирующими
- используем socket_select, чтобы ожидать событий на нескольких сокетах сразу
- как только произойдет какое-то событие (пришли новые данные, сокет готов к передаче новой порции данных) - обрабатываем это событие и снова вызваем socket_select в ожидании следюущего.
То есть освой работу с неблокирующими сокетами и socket_select.
Имей в виду что socket_select неэффективен при большом числе сокетов (десятки, сотни). Для них нужно что-то вроде libevent. Но ты пока об этом не беспокойся, у тебя их так много не будет.
В клиенте-загрузчике тут while (false !== ($out = socket_read($socket, 2048))) нет проверки, почему вернулся false - это ошибка или просто соединение закрыто сервером.
> $response = preg_split('/\r\n\r\n/', $response, 2)[1];
Тут ты предполагаешь что в ответе сервера есть эта последовательнсоть. Но что если он выдал ошибочный ответ? Также, советую сделать \r необязательным, чтобы работала и просто \n\n - так надежнее.
>>899904
> в её конструкторе заполняют pdo через синглетон, который находится в специальном отдельном классе Db.
Это плохая идея, почитай урок про DI: https://github.com/codedokode/pasta/blob/master/arch/di.md
> Далее в основной моделе описывают методы для работы с БД, типо findAll, findOne
Это просто класс для работы с базой данных. На практике тебе не нужны методы вроде findAll, а нужны getLatestNews, getNewsByYear и так далее.
> Казалось, всё бы нормально, но возникает проблема, при реальном использовании этой конструкции, в контроллере создаётся объекта класса модели, чтобы получить возможность использовать методы для работы в базе данных, в итоге и основной код, вроде манипуляции с различными данными из базы данных будет осуществляться в контроллере а не в моделе.
Либо сделать в "модели" дополнительные методы с бизнем-логикой, либо сделать слой классов-сервисов с бизнес-логикой, которые для работы с БД используют класс работы с БД.
Вообще, ты плохо понял MVC если думаешь:
- что модель это один класс или набор однотипных классов
- что назначение модели лишь работать с БД
Вот рассмотрим регистрацию пользователя. Что тут задача контроллера, а что модели? Задача контроллера это:
- принять данные из запроса
- попросить модель проверить их
- попросить модель зарегистрировать нового пользователя
- залогинить нового плоьзователя
Задача модели:
- проверка данных нового пользователя
- создание нужных записей в БД, их может быть больше одной если например данные хранятся в нескольких таблицах
- дополнительные задачи, вроде отправки письма подтверждения email
Потому модель может состоять из разных классов-сервисов.
Проверить, насколько верно у тебя реализовано разделение логики можно, попробовав написать альтернативный контроллер. Например, скрипт регистрации пользователя, который запускается из командной строки и должен либо вывести id нового пользователя либо сообщение об ошибке в данных. Или можно попробовать заменить хранение данных в базе на хранение в файле. Придется ли что-то менять, кроме модели?
Вообще, ты решал нашу задачу про студентов? Там многие такие вопросы разбираются.
> Создать в модели дополнительные свойства, в которых хранить определённые результаты манипуляция и получить к ним доступ из контроллера?
Не надо. Функция должна возвращать результат через return, а не обходным способом.
> class Main extends Application {
Странные названия. Main - это контроллер? А что такое Application?
> //передаём переменные в вид
> $this->set("posts", "title_post");
Странно, ты передаешь данные в вид, но метод вызываешь на контроллере. Зачем тут посредник и почему нельзя передать данные напрямую?
> Меня в общем смущает, что в контроллере будет много кода, а в моделе его практически не будет вообще
Это назывеатся "толстый контроллер" и это плохо, так как код в контроллере нельзя повторно использовать из других мест. Логика работы приложения должна быть в модели.
Ты кстати этот урок читал? https://github.com/codedokode/pasta/blob/master/arch/mvc.md
Там есть пример кода.
> В итоге у меня такая проблема, например, для пути нужен main, а у меня в свойствах содержится MainController, будет правильно обрезать приставку Controller специальной функцией
Проще хранить без приставки. Но вообще, в современных фреймворках я вижу тенденцию отказа от атоматического выбора имени в пользу явного вызова рендеринга, вроде такого:
$this->view->render('news/list.twig', ['news' => $news, 'x' => $x]);
или
return $this->render('...');
Во втором случае создается объект HTTP Response в тело которого помещается отрендеренный шаблон.
Преимущество в том, что видно какой шаблон рендерится и что ему передается.
Кстати, по возможности надо избегать сборки имен классов и функций по частям, так как такие имена не найти поиском.
Окей, посмотрю Фаулера. Алсо вспомнил про синглтон - я когда читал учил ООП понял как его делать, но не очень понял зачем он вообще нужен. Часто пишут, мол для соединения БД, типа ссылку на один и тот же объект возвращать, но немного обосрался когда это делал, в плане того что с ним получается несколько запутаней, да и опять же прочитал, что сейчас уже драйвер БД сам закрывает ненужные соединения, ну или вроде того. И вообще везде срачи на форумах в контексте использования синглтона.
>Изучи векторы, связные списки, очереди, стеки, деревья, графы, хеш-таблицы (словари) для начала.
Вот этого я пиздецки боюсь, я же гуманитарий по образованию и матана дальше квадратного корня не знаю. Это сильно надо вкатывальщику?
Про недостатки использования синглтонов написано в моем уроке про DI https://github.com/codedokode/pasta/blob/master/arch/di.md
Матан там не нужен, но хорошо бы понять как устроена память и спросить себя, а как бы ты например реализовал хранение массивов, добавление в них элементов, как бы ты хранил деревья и тд. Я могу подсказать, если что-то будет непонятно.
Я как раз сейчас про ООП смотрю.
Лучше чем на js но -> немного раздражает с оператором .
Окей как закончу курсы начну решать про список студентов.
А курсы от бауманки Специалист с бабочки скачал
А вообще меня маман уже взашей гонит работать так что хочется упор на цмски сделать, а тут диплом на носу и тему можно выбрать веб-сайт.
Так что хочется пока что на хлеб заработать себе на мамке кредиты за учебу ещё , а не в элиту кодинга идти со сложными веб приложениями.
Проблема в том, что все свойства модели будут заполняться именно в контроллере, который будет с ней работать. Как сделать, чтобы они заполнялись в самой модели я не знаю.
>синглтон
Помню, как дали тесовое задание написать синглтон, а мне было лень и я просто отправил им синглтон из документации ПХП, они сказали что он не верен.
И я вновь хотел поговорить про альтернативы PHPStorm.
Atom мне казался самой здравой, но он тормозит, вообще всё тормозит, кроме SublimeText, но его очень долго допиливать, а некоторые пакеты платные.
А мне хотелось бы: просмотр БД, гит, быструю навигацию и подсказки по классам, интеграцию с таскменеджерами, удалённые подключения, синхронизацию, подсветку разного синтаксиса в одной вкладке.
Ну и скорость работы по быстрее, т.к. шторм притормаживает у меня, но всё равно быстрее чем этом.
Есть ли варианты, говорят что эклипс и нетбинс ещё хуже?
Как с помощью php эмулировать сабмит?
Вот я настраиваю платежную систему. Она постом принимает данные. То есть как бы ждет что пользователь введет на моем сайте в форму какие-то данные, и нажмет кнопочку.
Но у меня на сайте уже есть несколько платежных систем, которые работают немного не так. И получается что при нажатии на кнопку, пользователь попадет лишь на еще 1 окно с кнопкой. Надеюсь все поняли это тупое пояснение.
В общем мне нужно скипнуть процесс нажатия пользователм второй раз кнопки "оплатить" так как все данные мне и так известны, и пользователю ничего вводить не нужно.
Нужна такая схема:
1. Пользователь сначала в общей форме на моем сайте вводит например сумму, выбирает в селекте платежную систему и жмет "оплатить"
2. Я на ходу скриптом формирую массив, и высылаю его постом на сайт платежной системы
3. И теперь то с чем у меня затуп, а именно как при этом еще и пользователя вместе с этим запросом туда отправить? Как будто это он засабмитил из формы данные???
Кажется, так нельзя нормально сделать.
Но есть разные извращения.
http://stackoverflow.com/questions/32387631/is-it-possible-to-send-post-data-with-a-php-redirect
Думаю тебе стоит получше изучить API платежных систем, думаю они возвращают нужный тебе юрл или типо того.
Да я вот тоже уже пытался гуглить, и на ум приходит только паршивый редирект с помощью js, то есть я всё таки оставляю прослойку в виде формы, в которой сам заполняю все поля, и тут же эту форму отправляю с помощью js. Юзер в теории ничего не заметит. Но если у него какой-то там браузер с отключенным яваскриптом то НАЖМЕТ ЕЩЕ 1 КНОПОЧКУ, что уж тут поделать :(
вот это: http://stackoverflow.com/questions/5576619/php-redirect-with-post-data
вроде работает
"тест 3"
"тест 2"
"тест 1"
Вот сам код
<?php
$dsn = "mysql:host=$host;dbname=$db;";
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$pdo = new PDO($dsn, $user, $password, $opt)
?>
<?php
$stmt = $pdo->query('SELECT * FROM news');
while ($row = $stmt->fetch())
{
printf('
<h2><img width="191" height="127" class="igm" src="%s"><p class="data">%s<p class="text">     %s</p>
<a href="%s"><img class="ss" src="images/ss.png"></a></h2>
',$row["photo"],$row["date"],$row["text"],$row["link"]);
}
?>
"тест 3"
"тест 2"
"тест 1"
Вот сам код
<?php
$dsn = "mysql:host=$host;dbname=$db;";
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$pdo = new PDO($dsn, $user, $password, $opt)
?>
<?php
$stmt = $pdo->query('SELECT * FROM news');
while ($row = $stmt->fetch())
{
printf('
<h2><img width="191" height="127" class="igm" src="%s"><p class="data">%s<p class="text">     %s</p>
<a href="%s"><img class="ss" src="images/ss.png"></a></h2>
',$row["photo"],$row["date"],$row["text"],$row["link"]);
}
?>
Ну за тебя нужно код написать или просто подсказку достаточно? Сделай вместо
>while ($row = $stmt->fetch())
что-то типа:
$newsArray = $stmnt->fetchAll();
далее массив переверни и его уже
foeache'm выводи.
Другой способ более элегантный наверное, это еще на этапе селекта задавать сортировку
с помощью 'SELECT * FROM news ORDER BY id DESC'
http://www.w3schools.com/sql/sql_orderby.asp
Создай пост-форму на стороне клиента и сделай .submit()
Да какой я программист, макака на хуевой работе, которая говнокод пишет в духе того что я тебе выше предложил с сортировкой массива вместо правильного селекта, PDO я вообще ниразу не щупал даже в жизни. ООП не знаю толком и те кто решил правильно задачу Оп-а со студентами и с обменником имеют за месяц больше скиллов уже чем я за год, такие дела. Но спасибо на добром слове.
Еще дам совет, ты можешь попробовать генерировать ссылки у себя же на странице с гет запросом к своему же скрипту
site/news.php?sort=DESK
и потом собираешь у себя в скрипте этот параметр
$sorting = $_GET['sort'];
$query = "SELECT * FROM news ORDER BY id {$sorting}";
>с сортировкой массива вместо правильного селекта
Я тут мимокрок, но вот что это значит? Это как и зачем? Тебя заставляют такое писать что ли?
Да в принципе зачем что то менять, если это работает. Но я сейчас пытаюсь сделать регистрацию и чет не получается.
Хочу взять из таблицы login и сравнить его с логином из <input>
Делаю это так:
( для начала хотя бы его вывести через echo)
$loginbd = $pdo->query('SELECT login FROM users');
echo($loginbd);
На что мне пишет ошибку
"Catchable fatal error: Object of class PDOStatement could not be converted to string in /home/u720408973/public_html/register.php on line 14"
Понимаю, что скорее всего не так обращаюсь к таблице, но в интернете не нашел ПОДРОБНЫХ гайдов по этой теме.
Что не так с кодом?
>$loginbd = $pdo->query('SELECT login FROM users');
>echo($loginbd);
Так ты же делаешь запрос, но не записываешь ответа. Правильней будет как-то так:
$sql = 'SELECT login FROM users';
$sth = $this->dbh->prepare($sql);
$sth->execute();
$sth->fetchAll(\PDO::FETCH_ASSOC);
SQL запрос выноси в отдельную переменную, так легче будет проверять правильно ли ты его написал. Короче глянь урок у ОПа, там где-то было про PDO написано.
Алсо, опять же читай что оно тебе там в ошибках выдает.
>"Catchable fatal error: Object of class PDOStatement could not be converted to string in /home/u720408973/public_html/register.php on line 14"
>Object of class PDOStatement could not be converted to string
А говорит он тебе, мол ты пытаешься обратиться к объекту как к строке, но это же не строка и вообще так низзя(на самом деле такое можно замутить, но про магию тебе еще рано знать). Т.е. в переменное у тебя все еще объект.
Немного запорол:
$sql = 'SELECT login FROM users';
$sth = $logindb->prepare($sql);
$sth->execute();
$sth->fetchAll(\PDO::FETCH_ASSOC);
Что непонятно - гугли методы. Фетч ассоц это опция что бы ассоциативный массив тебе вернулся, если что.
Как такое реализовать?
Роутинг это называется.
Это не новая страница, просто такой url, почитай о Router-e.
Всем привет. Начал писать фреймворк с нуля, в целях обучения, и с литературой по этой неме как то в сети не очень. Колеги посоветовали смотреть как работают другие фреймворки, но хотелось бы более подробного описания, так как на разных фреймворках индивидуальный подход, и не совсем понятно почему разработчики выбрали именно такой путь. Если кто знает хорошую книгу (или хотя бы статейку), буду очень благодарен.
>>901690
> Просмотрел
> Прочитал
> Досматриваю
> про ООП смотрю.
Программировать ты так не научишься.
>>902289
Велосипедистов полон тред. Книги/статейки тебе не хватит, нужно разбираться с ООП, разделением ответственности, снижением связанности, тестируемостью кода и главное - писать код. Тогда MVC и паттерны не будут казаться чем-то притянутым за уши, ведь тот же MVC - это одна из реализаций принципа разделения ответственности. Если ты будешь понимать, что и зачем отделять, то разобраться с новым (адекватным) фреймворком не составит труда.
Я могу посоветовать начать с пасты ОПа про ООП ну и решать задачи оттуда.
>>902151
> $sorting = $_GET['sort'];
> $query = "SELECT * FROM news ORDER BY id {$sorting}";
Здесь SQL-инъекция.
Пикрил - недавняя директива от начальника.
Я образно конечно сказал, но суть в том, когда нужно сложный селект сделать с подзапросами и всякими агрегациями прямо в мускуле, я не могу с этим справиться за 5 минут (и даже за час), и поэтому приходится делать простой селект или несколько, что бы собрать всю инфу и форичами хуярить перебирая массивы и уже там дергать/суммировать/пересчитывать что тебе нужно.
Таких начальников надо гнать в шею.
Попробуй взять первые 50 подмассивов и их добавь. Потом всегда можно таблицу почистить.
На работе достаточно много свободного времени (как впрочем и дома), поэтому возвращаюсь к учебе.
В планах повысить навыки: git (очень важно), подтянуть js/верстку, а также доделать testhub (когда-то начал, но забросил).
Первый вопрос по гиту. Нет ли каких-то практических гайдов, задач? Потому что очень неудобно тупо зубрить теорию.
Гитбук когда-то читал, и думал что неплохо разбираюсь в гит, но оказалось, что разбираюсь плохо, так что нужно довести до ума
эту тему. Гитбук попытаюсь заучить наизусть, но хотелось бы чего-то более увлекательного, может кто подкинет.
Рассказываю такой пример из практики:
Есть большой сайт/сервис. Решили сделать его клон с немного другим функционалом, дизайном и сео-направленностью.
(Кстати обсуждали вопрос по синхронизации бд, в итоге решили просто перезаливать дампы по крону, но я думаю что есть явно
более интересные способы решения этой задачи, скиньте почитать по этой теме).
Проблема так же в том, как синхронизировать изменения в системе контроля версий? На главном сайте один программист пофиксил
баг. Как мне перенести изменения на клонированный сайт? Пока с трудом (сейчас расскажу подробнее почему с трудом) смог вытянуть изменения
через git diff, но что-то мне подсказывает, что делаю что-то не так.
Так вот, каждый программист работает с отдельной версией сайта (папочки dev1, dev2, и т.д.), но гит-репозитории создаются либо локальные
(на рабочем компьютере программиста), либо на удаленном сервере, это зависит от того, как настроена ide.
Я работаю например с версией dev1, захожу через ssh и там уже работаю с гитом через командную строку. То есть мой нетбинс синхронизирует файлы
с сервером, но с гитом я работаю напрямую.
Чувак работает через PhpStorm, у него репозиторий на своем компе. Соответственно я не могу зайти к нему в папку dev2 на сервере
и посмотреть лог и дифф, как делаю в своей папке.
Продакшн в папке prod, в нем только мастер, который сливается раз в месяц.
Центральный репозиторий (откуда я вытягиваю изменения, и куду пушу, то есть тот который remote -v) в папке git.
Но в этой папке нет рабочих файлов, там внутренности гита (branches, HEAD, config и т.д.), соответственно я могу посмотреть разве что
git log на текущей ветке master, но не могу переключиться на другую ветку и посмотреть в ней историю, пишет
fatal: This operation must be run in a work tree
Решил проблему через жопу, через клонирование центрального репозитория себе на комп во временную папку
git remote add -t target_branch origin ssh://...
git checkout -b target_branch
git pull origin target_branch
В общем, нужно подтягивать знание гита, реквестирую интересные мануалы типа задач от опа (новых задач кстати не появлялось
за последние полгода?).
На работе достаточно много свободного времени (как впрочем и дома), поэтому возвращаюсь к учебе.
В планах повысить навыки: git (очень важно), подтянуть js/верстку, а также доделать testhub (когда-то начал, но забросил).
Первый вопрос по гиту. Нет ли каких-то практических гайдов, задач? Потому что очень неудобно тупо зубрить теорию.
Гитбук когда-то читал, и думал что неплохо разбираюсь в гит, но оказалось, что разбираюсь плохо, так что нужно довести до ума
эту тему. Гитбук попытаюсь заучить наизусть, но хотелось бы чего-то более увлекательного, может кто подкинет.
Рассказываю такой пример из практики:
Есть большой сайт/сервис. Решили сделать его клон с немного другим функционалом, дизайном и сео-направленностью.
(Кстати обсуждали вопрос по синхронизации бд, в итоге решили просто перезаливать дампы по крону, но я думаю что есть явно
более интересные способы решения этой задачи, скиньте почитать по этой теме).
Проблема так же в том, как синхронизировать изменения в системе контроля версий? На главном сайте один программист пофиксил
баг. Как мне перенести изменения на клонированный сайт? Пока с трудом (сейчас расскажу подробнее почему с трудом) смог вытянуть изменения
через git diff, но что-то мне подсказывает, что делаю что-то не так.
Так вот, каждый программист работает с отдельной версией сайта (папочки dev1, dev2, и т.д.), но гит-репозитории создаются либо локальные
(на рабочем компьютере программиста), либо на удаленном сервере, это зависит от того, как настроена ide.
Я работаю например с версией dev1, захожу через ssh и там уже работаю с гитом через командную строку. То есть мой нетбинс синхронизирует файлы
с сервером, но с гитом я работаю напрямую.
Чувак работает через PhpStorm, у него репозиторий на своем компе. Соответственно я не могу зайти к нему в папку dev2 на сервере
и посмотреть лог и дифф, как делаю в своей папке.
Продакшн в папке prod, в нем только мастер, который сливается раз в месяц.
Центральный репозиторий (откуда я вытягиваю изменения, и куду пушу, то есть тот который remote -v) в папке git.
Но в этой папке нет рабочих файлов, там внутренности гита (branches, HEAD, config и т.д.), соответственно я могу посмотреть разве что
git log на текущей ветке master, но не могу переключиться на другую ветку и посмотреть в ней историю, пишет
fatal: This operation must be run in a work tree
Решил проблему через жопу, через клонирование центрального репозитория себе на комп во временную папку
git remote add -t target_branch origin ssh://...
git checkout -b target_branch
git pull origin target_branch
В общем, нужно подтягивать знание гита, реквестирую интересные мануалы типа задач от опа (новых задач кстати не появлялось
за последние полгода?).
Расскажи как устраивался, с каким багажом в итоге начал по собеседованиям гонять и вообще как они проходили, куда и кем взяли? Помотивируй что ли к смене работы. А то сидишь вот так на своем дне и боишься дернуться, а то опять представляю как год без работы сычевать придется.
Попробовал вообще до одной строчки таблицу обрезать. Всё равно ошибка
Ошибка при добавлении в таблицу 1c_test: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') VALUES ('Баков Валентин Сергеевич','10366','' at line 29
Запрос: INSERT INTO `1c_test` (`Организация`, `Всего начислено`, `северные`, `Оклад по дням`, `Оклад по часам`, `Оплата больничных листов`, `Оплата больничных листов за счет работодателя`, `Оплата по среднему заработку`, `Отпуск учебный`, `Оплата отпуска по календарным дням`, `Отпуск за свой счет`, `Отсутствие по невыясненной причине`, `Районный коэффициент`, `Пособие по уходу за ребёнком до 1.5 лет`, `Дни неоплачиваемые согласно табелю`, `Месячная премия`, `Компенсация отпуска при увольнении по календарным дням`, `Всего удержано`, `Удержание по исп. листу процентом`, `Удержание по исп. листу процентом до предела`, `Удержание по исп. листу фикс. суммой`, `НДФЛ`, `Всего выплачено`, `Перечислено в банк (аванс)`, `Через кассу (аванс)`, `Перечислено в банк (под расчет)`, `Через кассу (под расчет)`, `Конечное сальдо`,) VALUES ('Баков Валентин Сергеевич','10366','','10366','','','','','','','','','','','','','','1348','','','','1348','9018','4100','','4918','','')
Таблица 1c_test имеется, в ней только один столбец id. В запросе 28 полей и 28 значений в VALUES. Что этой скотине от меня нужно?
В итоге плюнул и сделал через foreach. тоже не сахар и вроде как за такое ругают, но блин. Три часа вчера пытался "по хорошему" сделать, три часа сегодня - ну не получается. А так сразу получилось.
Я хотел вот попасть на стажировку/обучение в Noveo, но нихуя не смог пройти тестирование их автоматическое, так что до устного даже не допустили, хотя пол года назад летом, когда думал просто джуном к ним устроиться прошел это же автоматическое у них, но завалил устное.
В общем сложно это всё. Как анончики в соседнем треде про количество строк жалуются, что сложно писать фулл тайм на быдлоработе быдлокод, а потом вечером еще и работать над собой и при этом не выгорать за 2 недели такого ритма.
Если заставят въебывать все праздники как обычный месяц то уйду наверное в январе.
Лишняя запятая? Я её, вроде, пробовал удалять.
Вот так оно должно быть?
<?php
$sql = 'SELECT login FROM users';
$loginbd = $pdo->query($sql);
$sth = $loginbd->dbh->prepare($sql);
$sth->execute();
$sth->fetchAll(\PDO::FETCH_ASSOC);
?>
Ошибка такая:
Fatal error: Call to a member function execute() on a non-object in....
почему это так сложна :c??? Нельзя ли в самой таблице настроить столбец на параметр уникальности? Что бы одного и того же значения не могло быть?
Ошибку не правильно скопировал, она на prepare() жалуется
Fatal error: Call to a member function prepare() on a non-object in....
>$loginbd = $pdo->query($sql);
У тебя точно есть переменная, в которую записан объект PDO? И почему тебе так хочется именно query лепить?
>Fatal error: Call to a member function prepare() on a non-object in....
Ну да, ты пытаешься вызвать метода объекта, но у тебя я так понял даже нет $pdo.
$host = "mysql.hostinger.ru";
$user = "u720408973_xleb1";
$password = "*";
$db = "u720408973_xleb1";
$dsn = "mysql:host=$host;dbname=$db;";
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$pdo = new PDO($dsn, $user, $password, $opt);
Вроде все есть, файл подключен
ты че тупой? беги быстрее меня пароль блядь.
Ща тебе залезут базу дропнут нахуй сука!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
БЫСТРА БЛЯДЬ!!!
я уже лезучисто из интереса
а, там пароля нету, ну ладн, молодец тогда, я прост пьян
есть там пароль, ты пьян
$host = "localhost";
$user = "root";
$password = "";
$db = "test";
$dsn = "mysql:host=$host;dbname=$db;";
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$pdo = new PDO($dsn, $user, $password, $opt);
var_dump($pdo);
$sql = "SELECT * FROM test";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$sth = $stmt->fetchAll();
var_dump($sth);
У меня на локалке все работает. Ты точно файл подключил? Вообще у тебя такие вопросы, мне кажется ты явно не с того конца начал учить пыху.
Господа, а если я буду год верстать под вордпресс, я дорасту до мидла? И если да, то на мидла в пхп, жиес или верстке или просто вем миддла?
>Господа, а если я буду год отжиматься по утрам, то я стану кмс? И если да, то кмс по жиму лежа, боксу или просто иди нахуй?
на самом деле я просто пытаюсь создать регистрацию и редактор, а пхп я пытался учить но что то не получилось
Ну так понимаешь, ты таким образом ебанешься вкатываться в погромирование, так его не учат. Если тебе просто приспичило что-то сделать, то или найди кого кто сделает, или ищи готовые решения. Или же учись кодить, можешь поделать задачки ОПа например.
Тебе рано писать регистрации. Начни с изучения PDO хотя бы.
>>902338
Это конечно твое дело, где тебе работать, но я не думаю что полученный опыт тебе где-то потом пригодится.
>>902485
Ну и названия колонок. Ладно, что на русском, но зачем такие длинные?
>>902532
Тебе рано работать с базой данных. Тебе надо начать с изучения PDO, а перед ним наверно синтаксис самого пхп подучить, работу с объектами.
Другие аноны стремятся чему-то научиться, ты же не хочешь учиться, а просто хочешь чтобы за тебя бесплатно сделали твою работу, за которую тебе платят зарплату. С таким отношением ты далеко не уйдешь в нашей сфере.
Постить тут свой код ты конечно можешь, но я точно решать твои задачи за тебя не буду.
С каких это пор стало достаточно года опыта, чтобы претендовать на должность "миддла"? Сами эти градации обычно соответствуют опыту, но уровень требуемых навыков разный в разных компаниях. То есть если в вашей компании года опыта достаточно, чтобы стать миддлом, то ты можешь стать миддл-верстальщиком под вордпресс в ООО рога и копыта.
>>902614
Вкатится он только в компанию, где нет нормального отбора на собеседовании, то есть в компанию где нет хоть сколько-нибудь квалифицированных разработчиков.
>>902513
> Как анончики в соседнем треде про количество строк жалуются, что сложно писать фулл тайм на быдлоработе быдлокод, а потом вечером еще и работать над собой
Иди на другую работу тогда. Или может быть, ты уже достиг потолка и сейчас пик твоей карьеры.
>>902500
SQL надо изучить.
В одном проекте я видел такое решение: используется общий код для 2 проектов, при этом View (и JS/CSS) и Controller у каждого свои, а Model и БД - общая. Ну и естественно во многих местах ифы (если это проект1, то делать так, а если проект2, то иначе) натыканы. В БД также некоторые поля в 2 копиях (показывать страницу на проекте1, показывать на проекте2).
Даже если использовать 2 разных БД, все равно с общим кодом наверно было бы удобнее работать.
Из минусов - надо более внимательно править код, понимая что изменения могут отразиться и на другом проекте.
Идея сделать отдельный независимый репозиторий, в общем плохая. Замучаешься переносить изменения, и этот процесс невозможно автоматизировать, так как код может понадобиться адаптировать при переносе. Если будешь гуглить, это называется "port patches", портирование измененей в несвязанных друг с другом репозиториях.
Я бы советовал найти кого-нибудь, на кого можно переложить портирование изменений, это ручная не автоматизируемая на 100% работа.
В гите есть ветки, теоретически можно было бы вести 2 проекта в 2 ветках, но я ни разу с таким не сталкивался, и уверен, что будут какие-то сложности. Ветки все же предназначены для того, чтобы добавлять какие-то новые фичи, а не делать отдельный проект. Хотя гит позволяет, используя cherry-pick вместо merge выбирать, какие изменения перенести.
Также, теоретически, можно было бы попробовать разбить код проекта на уникальную часть и общую, и общую часть (части) вынести в отдельные библиотеки, которые подключаются в проекте1 и проекте2. Но это по сути усложненная вариация первого подхода.
Еще, я знаю, вордпресс позволяет вести несколько сайтов, но там по моему код общий, просто у каждого сайта своя копия БД. То есть тебе не очень подойдет.
Теперь насчет переноса изменений. Такая потребность часто бывает, ну например при обнаружении бага в Windows 10 майкрософт вынужден патчить и более старые ОС, как я понимаю, там вручную переносят и адаптируют патч под каждую ОС.
Ты можешь попробовать такой подход. Сделать дифф изменений в проекте1 (получится так называемый патч), вручную исправить этот патч (убрав ненужное) и применить к проекту2.
https://git-scm.com/book/ru/v1/Распределённый-Git-Сопровождение-проекта
Там есть пункт про применение патчей, полученных по почте, может оно тебе подойдет?
Также, тут http://stackoverflow.com/questions/5120038/is-it-possible-to-cherry-pick-a-commit-from-another-git-repository/15421574#15421574 предлагают подключить второй репозиторий как удаленный, сделать fetch, но вместо merge (примерджить все изменения из него) использовать cherry-pick (отобрать вручную нужные изменения). Но тут проблема, что если ты нечаянно сделаешь pull то вмерджится все, то есть легко ошибиться.
Я сам, если честно, до такой степени в гите не разбираюсь. Но я понимаю, что гит в общем не рассчитан на работу с 2 независимыми репозиториями. Я бы не советовал тебе изучать все его тонкости, а только те команды, которые могут пригодиться в твоей задаче.
По синхронизации БД: лучше всего было иметь одну БД. Также, в MySQL есть репликация, аж 2 вида: statement-based (с мастера на слейв идет поток SQL запросов) и row-based (с мастера на слейв идет в бинарном виде содержимое изменившихся строк и ячеек).
> Так вот, каждый программист работает с отдельной версией сайта (папочки dev1, dev2, и т.д.), но гит-репозитории создаются либо локальные
(на рабочем компьютере программиста), либо на удаленном сервере, это зависит от того, как настроена ide.
Так эти репозитории связаны? Они создаются через git clone или разработчик тупо копипастт файлы и создает новый репозиторий из них?
Обычно делают так: есть центральный репозиторий. Разработчик делает git clone, получает копию. Потом периодически делает git pull, чтобы забрать новые изменения от других людей. Когда надо что-то добавить в код, он создает ветку, пишет код в ней, пушит в центральный репозиторий и отправляет старшему на проверку. Тот смотрит, если все ок, мерджит его в мастер.
Иногда работают без веток, разработчик сразу пушит в мастер, если люди ответственные и внимательные то так тоже можно.
Из твоего описания я не понял, как это устроено у вас.
Тебе советую перечитать про удаленные репозитории и ветки, команды git branch, git pull/push. Что-то у меня ощущение, что ты плохо понимаешь, как все устроено.
Попробую немного пояснить.
Коммит - это набор всех файлов + дополнительная информация (вроде комментария, автора и тд). То есть когда ты делаешь коммит, гит, условно говоря, создает копию всех твоих файлов и сохраняет ее в папочку .git. разумеется, там все по-умному оптимизировано и сжато и при коммите репозиторий не увеличивается в 2 раза.
Коммит нельзя отредактировать, но можно удалить и закоммитить заново, при этом у него может поменяться хеш.
Ветка - это по сути последовательность коммитов. Можно в любой момент начать новую ветку с любого коммита в репозитории. В начале есть одна ветка master, дальше ты можешь от какого-то коммита отпочковывать новые ветки.
push работает так: он сравнивает список коммитов в локальной ветке и удаленной ветке с тем же именем и отправляет коммиты, которых нет на удаленном сервере. Если удаленно такой ветки нет - она там создается.
pull работает так: делает fetch изменений с удаленного сервера, затем merge.
fetch сравнивает список коммитов локально и удаленно в текущей ветке и скачивает отстутсвующие локально коммиты в origin/master. Ты можешь далее вмерджить их к себе в master.
Не понимаю, зачем тебе лезть в личные репозитории других разработчиков. Если тебе нужен код, который есть у них, но нет в центральном репозитории, есть 2 варианта:
- попроси их поместить код в отдельную ветку и запушить в центральный репозиторий
- подключи их репозиторий как удаленный (git remote add) например под именем dev2 и с помощью git fetch dev2 master ты скачаешь коммиты того разработичка в локальную ветку dev2/master, где можешь их просмотреть или смерджить.
Сделать git remote add можно только если репозиторий удаленно доступен по какому-то протоколу.
> Центральный репозиторий (откуда я вытягиваю изменения, и куду пушу, то есть тот который remote -v) в папке git.
Но в этой папке нет рабочих файлов, там внутренности гита (branches, HEAD, config и т.д.), соответственно я могу посмотреть разве что
> git log на текущей ветке master, но не могу переключиться на другую ветку и посмотреть в ней историю, пишет
Это репозиторий без рабочей копии, то есть используется просто как хранилище кода (коммитов) и состоит из папки .git. зачем тебе в нем что-то смотреть? Ты делаешь fetch/pull и получаешь все изменения из него к себе.
Не надо лазать по чужим репозиториям. Надо научиться работать с удаленными репозториями и ветками. В гитбуке есть глава по теме.
В одном проекте я видел такое решение: используется общий код для 2 проектов, при этом View (и JS/CSS) и Controller у каждого свои, а Model и БД - общая. Ну и естественно во многих местах ифы (если это проект1, то делать так, а если проект2, то иначе) натыканы. В БД также некоторые поля в 2 копиях (показывать страницу на проекте1, показывать на проекте2).
Даже если использовать 2 разных БД, все равно с общим кодом наверно было бы удобнее работать.
Из минусов - надо более внимательно править код, понимая что изменения могут отразиться и на другом проекте.
Идея сделать отдельный независимый репозиторий, в общем плохая. Замучаешься переносить изменения, и этот процесс невозможно автоматизировать, так как код может понадобиться адаптировать при переносе. Если будешь гуглить, это называется "port patches", портирование измененей в несвязанных друг с другом репозиториях.
Я бы советовал найти кого-нибудь, на кого можно переложить портирование изменений, это ручная не автоматизируемая на 100% работа.
В гите есть ветки, теоретически можно было бы вести 2 проекта в 2 ветках, но я ни разу с таким не сталкивался, и уверен, что будут какие-то сложности. Ветки все же предназначены для того, чтобы добавлять какие-то новые фичи, а не делать отдельный проект. Хотя гит позволяет, используя cherry-pick вместо merge выбирать, какие изменения перенести.
Также, теоретически, можно было бы попробовать разбить код проекта на уникальную часть и общую, и общую часть (части) вынести в отдельные библиотеки, которые подключаются в проекте1 и проекте2. Но это по сути усложненная вариация первого подхода.
Еще, я знаю, вордпресс позволяет вести несколько сайтов, но там по моему код общий, просто у каждого сайта своя копия БД. То есть тебе не очень подойдет.
Теперь насчет переноса изменений. Такая потребность часто бывает, ну например при обнаружении бага в Windows 10 майкрософт вынужден патчить и более старые ОС, как я понимаю, там вручную переносят и адаптируют патч под каждую ОС.
Ты можешь попробовать такой подход. Сделать дифф изменений в проекте1 (получится так называемый патч), вручную исправить этот патч (убрав ненужное) и применить к проекту2.
https://git-scm.com/book/ru/v1/Распределённый-Git-Сопровождение-проекта
Там есть пункт про применение патчей, полученных по почте, может оно тебе подойдет?
Также, тут http://stackoverflow.com/questions/5120038/is-it-possible-to-cherry-pick-a-commit-from-another-git-repository/15421574#15421574 предлагают подключить второй репозиторий как удаленный, сделать fetch, но вместо merge (примерджить все изменения из него) использовать cherry-pick (отобрать вручную нужные изменения). Но тут проблема, что если ты нечаянно сделаешь pull то вмерджится все, то есть легко ошибиться.
Я сам, если честно, до такой степени в гите не разбираюсь. Но я понимаю, что гит в общем не рассчитан на работу с 2 независимыми репозиториями. Я бы не советовал тебе изучать все его тонкости, а только те команды, которые могут пригодиться в твоей задаче.
По синхронизации БД: лучше всего было иметь одну БД. Также, в MySQL есть репликация, аж 2 вида: statement-based (с мастера на слейв идет поток SQL запросов) и row-based (с мастера на слейв идет в бинарном виде содержимое изменившихся строк и ячеек).
> Так вот, каждый программист работает с отдельной версией сайта (папочки dev1, dev2, и т.д.), но гит-репозитории создаются либо локальные
(на рабочем компьютере программиста), либо на удаленном сервере, это зависит от того, как настроена ide.
Так эти репозитории связаны? Они создаются через git clone или разработчик тупо копипастт файлы и создает новый репозиторий из них?
Обычно делают так: есть центральный репозиторий. Разработчик делает git clone, получает копию. Потом периодически делает git pull, чтобы забрать новые изменения от других людей. Когда надо что-то добавить в код, он создает ветку, пишет код в ней, пушит в центральный репозиторий и отправляет старшему на проверку. Тот смотрит, если все ок, мерджит его в мастер.
Иногда работают без веток, разработчик сразу пушит в мастер, если люди ответственные и внимательные то так тоже можно.
Из твоего описания я не понял, как это устроено у вас.
Тебе советую перечитать про удаленные репозитории и ветки, команды git branch, git pull/push. Что-то у меня ощущение, что ты плохо понимаешь, как все устроено.
Попробую немного пояснить.
Коммит - это набор всех файлов + дополнительная информация (вроде комментария, автора и тд). То есть когда ты делаешь коммит, гит, условно говоря, создает копию всех твоих файлов и сохраняет ее в папочку .git. разумеется, там все по-умному оптимизировано и сжато и при коммите репозиторий не увеличивается в 2 раза.
Коммит нельзя отредактировать, но можно удалить и закоммитить заново, при этом у него может поменяться хеш.
Ветка - это по сути последовательность коммитов. Можно в любой момент начать новую ветку с любого коммита в репозитории. В начале есть одна ветка master, дальше ты можешь от какого-то коммита отпочковывать новые ветки.
push работает так: он сравнивает список коммитов в локальной ветке и удаленной ветке с тем же именем и отправляет коммиты, которых нет на удаленном сервере. Если удаленно такой ветки нет - она там создается.
pull работает так: делает fetch изменений с удаленного сервера, затем merge.
fetch сравнивает список коммитов локально и удаленно в текущей ветке и скачивает отстутсвующие локально коммиты в origin/master. Ты можешь далее вмерджить их к себе в master.
Не понимаю, зачем тебе лезть в личные репозитории других разработчиков. Если тебе нужен код, который есть у них, но нет в центральном репозитории, есть 2 варианта:
- попроси их поместить код в отдельную ветку и запушить в центральный репозиторий
- подключи их репозиторий как удаленный (git remote add) например под именем dev2 и с помощью git fetch dev2 master ты скачаешь коммиты того разработичка в локальную ветку dev2/master, где можешь их просмотреть или смерджить.
Сделать git remote add можно только если репозиторий удаленно доступен по какому-то протоколу.
> Центральный репозиторий (откуда я вытягиваю изменения, и куду пушу, то есть тот который remote -v) в папке git.
Но в этой папке нет рабочих файлов, там внутренности гита (branches, HEAD, config и т.д.), соответственно я могу посмотреть разве что
> git log на текущей ветке master, но не могу переключиться на другую ветку и посмотреть в ней историю, пишет
Это репозиторий без рабочей копии, то есть используется просто как хранилище кода (коммитов) и состоит из папки .git. зачем тебе в нем что-то смотреть? Ты делаешь fetch/pull и получаешь все изменения из него к себе.
Не надо лазать по чужим репозиториям. Надо научиться работать с удаленными репозториями и ветками. В гитбуке есть глава по теме.
Лучше в тред. так будет соблюдаться очередь, плюс у нас есть аноны, которые не пишут в треде, но читают его и им может быть пригодится что-то из написанного.
Нет, как минимум еще node.js и ASP.NET надо подвезти.
> Расскажи как устраивался
Просто взял и устроился без левых мыслей.
> с каким багажом
Хорошим (по сравнению с другими "соискателями") багажом по php: написал оповский файлообменник с 95% поставленных багфиксов, поверхностно изучил два фреймворка (юи и симфони). Слабыми по js. Отличными по sql. Верстка само собой.
> как проходили собеседования
Их должно быть больше одного?
Чувак по скайпу минут пять примитивщину по теории (неймспейсы, замыкания, отличия между версиями php, внешние ключи, виды джойнов, движки mysql, нормализация, транзакции, области видимости в js, доктайп и т.п.).
> кем взяли?
Веб-разработчиком.
> помотивируй
Понятия не имею, кто ты и какие у тебя цели.
У меня была цель уехать из ненавистной мухосрани в культурный красивый город (который меня впрочем успел достать шумом и суетой в метро).
Теперь хочу а) уютный домик в тихом пригороде б) карьерный рост как показатель прокачки моего irl персонажа.
Умную бабу еще хочу, но тут придется обломиться наверное.
>>902687
> используется общий код для 2 проектов, при этом View (и JS/CSS) и Controller у каждого свои, а Model и БД - общая
Это пожалуй был бы оптимальный вариант. Вообще я спрашиваю чисто для себя, для будущего опыта.
На практике проект в таком состоянии, что действительно остается махнуть рукой и вносить вручную все изменения параллельно в обе версии.
Бд спроектирована плохо, и для некоторых таблиц должны быть разные данные, заполняемые каждая из своей админки. Вернее часть полей общая, часть конфликтующая между проектами. То есть нужно переделывать структуру бд. Код сильно завязан на структуру базы, придется много переписывать.
Ну и нет уверенности, что проекты будут в дальнейшем синхронизироваться, скорее всего на втором сайте будет добавляться новый функционал, старый убираться.
В общем суть в том, что пару месяцев назад проект форкнули, на главной версии продолжают фиксить ошибки, и мне нужно как-то параллельно синхронизировать багфиксы на втором проекте (дай бог чтобы не появился третий-четвертый). В принципе работы немного и это несложно (там меньше сотни коммитов, за неделю разгребу), но труд не благородный.
> Ты можешь попробовать такой подход. Сделать дифф изменений в проекте1 (получится так называемый патч), вручную исправить этот патч (убрав ненужное) и применить к проекту2.
Да, я же и пошел таким путем. Хотя запутался в репозиториях и ветках.
Про cherry-pick (видимо речь про вишенку с торта, считающуюся почему-то лакомством) слышал, но не знал что можно переносить коммиты между репозиториями.
По гиту основы знаю (пуш, пулл, коммит), но с ветками и работой с удаленными репозиториями пока слабо.
> Коммит нельзя отредактировать, но можно удалить и закоммитить заново, при этом у него может поменяться хеш.
--amend по-моему, но это только для последнего коммита, насчет удаления коммитов из "средины" истории даже не знаю, хотя наверное это не часто на практике
> То есть когда ты делаешь коммит, гит, условно говоря, создает копию всех твоих файлов и сохраняет ее в папочку .git
Начал перечитывать гитбук, этот момент кстати не совсем понятный. Походу при коммите делается копия не всех файлов, а только измененных. Те которые не менялись, там ставится что-то типа ссылки что ли.
>The major difference between Git and any other VCS (Subversion and friends included) is the way Git thinks about its data. Conceptually, most other systems store information as a list of file-based changes.
> if files have not changed, Git doesn’t store the file again, just a link to the previous identical file it has already stored. Git thinks about its data more like a stream of snapshots.
https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
Я если честно плохо понимаю, что такое "снимки" (snapshots), чем они отличаются от "list of file-based changes".
Кажется, суть в том, что другие скв хранят именно диффы для каждого файла, а гит рассматривает папку как "a miniature filesystem". Как будто я знаю принципы работы файловых систем и мне это о чем-то говорит.
Короче, не знаю почему они делают такой акцент на этом, и как это может пригодиться на практике.
############################################
По поводу того как у нас организована работа с гитом. Мне конечно сложно объяснить, потому что сам плохо понимаю, но попробую.
На удаленном сервере (где-то во Франкфурте) есть папочка /var/www/sites/project1
В ней список директорий
1) prod - папка продакшн, в ней есть репозиторий, но только с веткой master. В нее пушит тимлид когда сделает ревью по внесенным правкам. То есть я не могу зайти в папку prod и посмотреть в ней диффы нужных мне изменений, их здесь еще нет. Это не центральный репозиторий. Центральный репозиторий - в папке git (см. пункт 3).
2) папки dev1, dev2, ... - копии проекта для каждого разработчика. У меня в папке dev1 есть репозиторий, я по ssh захожу в эту папку и из нее делаю пулл/пуш в центральный репозиторий в папке git (см.пункт 3). В папке dev2 (коллеги) есть рабочая копия проекта, но нет репозитория, потому что он прямо со своей машины пушит в папку git (смотри пункт 3)
3) git (без точки) это папка, из которой я и другие разработчики тянут изменения, и куда все пушат изменения. То есть в своем репозитории я выполню git remote -v, выведет /var/www/sites/project1/git
Таким образом только здесь есть все ветки, в том числе нужная мне target_branch, где есть искомый коммит. Но я не могу посмотреть эти изменения прямо в репозитории (или не знаю как).
Спуллить не могу, потому что полезет туча конфлик... А, все, понял ошибку, я забыл что кроме пулла есть фетч. Вернее о существовании знал, но не употреблял на практике.
Вот вам и голая теория без упражнений.
Эх, ладно, я пошел перечитывать гитбук. Может я возьму его дотошностью, что поделать. Хотя было бы интереснее решая практические задачи, а не зазубривая последовательность букв наизусть.
От вас хрен дождешься годноты, что-то сам нагуглил.
https://githowto.com/ru
Сейчас проверю, хотя там скорее всего пять хелловорлдов а потом просьба где-то зарегистрироваться и что-то купить.
Вот еще что-то http://rypress.com/tutorials/git/index
> Расскажи как устраивался
Просто взял и устроился без левых мыслей.
> с каким багажом
Хорошим (по сравнению с другими "соискателями") багажом по php: написал оповский файлообменник с 95% поставленных багфиксов, поверхностно изучил два фреймворка (юи и симфони). Слабыми по js. Отличными по sql. Верстка само собой.
> как проходили собеседования
Их должно быть больше одного?
Чувак по скайпу минут пять примитивщину по теории (неймспейсы, замыкания, отличия между версиями php, внешние ключи, виды джойнов, движки mysql, нормализация, транзакции, области видимости в js, доктайп и т.п.).
> кем взяли?
Веб-разработчиком.
> помотивируй
Понятия не имею, кто ты и какие у тебя цели.
У меня была цель уехать из ненавистной мухосрани в культурный красивый город (который меня впрочем успел достать шумом и суетой в метро).
Теперь хочу а) уютный домик в тихом пригороде б) карьерный рост как показатель прокачки моего irl персонажа.
Умную бабу еще хочу, но тут придется обломиться наверное.
>>902687
> используется общий код для 2 проектов, при этом View (и JS/CSS) и Controller у каждого свои, а Model и БД - общая
Это пожалуй был бы оптимальный вариант. Вообще я спрашиваю чисто для себя, для будущего опыта.
На практике проект в таком состоянии, что действительно остается махнуть рукой и вносить вручную все изменения параллельно в обе версии.
Бд спроектирована плохо, и для некоторых таблиц должны быть разные данные, заполняемые каждая из своей админки. Вернее часть полей общая, часть конфликтующая между проектами. То есть нужно переделывать структуру бд. Код сильно завязан на структуру базы, придется много переписывать.
Ну и нет уверенности, что проекты будут в дальнейшем синхронизироваться, скорее всего на втором сайте будет добавляться новый функционал, старый убираться.
В общем суть в том, что пару месяцев назад проект форкнули, на главной версии продолжают фиксить ошибки, и мне нужно как-то параллельно синхронизировать багфиксы на втором проекте (дай бог чтобы не появился третий-четвертый). В принципе работы немного и это несложно (там меньше сотни коммитов, за неделю разгребу), но труд не благородный.
> Ты можешь попробовать такой подход. Сделать дифф изменений в проекте1 (получится так называемый патч), вручную исправить этот патч (убрав ненужное) и применить к проекту2.
Да, я же и пошел таким путем. Хотя запутался в репозиториях и ветках.
Про cherry-pick (видимо речь про вишенку с торта, считающуюся почему-то лакомством) слышал, но не знал что можно переносить коммиты между репозиториями.
По гиту основы знаю (пуш, пулл, коммит), но с ветками и работой с удаленными репозиториями пока слабо.
> Коммит нельзя отредактировать, но можно удалить и закоммитить заново, при этом у него может поменяться хеш.
--amend по-моему, но это только для последнего коммита, насчет удаления коммитов из "средины" истории даже не знаю, хотя наверное это не часто на практике
> То есть когда ты делаешь коммит, гит, условно говоря, создает копию всех твоих файлов и сохраняет ее в папочку .git
Начал перечитывать гитбук, этот момент кстати не совсем понятный. Походу при коммите делается копия не всех файлов, а только измененных. Те которые не менялись, там ставится что-то типа ссылки что ли.
>The major difference between Git and any other VCS (Subversion and friends included) is the way Git thinks about its data. Conceptually, most other systems store information as a list of file-based changes.
> if files have not changed, Git doesn’t store the file again, just a link to the previous identical file it has already stored. Git thinks about its data more like a stream of snapshots.
https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
Я если честно плохо понимаю, что такое "снимки" (snapshots), чем они отличаются от "list of file-based changes".
Кажется, суть в том, что другие скв хранят именно диффы для каждого файла, а гит рассматривает папку как "a miniature filesystem". Как будто я знаю принципы работы файловых систем и мне это о чем-то говорит.
Короче, не знаю почему они делают такой акцент на этом, и как это может пригодиться на практике.
############################################
По поводу того как у нас организована работа с гитом. Мне конечно сложно объяснить, потому что сам плохо понимаю, но попробую.
На удаленном сервере (где-то во Франкфурте) есть папочка /var/www/sites/project1
В ней список директорий
1) prod - папка продакшн, в ней есть репозиторий, но только с веткой master. В нее пушит тимлид когда сделает ревью по внесенным правкам. То есть я не могу зайти в папку prod и посмотреть в ней диффы нужных мне изменений, их здесь еще нет. Это не центральный репозиторий. Центральный репозиторий - в папке git (см. пункт 3).
2) папки dev1, dev2, ... - копии проекта для каждого разработчика. У меня в папке dev1 есть репозиторий, я по ssh захожу в эту папку и из нее делаю пулл/пуш в центральный репозиторий в папке git (см.пункт 3). В папке dev2 (коллеги) есть рабочая копия проекта, но нет репозитория, потому что он прямо со своей машины пушит в папку git (смотри пункт 3)
3) git (без точки) это папка, из которой я и другие разработчики тянут изменения, и куда все пушат изменения. То есть в своем репозитории я выполню git remote -v, выведет /var/www/sites/project1/git
Таким образом только здесь есть все ветки, в том числе нужная мне target_branch, где есть искомый коммит. Но я не могу посмотреть эти изменения прямо в репозитории (или не знаю как).
Спуллить не могу, потому что полезет туча конфлик... А, все, понял ошибку, я забыл что кроме пулла есть фетч. Вернее о существовании знал, но не употреблял на практике.
Вот вам и голая теория без упражнений.
Эх, ладно, я пошел перечитывать гитбук. Может я возьму его дотошностью, что поделать. Хотя было бы интереснее решая практические задачи, а не зазубривая последовательность букв наизусть.
От вас хрен дождешься годноты, что-то сам нагуглил.
https://githowto.com/ru
Сейчас проверю, хотя там скорее всего пять хелловорлдов а потом просьба где-то зарегистрироваться и что-то купить.
Вот еще что-то http://rypress.com/tutorials/git/index
Сколько тебе лет?
сколько платят? Чем конкретно занимаешься(есть ли в обязанностях js или чистая пыха?).
Что означает as?
Погодите емана.
Получается цикл с массивом - это когда условие цикла - весь массив, и по очереди проверяется каждый элемент массива как условие работы цикла?
Первый аргумент, это массив по которому ты хочешь пройтись.
Допустим массив $cars;
Второе это - псевдоним для текущего элемента цикла. Т.е.
foreach($cars as $car) {
echo $car;
}
Выведет тебе все элементы массива, т.е все машины.
Если у тебя ассоциативный массив, то можно так сделать:
foreach($cars as $key => $value) {
echo "Марка автомобиля :{$key} модель: {$value} ;
}
В key у тебя будет индекс элемента массива, в value - его значение.
Да. Потому что обычным циклом ты сможешь пройтись только по сортированному не ассоциированному массиву.
* не асоциативному, ну ты понел.
>>902837
Цикл обхода массива это немного не то что другие циклы, в которых ты задаешь условие: тип повторять цикл пока условие верно.
Тут у тебя цикл всегда выполнится столько раз, сколько у тебя в массиве элементов.
Ну и с помощью as ты показываешь в какую переменную на каждой итерации у тебя в переменную $car будет складываться текущий элемент массива.
Так как в массиве есть не только значения но и ключи, то есть более сложный вариант, когда у тебя текущий элемент массива раскладывается на две переменные: на ключ и значение - as $key => $value
хеш коммита определяется из хеша файлов и метаданных (включая время). --amend по сут создает новый коммит с новым хешем.
> Начал перечитывать гитбук, этот момент кстати не совсем понятный. Походу при коммите делается копия не всех файлов, а только измененных. Те которые не менялись, там ставится что-то типа ссылки что ли.
Это уже детали и оптимизации. Есть другой подход, когда мы сохраняем начальное состояние, а потом в каждом коммите сохраняем изменения (diff, patch) в сравнении с предыдущим. Нетрудно написать на bash такую примитивную систему управления версиям.
Гит так не делает, он к каждому коммиту прикрепляет все файлы из рабочей области. Ну а дальше уже идут оптимизации: информация о файлах записывается в виде хешей (по которому в хранилище можно найти тело файла), и если файл не поменялся, то его хеш остается неизменным и новый объект в БД не добавляется.
Вообще, гит построен вокруг хешей. Хеши являются идентификаторами для содержимого файлов, коммитов и других объектов внутри хранилища (хранилище - громко сказано, оно реализовано как папки с файлами внутри .git/objects).
Ну хеши - это уже детали, важно понимать что для любого коммита мы можем извлечь все относящиеся к нему файлы.
Кстати, ветки - это файлики в .git/refs/heads, которые содержат хеш "головы", последнего коммита ветки. Остальные коммиты ищутся благодаря тому, что в каждом коммите есть хеш родительского коммита.
> Я если честно плохо понимаю, что такое "снимки" (snapshots), чем они отличаются от "list of file-based changes".
snapshot это когда ты условно говоря при каждом коммите копируешь все файлы в отдельную папку. snapshot значит "снимок", то есть копия.
diff это когда ты при коммите сравниваешь текущее состояние с предыдущим и сохраняешь разницу между ними.
А дальшу уже идут оптимизации: гит вместо сохранения самих файлов каждый раз сохраняет в коммите их хеши, и добавляет те, которых там еще нет, в хранилище. Если файл не изменился то у него тот же самый хеш и он второй раз в хранилище не добавляется.
То есть там такой алгоритм коммита (я упрощаю конечно):
- взять хеши всех файлов
- добавить каждый в хранилище, если там нет файла с таким хешем
- записать все хеши в объект коммита
- сгенерировать хеш от коммита и записать сам объект коммита в хранилище под этим хешем
- записать в указатель на "голову" текущей ветки хеш нового коммита
На самом деле там все еще немного сложнее, там в коммит записывается не просто список всех хешей файлов, а ссылка (хеш) объекта tree, который как бы соответствует папке. Но это уже детали.
> Короче, не знаю почему они делают такой акцент на этом, и как это может пригодиться на практике.
Возможно они хотят поделиться тем, как оригинально устроен гит внутри.
> prod - папка продакшн, в ней есть репозиторий, но только с веткой master. В нее пушит тимлид когда сделает ревью по внесенным правкам.
Это скорее всего репозиторий только для деплоя, то есть выгрузки изменений на сайт. зачем тебе в него лезть? Он скорее всего содержит копию мастера из основного репозитория.
> папки dev1, dev2, ... - копии проекта для каждого разработчика.
Ну это уже получается личное дело разработчика, как туда помещать файлы. Тебе к ним лезть незачем.
> Таким образом только здесь есть все ветки, в том числе нужная мне target_branch, где есть искомый коммит. Но я не могу посмотреть эти изменения прямо в репозитории (или не знаю как).
Тебе надо скачать нужные ветки к себе и смотреть у себя. pull ты делать не обязан, для просмотра хватит и fetch.
> Спуллить не могу, потому что полезет туча конфлик
Ну это потому что ты пуллишь удаленный мастер в свой. Но ты мог бы пуллить или фетчить именно нужную ветку оттуда и конфликтов не будет, если ты ничего не коммитил в нее. В origin/branch не коммитят и потому fetch не вызывает конфликтов.
fetch копирует новый коммиты из удаленной ветки branch в origin/branch у тебя. А pull дополнительно мерджит origin/branch в локальный branch.
origin/branch - это как бы локальная копия удаленной ветки
Тебе надо читать именно про ветки и удаленные репозитории. Точно помню, что там есть глава.
хеш коммита определяется из хеша файлов и метаданных (включая время). --amend по сут создает новый коммит с новым хешем.
> Начал перечитывать гитбук, этот момент кстати не совсем понятный. Походу при коммите делается копия не всех файлов, а только измененных. Те которые не менялись, там ставится что-то типа ссылки что ли.
Это уже детали и оптимизации. Есть другой подход, когда мы сохраняем начальное состояние, а потом в каждом коммите сохраняем изменения (diff, patch) в сравнении с предыдущим. Нетрудно написать на bash такую примитивную систему управления версиям.
Гит так не делает, он к каждому коммиту прикрепляет все файлы из рабочей области. Ну а дальше уже идут оптимизации: информация о файлах записывается в виде хешей (по которому в хранилище можно найти тело файла), и если файл не поменялся, то его хеш остается неизменным и новый объект в БД не добавляется.
Вообще, гит построен вокруг хешей. Хеши являются идентификаторами для содержимого файлов, коммитов и других объектов внутри хранилища (хранилище - громко сказано, оно реализовано как папки с файлами внутри .git/objects).
Ну хеши - это уже детали, важно понимать что для любого коммита мы можем извлечь все относящиеся к нему файлы.
Кстати, ветки - это файлики в .git/refs/heads, которые содержат хеш "головы", последнего коммита ветки. Остальные коммиты ищутся благодаря тому, что в каждом коммите есть хеш родительского коммита.
> Я если честно плохо понимаю, что такое "снимки" (snapshots), чем они отличаются от "list of file-based changes".
snapshot это когда ты условно говоря при каждом коммите копируешь все файлы в отдельную папку. snapshot значит "снимок", то есть копия.
diff это когда ты при коммите сравниваешь текущее состояние с предыдущим и сохраняешь разницу между ними.
А дальшу уже идут оптимизации: гит вместо сохранения самих файлов каждый раз сохраняет в коммите их хеши, и добавляет те, которых там еще нет, в хранилище. Если файл не изменился то у него тот же самый хеш и он второй раз в хранилище не добавляется.
То есть там такой алгоритм коммита (я упрощаю конечно):
- взять хеши всех файлов
- добавить каждый в хранилище, если там нет файла с таким хешем
- записать все хеши в объект коммита
- сгенерировать хеш от коммита и записать сам объект коммита в хранилище под этим хешем
- записать в указатель на "голову" текущей ветки хеш нового коммита
На самом деле там все еще немного сложнее, там в коммит записывается не просто список всех хешей файлов, а ссылка (хеш) объекта tree, который как бы соответствует папке. Но это уже детали.
> Короче, не знаю почему они делают такой акцент на этом, и как это может пригодиться на практике.
Возможно они хотят поделиться тем, как оригинально устроен гит внутри.
> prod - папка продакшн, в ней есть репозиторий, но только с веткой master. В нее пушит тимлид когда сделает ревью по внесенным правкам.
Это скорее всего репозиторий только для деплоя, то есть выгрузки изменений на сайт. зачем тебе в него лезть? Он скорее всего содержит копию мастера из основного репозитория.
> папки dev1, dev2, ... - копии проекта для каждого разработчика.
Ну это уже получается личное дело разработчика, как туда помещать файлы. Тебе к ним лезть незачем.
> Таким образом только здесь есть все ветки, в том числе нужная мне target_branch, где есть искомый коммит. Но я не могу посмотреть эти изменения прямо в репозитории (или не знаю как).
Тебе надо скачать нужные ветки к себе и смотреть у себя. pull ты делать не обязан, для просмотра хватит и fetch.
> Спуллить не могу, потому что полезет туча конфлик
Ну это потому что ты пуллишь удаленный мастер в свой. Но ты мог бы пуллить или фетчить именно нужную ветку оттуда и конфликтов не будет, если ты ничего не коммитил в нее. В origin/branch не коммитят и потому fetch не вызывает конфликтов.
fetch копирует новый коммиты из удаленной ветки branch в origin/branch у тебя. А pull дополнительно мерджит origin/branch в локальный branch.
origin/branch - это как бы локальная копия удаленной ветки
Тебе надо читать именно про ветки и удаленные репозитории. Точно помню, что там есть глава.
foreach это специальный цикл для перебора всех элементов массива. на каждом шаге берется очередной элемент и его ключ и значение копируются в указанные тобой переменные и выполняется тело цикла.
Условие там не массив, а "пока мы не перебрали все элементы по очереди, продолжать цикл". То есть если в массиве 100 элементов, тело цикла выполняется 100 раз.
as это обязательная часть синтаксиса:
foreach ({ выражение, дающее массив} as {переменная, куда копировать ключ} => {переменная, куда копировать значение элемента}) {
тело
}
В "выражение, дающее массив" можно писать не только переменную, но любое выражение, которое возвращает массив, например:
foreach ([1, 2,3] as $k => $number)
>Ну и названия колонок. Ладно, что на русском, но зачем такие длинные?
Так выгружается из 1С. Я подумал и решил оставить. Программе до лампочки, а мне не приходится возиться с переименовыванием выгруженных данных сначала в некие "программные", а потом в понятные пользователю и выводимые в браузер (Месячная премия -> monthlyBonus -> Месячная премия).
>Сап, пхп боги. Имею хороший опыт ООП на Java, понимание алгоритмов и вообщем хороший уровень Computer Science.
Если это правда, то никаких проблем не должно возникнуть же, за пару недель должен осилить.
>сопутствующих технологий/фреймворков?
Сопутствующие технологии это что вообще? Ну, SQL, 1-3 нормальные формы знаешь же? Фреймворки все знать не надо. Если хочешь посмотреть самые популярные для продакшена - смотри Yii2 и laravel, там в принципе хорошо надо знать толко ООП и MVC паттерн. В принципе, если ты реально так хорош как говоришь можешь сходу Zend учить, самый илитный фреймворк же.
>Я работал с разными БД
Работать с разными БД это одно, сам SQL то знаешь? CRUD, Left, inner join, primary - foreign key - знакомые слова?
>а с MVC в теории знаком
Ну, тогда начни с того что бы быть с ним знакомым не в теории. У ОПа есть хорошие задачки по MVC.
Да, я видел, вот сегодня уже начну штрудировать, хочу к концу января уже начать ходить на собеседования. Спасибо, хороший трэд у вас.
Неистово удваиваю этого парня. Тоже хочется узнать.
>>901669
>Только надо понимать, что это библиотека для ассертов (утверждений, которые всегда верны), а не проверки например пользовательских данных из формы.
Нифига себе, я ее не по назначению, оказывается, использовал! Думал удобно и легко так - прошелся по нужным переменным, и дальше уверен что всё корректно (ну или сразу исключение кинет). Тогда вопрос еще, пожалуйста: как валидовать валидировать? пользовательский ввод? В PHP есть специальные функции (isset, empty, gettype, isbool и т.п.), это оно? А если мне, например, нужно:
а) $_POST['date'] : например, дата только этого года
б) $_POST['productname'] : строка длиной от 3 до 15 символов, например, не содержащая определенные символы
в) $_POST['value'] : например, строго число, укладывающееся в диапазон INT(11) UNSIGNED и вдобавок, не равное нулю (для вставки в БД)
г) $_POST['price'] : число с плавающей точкой (например 19.56)
Это все if-ами + эти функции?
ОПчик, посмотри моих студентов позязь больше никому не смотреть я стесняюсь
<?
$x = 2;
$n = 3;
function myDegree($x, $n) {
if($n == 0) {
return 1;
}
if($n>0) {
return $x*myDegree($x, $n-1);
}
}
echo myDegree($x, $n);
Есть же условие, если $n == 0 вернуть 1, а она возвращает 8, но $n же должно стать 0 если от 3 несколько раз отнять 1 и когда она станет равно 0 функция должна вернуть 1, но она возвращает 8, почему?
Она возвращает 1 не в место первоначального вызова (т.е. не в echo), а в предыдущую копию себя же, где 1 умножается на $x/
>е достиг потолка и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>и сейчас пик твоей карьеры.
>в днище-шараге на днище-зп
Ничего более злого я в этом треде не читал еще
>функции возвращающие false вместо выбрасывания исключений
Почему ты считаешь, что выбрасывать исключения всегда лучше, чем возвращать false? Разве исключенря существуют не для исключительных ситуаций, которые никогда не должны происходить?
Код покрытый исключениями выглядит в целом лучше, чем функция выбрасывающие ошибки в том месте где ты и не ожидаешь. С исключениями гораздо удобней контролировать поведения кода, опять же можно свои исключения выбрасывать. А контролировать ошибки бесконечной чередой if/else это сплошное уродство.
>>885216 | http://arhivach.org/thread/216627/#885216
Тут можно было конечно сделать чуть оптимальнее - вместо пары методов generateDayPower/consumeDayPower сделать один метод, который возвращает положительное или отрицательное значение (вклад в баланс). Это позволило бы сократить код.
> function PowerStation() {
> PowerNetElement.apply(this, arguments);
как я помню, у электростанции мощность всегда одинакова и потому тут в конструкторе должен быть 1 аргумент.
Аналогично, у SolarPanel должен быть 1 аргумент - дневная мощность, у дома - число квартир, и тд.
Дом нет особого смысла представлять как массив квартир, так как у нас тут нет необходимости работать с отдельными квартирами и можно просто сделать дом одним обычным элементом сети. То есть не надо пытаться моделировать те детали, которые не важны для расчетов.
Насчет линии электропередач - если она наследуется от элемента сети, стоило все же вызвать его конструктор, указав 0 в качестве мощности.
> @param {(PowerElement|PowerStation|SolarPanel|House|PowerLine)}
Можно просто писать название базового класса.
> var tmpNetElements = this._netElements.filter(function(element)
> return ! ("getTransmittedPower" in element);
Я думаю, для отсеивания линий передач лучше было бы проверять конструктор через instanceof PowerLine, либо сделать в PowerNetElement метод isPowerline. Первое наверно проще.
> if (instockPower >= demandPower - power) {
> } else {
Тут стоило применить Math.min или max вместо if.
> 'purchaseCost': purchaseCost + ' тугр.',
Я думаю, лучше возвращать просто число, чтобы с ним можно было делать какие-то действия.
> if (typeof(report[key]) == "number") {
> report[key] = Util.convertkWToMW(report[key]);
Вот такие преобразования - верный повод создать ошибку. Лучше везде использовать одну, жетлательно стандартую единицу - например, Ватт-час.
>>885216 | http://arhivach.org/thread/216627/#885216
Тут можно было конечно сделать чуть оптимальнее - вместо пары методов generateDayPower/consumeDayPower сделать один метод, который возвращает положительное или отрицательное значение (вклад в баланс). Это позволило бы сократить код.
> function PowerStation() {
> PowerNetElement.apply(this, arguments);
как я помню, у электростанции мощность всегда одинакова и потому тут в конструкторе должен быть 1 аргумент.
Аналогично, у SolarPanel должен быть 1 аргумент - дневная мощность, у дома - число квартир, и тд.
Дом нет особого смысла представлять как массив квартир, так как у нас тут нет необходимости работать с отдельными квартирами и можно просто сделать дом одним обычным элементом сети. То есть не надо пытаться моделировать те детали, которые не важны для расчетов.
Насчет линии электропередач - если она наследуется от элемента сети, стоило все же вызвать его конструктор, указав 0 в качестве мощности.
> @param {(PowerElement|PowerStation|SolarPanel|House|PowerLine)}
Можно просто писать название базового класса.
> var tmpNetElements = this._netElements.filter(function(element)
> return ! ("getTransmittedPower" in element);
Я думаю, для отсеивания линий передач лучше было бы проверять конструктор через instanceof PowerLine, либо сделать в PowerNetElement метод isPowerline. Первое наверно проще.
> if (instockPower >= demandPower - power) {
> } else {
Тут стоило применить Math.min или max вместо if.
> 'purchaseCost': purchaseCost + ' тугр.',
Я думаю, лучше возвращать просто число, чтобы с ним можно было делать какие-то действия.
> if (typeof(report[key]) == "number") {
> report[key] = Util.convertkWToMW(report[key]);
Вот такие преобразования - верный повод создать ошибку. Лучше везде использовать одну, жетлательно стандартую единицу - например, Ватт-час.
Кроме того, меня постоянно тянет оптимизировать придуманные структуры. Кто-то постоянно нашептывает мне: "Эта переменная не нужна, эта функция тоже, а вот это вообще можно побитово хранить". Я, конечно, сопротивляюсь, но четкого понятия о том, как организовывать данные в классе не имею.
Возьмем код, проанализированный вот в этом посте >>903624. JS не знаю, так что, возможно, я просто ничего не понял. Там от класса-родителя наследуются, кроме прочих, PowerLine (линии электропередач), при этом в PowerLine не используются ни поля класса-родителя, ни его методы, таким образом, наследование чисто логические. Однако, вместо добавления нового поля _transmittedPower можно хранить величину в уже имеющемся _generateDayPower, и мне кажется, что это хорошо, в конце концов линии электропередач по условию тоже в некотором роде поставщики/потребители энергии, и я не вижу в таком использовании логической ошибки. Но так ли это? Вообще, когда нужно следить, чтоб лишних полей не было, а когда можно махнуть на них рукой? Где почитать про это?
Вот как я бы организовал данные: http://pastebin.com/TBGN0e5M
Написано на условном языке, но должно быть понятно.
>Где можно что-то хорошее почитать на эту тему?
Ты нигде об это не прочитаешь, оно только с опытом приходит, как инкапсуляция. Можешь почитать про object relation mapping, если осилишь конечно же.
Посоны, российское правительство хакнуло американские выборы, используя PHP. Послов выслали, Обама зверствует, американцы ищут шпионов. Весь зарубежный инет обсуждает.
Вот код из отчета ФБР:
rule PAS_TOOL_PHP_WEB_KIT
{
meta:
description = "PAS TOOL PHP WEB KIT FOUND"
strings:
$php = "<?php"
$base64decode = /\='base'\.\(\d+\*\d+\)\.'_de'\.'code'/
$strreplace = "(str_replace("
$md5 = ".substr(md5(strrev("
$gzinflate = "gzinflate"
$cookie = "_COOKIE"
$isset = "isset"
condition:
(filesize > 20KB and filesize < 22KB) and
#cookie == 2 and
#isset == 3 and
all of them
}
Этот код ни о чем еще не говорит, и вообще отчет скорее говорит о том, что там какие-то древние сайты у них, рассказывают мол что такое SQL инъекция, XSS уязвимость. Спорить можно разве что по поводу, действительно ли хаккеры типа тех же Fancy Bear аффилированы с российскими госскруктурами ну прямо очевидное-невереоятное, но то уже политота.
Был тред на HN c обсуждением ( https://news.ycombinator.com/item?id=13279600 ), ОП там отписался (и нахватал плюсов). Код в примере, как выяснили, - шаблон для определения украинского (хитрый план) веб-шелла отсюда: http://profexer.name/pas/download.php
По теме есть еще файл с индикаторами атаки (в основном IP адреса VPS и Тора): https://www.us-cert.gov/security-publications/GRIZZLY-STEPPE-Russian-Malicious-Cyber-Activity
Использованные методы - рассылка емайлов с exe-шниками и фишинговые письма, имитирующие письма от гугла.
Немного еще есть тут (без пруфов): https://www.crowdstrike.com/blog/bears-midst-intrusion-democratic-national-committee/
Утверждают что использовались трояны на Питоне и Powershell.
Хотя в атаке использовались примитивные средства, трудно представить чтобы это были обычные хакеры. Они обычно ищут денег или славы, а тут надо было рассылать тысячи фишинговых писем, потом перерыть тонны скучных почтовых сообщений, чтобы найти интересные вещи и слить их в СМИ и в викиликс. Очень много труда затрачено. Причем взлом сделан чуть ли не год назад.
http://arhivach.org/thread/216627/#898482
Исправленные задачи:
Угол между стрелками: http://ideone.com/4KXN1O
Вывод знака зодиака: http://ideone.com/Fg7RMI
> Насчет поиска знака зодиака, я думаю, проще было сделать массив вида ['Козерог', 21, 1] и сравнивать числа, не перекодируя их в даты.
Тогда и номер месяца можно убрать, если его можно получить из индекса знака зодиака в массиве? Это позволило избавится от цикла и сразу по индексу обращаться к нужному подмассиву с информацией о знаке зодиака. А дальше проверка: если номер дня на входе больше, чем номер дня в подмассиве - отдаём этот знак, если номер дня на входе меньше - отдаём предыдущий знак.
Дерево
> Также, можно написать код без объектов Node и рекурсии, используя стек (стек для хранения записей, которые мы позже выведем, после того как разберемся с их детьми).
Подход без рекурсии на базе стека действительно оказался простым: https://jsfiddle.net/0gpbzo8y/
но не совсем понятно, как выводить список вложенным, а не просто показывать список посещённых элементов. Сделал очень костыльно с добавлением ключа depth к каждому элементу массива: https://jsfiddle.net/0gpbzo8y/2/
Алсо я вспомнил про задачу на джуна, которую тут вбрасывали пару тредов назад, приложил скрин к посту. Как правильно реализовать третью часть? На запрос вида /catalog/samsung/s7 мне нужно выдавать товар, у которого path = s7, path родителя samsung, path его родителя = catalog. Я додумался только до того, чтобы генерировать вложенный sql-запрос. Например, для запроса /catalog/dvd/lg будет генерироваться такой:
select * from items where path = 'lg' and parent_id = (
__select id from items where path = 'dvd' and parent_id = (
____select id from items where path = 'catalog'
__)
);
Это очень плохое решение? Запрос генерирую так: http://ideone.com/pTS09b
Дом 2
https://jsfiddle.net/je858mz4/5/
N рабочих дней
> Автоматически рассчитать даты праздников, увы, нельзя, так как их переносят из-за выходных.
Я понял. Допустим, я получил, неважно как, список всех нерабочих дней в виде массива элементов вида 'd-m-y', решение будет примерно таким http://ideone.com/go0Mfo ?
Про алгоритмическую сложность отпишу отдельно, спасибо большое за ответ!
http://arhivach.org/thread/216627/#898482
Исправленные задачи:
Угол между стрелками: http://ideone.com/4KXN1O
Вывод знака зодиака: http://ideone.com/Fg7RMI
> Насчет поиска знака зодиака, я думаю, проще было сделать массив вида ['Козерог', 21, 1] и сравнивать числа, не перекодируя их в даты.
Тогда и номер месяца можно убрать, если его можно получить из индекса знака зодиака в массиве? Это позволило избавится от цикла и сразу по индексу обращаться к нужному подмассиву с информацией о знаке зодиака. А дальше проверка: если номер дня на входе больше, чем номер дня в подмассиве - отдаём этот знак, если номер дня на входе меньше - отдаём предыдущий знак.
Дерево
> Также, можно написать код без объектов Node и рекурсии, используя стек (стек для хранения записей, которые мы позже выведем, после того как разберемся с их детьми).
Подход без рекурсии на базе стека действительно оказался простым: https://jsfiddle.net/0gpbzo8y/
но не совсем понятно, как выводить список вложенным, а не просто показывать список посещённых элементов. Сделал очень костыльно с добавлением ключа depth к каждому элементу массива: https://jsfiddle.net/0gpbzo8y/2/
Алсо я вспомнил про задачу на джуна, которую тут вбрасывали пару тредов назад, приложил скрин к посту. Как правильно реализовать третью часть? На запрос вида /catalog/samsung/s7 мне нужно выдавать товар, у которого path = s7, path родителя samsung, path его родителя = catalog. Я додумался только до того, чтобы генерировать вложенный sql-запрос. Например, для запроса /catalog/dvd/lg будет генерироваться такой:
select * from items where path = 'lg' and parent_id = (
__select id from items where path = 'dvd' and parent_id = (
____select id from items where path = 'catalog'
__)
);
Это очень плохое решение? Запрос генерирую так: http://ideone.com/pTS09b
Дом 2
https://jsfiddle.net/je858mz4/5/
N рабочих дней
> Автоматически рассчитать даты праздников, увы, нельзя, так как их переносят из-за выходных.
Я понял. Допустим, я получил, неважно как, список всех нерабочих дней в виде массива элементов вида 'd-m-y', решение будет примерно таким http://ideone.com/go0Mfo ?
Про алгоритмическую сложность отпишу отдельно, спасибо большое за ответ!
А еще в новом дизайне при выборе города в автокомплите клавишей enter, а не мышью, форма сабмитится. А еще дату нельзя ввести с клавиатуры, а можно только выбрать. Хотя я еще много лет назад делал виджет, в котором дату можно вводить в любом формате вроде "5 января" или "01.12.16"
Зачем ломать, то что нормально работает? Ну вот посмотрите сами: https://pass.rzd.ru/tickets/public/ru?layer_name=e3-route&st0=Санкт-Петербург&code0=2004000&st1=Москва&code1=2000000&dt0=12.01.2017&tfl=3&checkSeats=1
Мне наверно пора завести блог
Это же сурьезный бизнес, там никому не надо умно и удобно, там надо откаты и хоть что-то на презентацию.
Но смотри, допустим, твой код выполняет пять операторов подряд, которые ты заключил в try catch, и чтобы реагировать на ошибку каждого оператора отдельно, тебе придётся либо выбрасывать в каждом своё исключение и обрабатывать его в виде нескольких катчей, либо обёртывать каждый отдельный оператор в свой try catch. Разве это удобно?
Потому что почти в каждой функции (кроме самых простых) может произойти ошибка и писать if после каждого вызова функции утомительно. Более того, из-за необходимости проверять результат мы не можем вкладывать вызовы функций вроде
func1(func2(..))
Исключения решают обе этих проблемы. Функция либо возвращает результат, либо выбрасывает исключение, если не может его получить. А что делать с исключением - решает вызывающий.
В принципе можно возвращать false вместо исключения тогда, когда мы ожидаем, что может быть ошибка и пишем там if. Но для каждой функции это делать утомительно.
Ну и на практике пропускают проверки. Посмотри сколько только в этом треде кода без проверки результатов вызова.
В большинстве случаев исключения никто и не ловит. В случае 5 операторов подряд, может быть и так, что выгоднее возвращать false, а уже в коде, где они вызываются, выбрасывать однотипное исключение в каждом из 5 случаев. Но можно и try/catch. В этом случае в других местах, где вызываются эти функции, нам if писать не придется.
И еще надо помнить, что если мы пишем throw в функции чтения файла, то мы выбрасываем например IOException, а если throw в функции загрузки пользователей из файла - то уже пишем UserLoadException. Хотя во многих случаях, когда мы точно не будем ловить исключение, можно использовать и просто Exception.
1. В PHP функции, не выбрасывающие исключение при исключительных ситуациях как минимум уродливы тем, что часть из них возвращает null, а часть - false.
http://php.net/manual/en/function.json-decode.php
http://php.net/manual/en/function.mkdir.php
2. Не надуманный пример с 5-ю ифами, а рельная задача - нужно сделать так, чтобы все ошибки логгировались. В случае с исключениями можно сделать один try/catch на уровне фронт-контроллера и логгировать ошибки. Без исключений нужно постоянно помнить о том, что возвращает функция в случае ошибки, как получать сообщение об ошибке (где-то json_last_error, где-то curl_error) и вручную логгировать, используя копипаст по всему проекту. Можно, конечно, сделать логирующий декоратор, но ему ещё нужно знать, какая функция что возвращает при неудаче + этим декоратором будет усыпан весь проект, вместо одного try/catch на самом верхнем уровне.
https://jsfiddle.net/bv1gsbqw/
Предупреждаю, что я в вёрстке полнейший довн, htmlbook.ru читаю второй день.
>И еще надо помнить, что если мы пишем throw в функции чтения файла, то мы выбрасываем например IOException
В том то вот и плюс. Ты можешь в нормальном виде отобразить код ошибке, ее текст и проконтролировать поведение кода в случае исключения одновременно. В то время как ловля ошибок ифами это уродство и костыль. Зачем вообще это делать, если есть специальный, удобный инструмент исключений?
Осторожно, политота:
Не понимаю смехуечков. Что такого смешного в том, что бы гебня РФ использовала хакиров? Да господи, я знаю случаи вербовки хороших айтишников Службой Безопасности Украины, чего уж тогда про РФ говорить? Тем более, если уж посмотреть на тех же FancyBears, то их жертвы внезапно прохоядят по категории "врагои Россиюшки"
ой, все нормально, прошу прощения, как теперь совместить и css и хтмлом?
я в блокноте все делаю
2016-12-31 14:50:59 --------------------------------------------
2016-12-31 14:50:59 Начало процедуры запуска сервера
2016-12-31 14:50:59 Файл C:\Windows\system32\drivers\etc\hosts недоступен для записи
2016-12-31 14:50:59 Отключите использование HOSTS файла или настройте права доступа
2016-12-31 14:50:59 Сбой запуска!
Кстати возвращать null (в примере с json_encode) плохо еще тем, что причину ошибки надо получать отдельной функцией.
Насчет логгирования - обычно функции, которые возвращают false еще и выдают какую-то ошибку (которая идет в логи), но так делают не все. С системой ошибок конечно в PHP полный бардак.
>>904093
Более того, в своем классе исключений можно делать дополнительные поля, например для хранения отдельно имени файла, который не удалось открыть или еще чего-то.
>>904098
Да в общем-то многие развитые страны так делают. Можно вспомнить предположительно Израиль, уничтоживший трояном иранские центрифуги. Странно было бы не иметь нужных специалистов когда компьютеры так широко используются.
Смотрите. Снимаю галку, он требует права админа, нажимаю продолжить...
Открываю снова свойства - снова галка стоит! Что за бред? Я за администратора же зашел.
Мало быть администратором, начиная с шиндоус7 надо быть СУПЕР-АДМИНИСТРАТОРОМ. А в десятке этот марразм до пиздеца довели. Из-за этого я с десятки в итоге перешл на семерку, а когда шин7 перестанут поддерживать перейту на прыщи. Ибо блевать тянет от этого идиотизма от майкрософт.
Вот, зашел глубже. Как сделать чтобы эти галочки подсвечены были? Это в свойствах - безопастность,
У меня 8.1.
Вот, основная суть, смотри на пик - все серое. И при этом у меня от админа учетка.
Я уже всех заебал наверное. Админа получил, права выставил, комп перезагрузил, включил - запустил сервер от администратора, нажимаю включить - и опять тоже самое.
Ты не пробовал читать инструкцию по установке твоей чудо-сборки? И кстати, лучше было бы не использовать сборки, а в первый раз самому все руками поставить. скорее всего чудо-сборку надо запускать от имени админитратора.
hosts.txt?
> В большинстве случаев исключения никто и не ловит.
>>904093
То есть их назначение, с твоей точки зрения, просто информировать о сбое? Но непойманное исключение прекратит выполнение программы, причём — покажет голую информацию о коде пользователю, разве это допустимо?
Представь, что у тебя в участке кода вызывается несколько методов класса, в каждом из которых может возникнуть ошибка, с которой нужно будет предпринимать какое-то действие: изменить порядок будущего исполнения программы или её свойства, вывести сообщение пользователю, откатиться на ранее сохранённый (безопасный) этап работы с сайтом. Чтобы различать ошибку каждого отдельного метода, придётся либо использовать много try..catch'ей, либо вызывать в каждом методе свой тип исключений, либо вызывать один тип исключений с разными кодами в них (и потом использовать switch для выбора конкретного действия); мне кажется, в такой ситуации простейший if, проверящий успех/неуспех (false) операции, выглядит проще и красивее.
> В случае с исключениями можно сделать один try/catch на уровне фронт-контроллера и логгировать ошибки.
А если ошибку нужно не просто логгировать, а предпринять с ней какое-то действие? Причём на том участке кода, который применяет метод, в котором произошла ошибка?
решение задачи Minesweeper.MVC в процессе:
https://github.com/enotocode/minesweeper.mvc
В такой ситуации все исключения просто передают наверх, контролирующему коду.
Так что трукатчить приходится один раз на модуль, при вызове цепочки функций.
А тот код уже решает, что делать: возможна ли дальнейшая работа, нужно ли передать исключение выше, нужно ли падать, или можно перезапускать модуль до победного (например ожидая пока сеть не восстанет), или же можно просто проигнорить неотработку модуля и изменить поведение программы.
бля посоны, взяли макакой под вордпресс, а у меня там все отваливается типа скриптов и всплывающих окон.
Всё принял к сведению. Angular зацепил. Жаль, мало бесплатных курсов. Четвертый день сижу с красными глазами, спасибо, ОП!
используй цикл, люк
Очевидно же что нет. Как ты вложенные уровни сканировать без рекурсии собрался?
теперь не нужно вспоминать, что ты правил в файле когда комитишь. Ващеее...
http://ideone.com/6D5Zim
Как вариант, но c рекурсией проще.
Вот что ответил ОП в прошлом треде: http://arhivach.org/thread/216627/#898482
"Также, можно написать код без объектов Node и рекурсии, используя стек (стек для хранения записей, которые мы позже выведем, после того как разберемся с их детьми). Алгоритм примерно такой:
- кладем в стек все записи, у которых parentId === null (корни деревьев)
- цикл {
- снимаем из стека верхнюю запись
- выводим
- ищем в массиве всех ее детей по parentId
- кладем их в стек
}
Вообще, многие рекурсивные алгоритмы можно заменить на алгоритм с циклом и стеком или очередью. Но он может стать сложнее."
В PHP есть структуры данных стек и очередь: php.net/manual/en/spl.datastructures.php , но можно обойтись и массивом.
Кому интересно, собираю простые задачки на деревья/рекурсию, они отсортированы здесь по возрастанию сложности: http://pastebin.com/raw/SppMQayQ
Напомню ОПу про своих студентов. Если не тяжело - отпишись когда сможешь их посмотреть, что бы я зря тред не рефрешил.
Напомню ОПу про своих студентов. Если не тяжело - отпишись позязь когда сможешь их посмотреть, что бы я зря тред не рефрешил.
порешаю задачки. спасибо
1) По условию требуется, чтобы программа выводила NIL, если элемент не найден.
2) У тебя программа ищет ещё и дубликаты, а в условии написано "Выход: индекс", то есть один элемент. Для выхода из функции можно использовать return. Ну и функцию лучше бы назвать linearSearch, сортировка вставками это совсем другое.
Так лучше.
>>904614
> теперь не нужно вспоминать, что ты правил в файле когда комитишь.
Это просто визуализация git diff, такие плагины есть под каждый нормальный редактор кода.
Ага, позже нашел подобное в Komodo и NetBeans. Но таки в Visual Studio Code реализовано очень круто.
Вообще программа очень шустрая, дистрибутив весит мало и работает без установки. Приятный отзывчивый интерфейс, особенно в сравнении с бобами и штормом. Можно обмазываться плагинами, куча их. В общем все как ты любишь, Анон. Серьезный конкурент Атому, Саблайму и прочим Нотпадам++. Рекомендую к ознакомлению.
Я посмотрел немного кода по первой задаче, но там пиздец.
У всех в Student.php свойства public. Только один написал геттеры/сеттеры, но назвал методы не setId, а insertId (где он вообще видел такое???)
прочитал док и не понял: в php 5 есть вообще модификаторы доступа к методам и свойствам или они только в 7.1 появились?
Мне она показалась еще очень недоработанной. Опять же, необходимость обмаза плагинами - это говорит о недоработанности IDE.
>Мне она показалась еще очень недоработанной
В отзывах о последнем на данный момент релизе пишут, что по сравнению с предыдущим - небо и земля.
>необходимость обмаза плагинами - это говорит о недоработанности IDE.
Я не согласне с этим утверждением. Т.к. необходиость обмазываться плагинами - это особенность, а не показатель недоработанности. Такой подход позволяет настроить программу следуя индивидуальным предпочтениям. Популярность Саблайма и Нотпада+ пример тому.
> прочитал док и не понял: в php 5 есть вообще модификаторы доступа к методам и свойствам или они только в 7.1 появились?
Область видимости для методов и свойств доступна с 5-й версии. В 7.1 появились модификаторы доступа для констант класса.
Геттеры/сеттеры это не самая главная часть инкапсуляции. Инкапсуляция - это о том, как прятать детали реализации и выставлять наружу лишь интерфейс: http://learn.javascript.ru/internal-external-interface
А сущность Студент это ведь глупое хранилище данных, какие там детали реализации? Валидируется оно другим объектом, заносится в базу - тоже.
Я руководствовался такими соображениями, когда сдавал своих студентов. Хотя, сейчас бы наверное использовал геттеры/сеттеры.
>Я не согласне с этим утверждением. Т.к. необходиость обмазываться плагинами - это особенность, а не показатель недоработанности. Такой подход позволяет настроить программу следуя индивидуальным предпочтениям. Популярность Саблайма и Нотпада+ пример тому.
Ну, это на любителя. А мне не нравится, что несмотря на заявленную поддержку пхп надо для работы с ним еще обмазываться плагинами, в то время как в нетбинс или пхпшторм это все искаропки. Кстати грузится оно у меня дольше чем визуал студия 15, лол.
Мне нетбинс понравился, следующий проект буду в нем делать.
А ты чаще полузуешься, нетбинсом или штормом? Полноценная визуал студия не устраивает?
https://github.com/enotocode/minesweeper.mvc
function __construct(\PDO $connection){
$this->db=$connection;
}
в другом
public function __construct(PDO $pdo) {
$this->$pdo = $pdo;
}
на какой стул сесть? почему в одном случае есть \ перед PDO, а в другом его нет
и почему это работает в обоих случаях?
и почему мне автоподстановка предлагает писать вот так:
catch (\PDOException $exc)
как это заимпортить, чтобы не требовало подставлять неймспейс руками?
да на опечатку забей. суть в операторе \
то есть, можно его не писать к классам стандартной библиотеки?
просто единообразия нет и я путаюсь
в каком смысле надёжнее? я нагуглить не могу где бы это объяснялось нормально
в джаве такой хуйни не было
Я сам не пойму, т.к. шарю точно не больше твоего. Хотя кажись если ты вызываешь какой-то стандартный класс из из своего класса, то без слешей не работает, по крайней мере у меня тут
http://ideone.com/9iSzfm к примеру без слешей точно не работает
Оператор use. Но в данной ситуации (т.к. мы вызываем класс из корневого неймспейса) лучше использовать \PDO.
Книга Мэта Занстры из шапки Начало Главы 5 можешь глянуть
> я нагуглить не могу где бы это объяснялось нормально
http://php.net/manual/ru/language.namespaces.php
> почему в одном случае есть \ перед PDO, а в другом его нет
Если обратного слеша нет и всё работает - значит ты находишься в контексте глобального пространства имён. То есть вверху файла не указан неймспейс. Но если ты добавишь неймспейс для файла, к примеру Foo, то тайп-хинт PDO будет восприниматься PHP в пределах этого неймспейса как Foo\PDO. Поэтому для встроенных классов лучше использовать обратный слеш как указание на то, что класс нужно искать в глобальном пространстве имён (если ты в будущем планируешь использовать неймспейсы в проекте).
Интересно. А обычные функции пхп находятся в каком-нибудь пространстве имен?
Да, они находятся в глобальном пространстве имён. Можешь попробовать вызвать любую встроенную функцию, предварив её обратным слешем.
http://ideone.com/WAc2mO
Интересно, внезапно у меня и без указания глобального неймспейса работает.
Потому что неймспейс там и так глобальный. Если задашь свой - работать не будет без указания глобального.
А, точно, забыл.
я ващпе rookie тут у вас здеся, и не очень прохавал, как именно работает апач и какие функции выполняет. (если он тут вообще каким то боком связан). В качестве web-сервера использую XAMPP, если это имеет значение.
Говорить что ты используешь XAMPP в качестве веб-сервера - не правильно. XAMPP это сборка из веб-сервера, интерпретатора PHP и чего-нибудь еще. У тебя функции веб-сервера выполняет апач, именно он отвечает за то, какую страницу покажет браузеру и при вводе URL, и те вещи которые ты описал.
https://github.com/codedokode/pasta/blob/master/soft/web-server.md
Ознакомься с вот этим уроком, там описано как работает браузер, сервер и протокол с помощью которого они общаются друг с другом
>Поясните за index.php в корневой директории папки с сайтом. Почему он вызывается по умолчанию при вводе url сайта в браузере? Типа мой апач так настроен, что при вводе URL сайта по дефолту запускается index.php если таковой имеется, и дерево папок и файлов, если такого нет?
Да. Точнее не по дефолту, а если ты заходишь на корень сайта.
>Где и как это можно изменить, если, например, я хочу, чтобы по дефолту вызвался huindex.php?
А вот не помню, где-то в настройках апача же. Только зачем тебе это? Лучше почитай про правила в .htacces
Я изучал по этому учебнику http://archive-ipq-co.narod.ru/
Дошел до массивов.
Затем обнаружил этот http://php720.com/, здесь тоже дошел до массивов.
Собственно, хочу спросить - какой лучше, где чем отличается?
И еще. Возможно ли на моём этапе заняться хоть какой-либо практикой? Что вообще здесь можно сделать? Я заучиваю эти команды, функции, операторы, как найти определенную строку, то да се. А смысл то? Я чувствую как все это уже вытекает из меня.
Практики хочется. К примеру, пхп это же язык для сайтов? Возможно ли сайт что-ли сделать? Найти какой-нибудь бесплатный или дешманский хостинг, я не знаю.
>Я чувствую как все это уже вытекает из меня.
Ну это норм, только заучивать не надо, надо практиковаться и тогда все само дойдет.
>Практики хочется. К примеру, пхп это же язык для сайтов? Возможно ли сайт что-ли сделать? Найти какой-нибудь бесплатный или дешманский хостинг, я не знаю.
Читай уроки ОПа, как сделаешь самые простые задачки приступай к студентам. Вот там напрактикуешься вдоволь. Задания выполняй на локальном сервере.
А что по учебникам?
Тот, который в оп-посте - более дружелюбный, и объясняют лучше. В том, который я обнаружил - больше инфы, но она какая-то сухая, и объясняют так, что раз с 10 догонишь, что хотят донести.
Все понял, благодарю.
После выделенной команды(функции, оператора)
Весь выводящийся ( кстати, как это называется, ну, то, что выводится из моих команд?) сместился на середину. Как это исправить? Вернуть после CMD команды на показ моего содержимого диска все налево?
Проблема решена, это я дурак.
Господа, как можно этот запрос короче на чистом сокле написать? Мне надо чтобы поля, чьи плейсхолдеры равны нулям, вообще не выбирались, а оставшиеся поля выбирались как обычный запрос.
рейтаните сокл запрос. на работе написал.
http://ideone.com/kEdhTe
>Тут надо проставить внешние ключи
Классная штуковина, не знаю как без нее раньше обходился. Остановился на таком:
https://github.com/fidnex/filehost/blob/master/dump.sql#L24
>Еще мне не очень нравится такая штука:
Без нее функция path_for во вьюшке не хочет работать.
Спасибо за время твое, к февралю остальное разберу.
Так как количество параметров произвольно, то одного SQL вряд ли хватит, нужно строить запрос средствами PHP. У меня попроще вышло: http://ideone.com/ZCcvJr
Чтобы вызвать $this->db->placehold c произвольным количеством параметров можно использовать call_user_func_array или spread оператор.
>>906481
У него параметры функции могут быть null'ами, а не значения в базе.
>11. дан список вида «страна, город, население»
https://jsfiddle.net/50cncv5L/1/
>12. Некая сеть фастфудов предлагает несколько видов гамбургеров
https://jsfiddle.net/y28h2o2b/2/
>13. В одном городе есть электрическая сеть. К ней могут быть подключены
https://jsfiddle.net/6591a2sL/3/
>> someTypeOfSolarPanel.prototype = Object.create(SolarPanele.prototype);
>ты тут делаешь наследование, но тогда хорошо бы в someTypeOfSolarPanel() вызывать конструктор класса-предка. А то кто-то допишет в него код (или модифицирует), а потом будет гадать, почему этот код не срабатывает в наслднике. Если нет каких-то причин не делать этого, то стоит вызвать конструктор предка. То есть конструктор предка заполняет нужные ему поля, конструктор наследника - нужные наследнику.
Я предпологал что отдельный тип панели, это тот в котором "железно" определена мощность. Если этой причины не достаточно, то я просто переопределю конструктор класса-предка написав:
function someTypeOfSolarPanel(power) {
SolarPanele.apply(this, arguments);
}
>> for (var element in electricalnetwork.elements) {
>> if (electricalnetwork.elements[element] instanceof PowerLine) {
>Если делать это циклом то нет гарантии что мы продадим энергию по максимальной цене. Лучше отсортировать предложения по выгодности и продавать начиная с самой дорогой линии.
Теперь можно написать программу по расчету самого выгодного предложения. Линия электропередач может посчитать какую сумму можно получить за определенное количество ватт PowerLine.countPrice(power), учитывая свою пропускную способность. Осталось только поместить эти данные в массив и отсортировать его.
Но не будет ли это уже другой задачей?
У меня, кстати, есть решение по которому я сначала делал эту задачу, где происхдоит "имитация" торговли: https://jsfiddle.net/qvdz8y5j/
Такой вариант задачи мне нравится больше, потому что мы не просто считаем возможные варианты прибыли\мощностей, а действительно меняем систему.
>14. напиши функцию, определяющую тип переменной. Результат должен быть одной из строк: 'undefined', 'boolean' (для true/false), 'null', 'number', 'string', 'function', 'array', 'array-like', 'object'
https://jsfiddle.net/5q3r473h/3/
>15. Напиши функцию неглубокого копирования объектов и массивов
>> var clone = new object.constructor;
>Тут есть подвох: JS не гарантирует что свойство хранит ссылку на фукнцию-конструктор. Оно хранит ее по умолчанию, но разработчик может перезапистаь его, а также забыть правильно прописать его при наследовании. Ну я думаю, что это можно оставить, но стоит помнить про такие вещи. В самой задаче копировать до такой степени не требовалось.
Значит в js нету универсального решения чтобы получить имя класса - нужно писать самому под каждый отдельный случай?
>> for (property in object) {
>Ты склонируешь не только свойства объекта, но и всех его прототипов. если у объект в прототипе есть метод, он будет перебран в этом цикле (у встроенных в JS объектов вроде массивов методы защтщены от этого и не перебираются циклом, а у пользовательских классов перебираются).
Так это же хорошо что клонируется и прототип объекта, разве нет? Я думал, что прототип, это что-то, что принадлежит каждому образцу класса\объекта. Разве это не нужно клонировать?
>16. Напиши функцию глубокого копирования объектов и массивов
https://jsfiddle.net/j8pydqsg/1/
>11. дан список вида «страна, город, население»
https://jsfiddle.net/50cncv5L/1/
>12. Некая сеть фастфудов предлагает несколько видов гамбургеров
https://jsfiddle.net/y28h2o2b/2/
>13. В одном городе есть электрическая сеть. К ней могут быть подключены
https://jsfiddle.net/6591a2sL/3/
>> someTypeOfSolarPanel.prototype = Object.create(SolarPanele.prototype);
>ты тут делаешь наследование, но тогда хорошо бы в someTypeOfSolarPanel() вызывать конструктор класса-предка. А то кто-то допишет в него код (или модифицирует), а потом будет гадать, почему этот код не срабатывает в наслднике. Если нет каких-то причин не делать этого, то стоит вызвать конструктор предка. То есть конструктор предка заполняет нужные ему поля, конструктор наследника - нужные наследнику.
Я предпологал что отдельный тип панели, это тот в котором "железно" определена мощность. Если этой причины не достаточно, то я просто переопределю конструктор класса-предка написав:
function someTypeOfSolarPanel(power) {
SolarPanele.apply(this, arguments);
}
>> for (var element in electricalnetwork.elements) {
>> if (electricalnetwork.elements[element] instanceof PowerLine) {
>Если делать это циклом то нет гарантии что мы продадим энергию по максимальной цене. Лучше отсортировать предложения по выгодности и продавать начиная с самой дорогой линии.
Теперь можно написать программу по расчету самого выгодного предложения. Линия электропередач может посчитать какую сумму можно получить за определенное количество ватт PowerLine.countPrice(power), учитывая свою пропускную способность. Осталось только поместить эти данные в массив и отсортировать его.
Но не будет ли это уже другой задачей?
У меня, кстати, есть решение по которому я сначала делал эту задачу, где происхдоит "имитация" торговли: https://jsfiddle.net/qvdz8y5j/
Такой вариант задачи мне нравится больше, потому что мы не просто считаем возможные варианты прибыли\мощностей, а действительно меняем систему.
>14. напиши функцию, определяющую тип переменной. Результат должен быть одной из строк: 'undefined', 'boolean' (для true/false), 'null', 'number', 'string', 'function', 'array', 'array-like', 'object'
https://jsfiddle.net/5q3r473h/3/
>15. Напиши функцию неглубокого копирования объектов и массивов
>> var clone = new object.constructor;
>Тут есть подвох: JS не гарантирует что свойство хранит ссылку на фукнцию-конструктор. Оно хранит ее по умолчанию, но разработчик может перезапистаь его, а также забыть правильно прописать его при наследовании. Ну я думаю, что это можно оставить, но стоит помнить про такие вещи. В самой задаче копировать до такой степени не требовалось.
Значит в js нету универсального решения чтобы получить имя класса - нужно писать самому под каждый отдельный случай?
>> for (property in object) {
>Ты склонируешь не только свойства объекта, но и всех его прототипов. если у объект в прототипе есть метод, он будет перебран в этом цикле (у встроенных в JS объектов вроде массивов методы защтщены от этого и не перебираются циклом, а у пользовательских классов перебираются).
Так это же хорошо что клонируется и прототип объекта, разве нет? Я думал, что прототип, это что-то, что принадлежит каждому образцу класса\объекта. Разве это не нужно клонировать?
>16. Напиши функцию глубокого копирования объектов и массивов
https://jsfiddle.net/j8pydqsg/1/
Анон, а ты уже начал сапера делать? А файлообменник сделал? Сколько ты занимаешься по курсу ОПа?
На многих видеотубах можно встретить такой адрес: domenname.com/video/34054
Что здесь что вообще? video это контроллер? А какой тогда запускается action? Не может же быть в контроллере action под каждый id видео. Или video это имя переменной а 34054 это её значение и обрабатывает этот запрос какой-то контроллер и его экшен по умолчанию?
Скоро начну и сапера делать. С файлообменником ещё нет, ещё не начинал даже. Занимаюсь года 3.
>domenname.com/video/34054
Можно так определять параметр. Например, в микро-фреймворке слим можно делать вот так:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require 'vendor/autoload.php';
$app = new \Slim\App;
//get() - метод, который в слиме делает разбор строки запроса
$app->get('/video/{name}', function (Request $request, Response $response) {
$name = $request->getAttribute('name'); //получаем название видоса
/ ну ты понел, тут стандартная работа с базой, шаблонами и прочее /
});
$app->run();
Ютуб вроде на джанго написан, там такой роутинг делает
Я согласен. Где можно завербоваться?
быстренько накидал вариант на компе с экранной клавой
http://ideone.com/G5xn2Q
будет ли корректно работать call_user_func_array([$this->db, 'placehold'], $args);?
Опечатка: $args определил неправильно, нужно было explode(...)[1]."=?"
http://ideone.com/SNttHo
Я думал таким образом будет передаваться объект с твигом в колбек get'a, но не работает.
$app->get("/", function (Twig_Environment $twig) {
echo $twig->render('index.php');
});
http://ideone.com/EcokkU
Суть такова - почему цикл не перебирает каждый элемент массива? Он вообще не работает.
Строки должны быть либо внутри ', либо ". У тебя почему-то используется `
Советую таки использовать IDE, а то ебли с синтаксисом много будет как у любого ньюфага.
> Как нормально интегрировать слим и твиг?
За тебя уже интегрировали: https://github.com/slimphp/Twig-View
Это пакет-надстройка, которая пишет в тело PSR7-ответа то, что отрендерил твиг:
> $app->get("/", function (Twig_Environment $twig) {
> echo $twig->render('index.php');
> });
Вот дефолтная стратегия, определяющая аргументы обработчиков роутов: https://github.com/slimphp/Slim/blob/3.x/Slim/Handlers/Strategies/RequestResponse.php#L41
Лучше початать исходник, а не ожидать магии от фреймворка.
Как вариант в твоём случае можно пробросить твиг в обработчик роута через замыкание. То есть вместо
> $app->get("/", function (Twig_Environment $twig) {
написать
> $app->get("/", function ($req, $res, $args) use ($twig) {
но это глупо, слим использует контейнер, поэтому всё, что тебе нужно - это зарегистрировать твиг в нём. Класс App создаёт/получает контейнер и привязывает его контекст к callable: https://github.com/slimphp/Slim/blob/3.x/Slim/App.php#L234
bindTo это аналог bind из JavaScript
Кстати есть ещё шаблонизаторы, использующие нативный синтаксис PHP, например Plates. Такие шаблонизаторы очень просты в интеграции и использовании и не требуют от тебя изучать новый синтаксис.
>Лучше початать исходник, а не ожидать магии от фреймворка.
Если честно, мне крайне тяжело читать исходники фреймворка. Ну, хоть документацию нормально понимаю, хоть и не дочитал ее, видимо того и не знал что там твиг уже есть.
>Как вариант в твоём случае можно пробросить твиг в обработчик роута через замыкание. То есть вместо
Но все же я не понял почему мой способ не сработал, объекты реквест и респонд передаются же.
Что делает слим, очень грубо и упрощённо:
class Request {}
class Response {}
handler(new Request, new Response); // Передаёт аргументы в созданный тобой хендлер.
Созданный тобой хендлер:
function handler(Twig $twig) {
____return $thig->render('template');
}
Очевидно, что в таком случае ничего не работает как минимум из-за тайп-хинтов. У Кантора про замыкания и контекст вызова хорошо расписано, ещё советую первые 6-7 задачек на JS из ОП-поста порешать. С чем тут ещё могут быть проблемы - не знаю.
Да вроде тоже ИДЕ, но впервые вижу её. Вижу, ты совсем нюфаня. Короче, ИДЕ - это как текстовый редактор (читай, блокнот), но с кучей фишек.
Блокнот <<< редактор (типа саблайм, нотпад++ и тп) <<<<<< ИДЕ.
Второе - подсветка, маленькие ништяки типа автозаполнения уже введённых команд, но ещё не идеха.
Третьи - шторм, нетбинс(хуёвый дизайн и меньше плюшек), - полноценные станции для работы. Можно прикрутить компилятор, есть поддержка всяких плагинов и дерево файлов. Короче, в ней можно работать с целым проектом.
>handler
Что за хендлер? Ну я понимаю, что создаются переменные с объектами реквест и респонс.
>Созданный тобой хендлер:
Это ты имеешь ввиду $app->get()? Все равно не понял почему я туда не могу так же передать твиг. Без тайп-хинтов все тоже самое.
> Что за хендлер?
Обработчик роута.
> Все равно не понял почему я туда не могу так же передать твиг.
Потому что аргументы хендлеру передаёшь не ты, а слим. Короче, ты бы не задавал таких вопросов если бы понимал замыкания и как передавать функцию в функцию. Как это исправить я написал в предыдущем посте.
>Потому что аргументы хендлеру передаёшь не ты, а слим.
Не понял. Метод get умеет принимать реквест и респонд?
> Короче, ты бы не задавал таких вопросов если бы понимал замыкания и как передавать функцию в функцию. Как это исправить я написал в предыдущем посте
Да, тут ты явно прав.
Я так понял, я должен гуглить это самостоятельно? Но как вообще подобное гуглить?
Все, сам догнал, оказалось так легко.
http://rutracker.org/forum/viewtopic.php?t=4353990
Поставил ПХПшторм - он там не читает русские буквы. Да и к ПХПдесигнеру привык как-то, он делает все то, что ты описал. Правда не знаю что такое компилятор и дерево файлов, но вроде переменные подсказывает, и такое.
лся несколько часов с jsfiddle, но так и не смог залить туда новую версию. Соответственно, не могу поделиться, товарищ >>901673 ! Накодил почти все пункты (с использованием AngularJS, так как он мне понравился, буду дальше его копать тоже.). Очень хочу узнать твое мнение, но не знаю как показать результаты.*
Упс, разметка поехала.
Мучался несколько часов с jsfiddle, но так и не смог залить туда новую версию. Соответственно, не могу поделиться, товарищ >>901673 ! Накодил почти все пункты (с использованием AngularJS, так как он мне понравился, буду дальше его копать тоже.). Очень хочу узнать твое мнение, но не знаю как показать результаты.
codepen.io, jsbin.com, jsfiddle.net и cssdesk.com
>Поставил ПХПшторм - он там не читает русские буквы.
Кодировки ептыть. Короче как начнешь юзать пространства имен поймешь в чем фишка.
>он там не читает русские буквы
Не называй это русскими буквами никогда. Это называется кириллица, и говорить "русские буквы" это признак тотального ньюфажества.
Javascript canvas
Там есть и шестилетние комментарии. Там все сильно устаревшее?
А это не надо, там обновляются, не сразу заметил.
Ребят посмотрите пожалуйста код, а то всем знакомым либо лень, либо просто не хотят помочь.
Я уже несколько часов пытаюсь заставить роутер работать.
Пытаюсь сделать базу mvc под задачу со студентами.
http://rgho.st/7B9SFlwq9
Он просто 404 выдает и из контроллера не рекваирит.
А на пустую строку после localhost выдает index.php =/
Я бы начал разбираться с гитом но я просто не могу пока этот код работать не заставлю.
Так что не суди строго чувак мне этот код снится уже.
Ошибки в синтаксисе искал, всё круто.
В рекваерах рылся и кажется проблема там но на ларакасте именно всё так же.
сначала идет +7 или 8, за ними ровно 10 цифр, между которыми может быть любое число скобок, минусов, пробелов
Вот с этим вот "между" проблема. В качестве решения использую [()\-\s] , но это приходится вставлять несколько раз по ходу регекспа — некрасиво.
В итоге получилось нечто уровня /^(\+[()\-\s]7)|8[()\-\s]([0-9][()\-\s]){10}$/u
Есть два вопроса: есть ли способ реализовать это "между" лучшим путём, чем вставкой кода "эти символы могут встречаться, а могут и нет" после каждого обычного закодированного символа?
Второй вопрос: в начале регеспа написал (\+[()\-\s]*7)|8, то есть фактически (\+7)|8, вроде бы всё чётко, но почему-то номер +8 234 5678901 прошёл регексп. Если кому-то здесь не лень возиться с регеспами, не подскажите, как так вышло?
Код: http://pastebin.com/2gFPL7Jy
Протоколы https и http. Разницу между ними можешь нагуглить. Язык можно переключить на сайте, или просто зайти на https://secure.php.net/manual/ru/
>Увы, пока неправильно.
Вроде, после прочтения в гугле, начинаю что-то понимать, а не думать на угад.
Единственное непонятно почему в 3-ем примере возвращается window а не z, ведь fn это ссылка на метод объекта.
>>898828
>> Нужно стараться решать самому
>Да, верно, нужно самому гуглить, искать документацию, так как например на работе никто не будет за тобой по пятам ходить и решать проблемы за тебя. Но если ты не смог сам найти решение, то наверно смысла дальше ждать нет. Можно попросить подсказку или спросить, в каком направлении дальше гуглить. Мы тут все-таки не злодеи, поможем, тем более если человек сам старается найти решение.
Я должен пройти по сложному пути чтобы в будущем быть сильнее. В прошлый раз меня это научило тому что преодолевая трудность, я экономлю больше времени, чем не не делая этого.
Классы не должны создавать другие классы, потому что тогда возникает тесная связь - это очень плохо, код становится сложно расширяемым. Чтобы этого не было, мы юзаем Dependency Injection - классы просто получают готовые экземпляры классов и сами ничего не создают.
Все хорошо работает, пока у нас есть только index.php и пара классов. Но когда приложение становится сложным, у нас появляются контроллеры, рутеры, классы бд, вьюхи и еще куча всего.
Все это хозяйство должно создаваться в бутстрап файле, откуда расходится по соответствующим классам через Dependency Injection. Бутстрап файл быстро раздувается, там куча всяких параметров и даже ненужных классов, которые не юзаются. К примеру мы вызвали контроллер страницы 1, а создаются контроллеры и цепочка классов для страницы 2, которые потом не используются. Это естественно все не очень хорошо. Или мы хотим поменять один из классов, который получает один из контроллеров, тогда надо лезть в этот бустрап код, искать где создается класс, менять его на другой, создавать для него всю цепочку зависимостей. Все эти проблемы решает Dependency Injection Container. Там просто все зависимости описаны в явной форме. Захотел поменять класс - описал его в конфиге вместе с зависимостями, и поменял там же в конфиге у контроллера завимость на новый класс. Все работает, старый код мы не меняли. Кроме того Dependency Injection Container не создает всю тучу классов как бутстрап файл, он создает только нужные для конкретного случая. Страничка будет грузиться быстрее, меньше памяти съедать.
Максимум криво написано. Чому bootstrap не юзает композер для создания классов? Зачем все эти require в коде? Метод load в роутере получает файл, но создает сам себя? Пиздец логика. routes.php работает с переменной, про которую вообще ничего не знает? Бляя, я уже за голову хватаюсь. Все максимум криво.
Карта роутов должна быть либо в конфиге, либо в самом роутере прописана, либо отдаваться роутеру посредством dependency injection и интерфейса. Но никак не через этот кривой механизм с require и неизвестной переменной.
Алсо в таблице роутов должны быть имена классов, а не .php файлов, это тоже максимум криво.
Композер завтра подключу.
А в лоаде я это заюзал
http://php.net/manual/ru/language.oop5.late-static-bindings.php
А про остальное я хз и почему это не работает тоже.
Это php шлюхи, они с тимлидом спали.
Композер сразу подключают, тогда всех этих десятков require у тебя в коде не было бы. Late static binding там вообще не нужен, у тебя нет классов, наследующих от роутера. new self() вполне хватило бы.
Не работает из-за кривых роутов видимо, делай var_dump(trim($_SERVER['REQUEST_URI'], '/'));
Я ларакаст смотрю там он позже композер ставит.
А разве меж new self и static разница есть? У чувака оттуда new static.
Var_dump делал уже, чтобы протестить на обычный индекс возвращает '/' и роутер находитю
Я не смотрел твой ларакаст, у чувака скорее всего причина была, его использовать, может у него наследования там. У тебя не вижу, у тебя роутер единственный класс.
Композер сразу подключают, потому что потом тебе придется по всем файлам рыться и удалять эти require и менять все .php на названия классов.
Если в ларакасте сразу не подключили, то чувак просто не понимает смысл композера.
Переписать метод Request:uri(). Он у тебя возвращает не то, что routes.php ждет. При наличии аргументов нужно парсить урл и извлекать часть. Я тебе для этого var_dump и сказал делать.
Точнее метод request:uri можно оставить, а в самом роутере уже метод добавить, который параметры извлекает из uri.
class Request {
public static function uri() {
return trim(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/'
);
}
public static function method() {
return $_SERVER['REQUEST_METHOD'];
}
Там сейчас такой код, чтобы гет запросы мог роутить.
А это в индексе
require Router::load('routes.php')
->direct(Request::uri(), Request::method());
Лол, этот код просто аргументы у uri съедает. Ты var_dump не делаешь что-ли? Вообще чего ты добиться пытаешься? Если чтобы /about на about редиректил, то тебе .htaccess сначала нормально написать надо.
У тебя там стоит просто index.php индексом считать, но про редиректы ничего нет.
RewriteEngine on
DirectoryIndex index.php
# If requested resource exists as a file or directory go to it
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule (.) - [L]
# Else rewrite requests for non-existent resources to /index.php
RewriteRule (.) index.php?route=$1 [L]
Вот так это делается. Тогда при вызове /about?arguments ты будешь получать редирект на index.php. В $_GET у тебя будет route=about, а аргументы ты можешь вытащить из REQUEST_URI.
Чувак я не шибко понял, что ты сделал но как только прописал у меня запахало!!1
Спасибо, долгой и счастливой жизни тебе.
Ты просто роутер делаешь, не представляя как оно все вместе работает. Если .htaccess не делает редирект, то тебе никакой роутер не поможет, апач его просто запускать не будет.
Надо будет погуглить как грамотно .htaccess настроить.
Recursiоn? Чья это кoнфа? Откуда там столько людей?
Но мне сказали что мол нельзя что бы родители что-то там знали о потомках, ВУТ? Что это блин значит и вообще как тогда наследоваться, если родителям не пихать общие для наследников методы / поля?
Утерян на старой пекарне, я переписываю задачу сейчас с 0
Класс ничего не может знать про своих наследников. Причина очень простая: когда ты пишешь класс, ты ведь не можешь заглянуть в будущее и угадать, кто и как будет писать классы-наследники.
Ты не можешь вызывать методы и обращаться к полям, которых нет в текущем классе.
Но есть специальный способ указать, что в наследнике должен быть определенный метод. Для этого надо сделать абстрактный метод - метод, у которого нет тела и который наследники обязаны реализовать, подробнее: http://php.net/manual/ru/language.oop5.abstract.php
Другой вариант - сделать в классе-предке реализацию метода по умолчанию и предоставить потомкам возможность переопределить его. В отличие от варианта с абстрактным методом, они это делать не обязаны.
>>907570
Подозреваю, потому что авторы тупо спамят все треды ей. Ну и владелец площадки вряд ли рад что кто-то пытается бесплатно переманить его пользователей.
>>907536
Звездочки потерялись в коде, скобки в регулярке не нужны.
> Тогда при вызове /about?arguments ты будешь получать редирект на index.php
Неточно. Редирект - это HTTP ответ с кодом 3xx, и визуально его можно увидеть по изменению адреса страницы в браузере. Тут не редирект, а rewrite (переписывание URL), просто настройка, как на сервере будет обрабатываться запрос.
Также, надо писать [L, QSA] чтобы не терялись параметры после знака вопроса в URL. route=$1 лучше убрать, исходный URL доступен в REQUEST_URI.
- https://habrahabr.ru/company/sprinthost/blog/129560/ - тут есть про QSA
- https://httpd.apache.org/docs/2.4/rewrite/flags.html#flag_qsa - оф док на англ
> А разве меж new self и static разница есть? У чувака оттуда new static.
Почитай в мануале про разницу https://www.google.ru/search?q=php+manual+static+self&newwindow=1&gbv=1&sei=xgRxWKpHw5GyAY2ftdAN
>>907523
Поправлю, подключают не композер, а сгенерированный им скрипт автозагрузчика классов.
>>907493
Почитай урок ОПа https://github.com/codedokode/pasta/blob/master/arch/di.md
>>907413
> Единственное непонятно почему в 3-ем примере возвращается window а не z, ведь fn это ссылка на метод объекта.
В JS нет такой сущности, как "ссылка на метод объекта". Там и методов-то нет - это просто функции, сохраненные в поле объекта. Потому конструкция
var fn = obj.method;
Просто копирует функцию из поля method в переменную. И при вызове fn() функция вызывается с this = window.
this определяется тем, какой конструкцией вызвана функция:
- obj.method() - this = object
- method() - this = window
- method.apply(x) - this = x
> Я должен пройти по сложному пути
Может тогда можно попросить какую-то дополнительную задачку, которая поможет разобраться? Или например можно спросить, что почитать по теме.
> А разве меж new self и static разница есть? У чувака оттуда new static.
Почитай в мануале про разницу https://www.google.ru/search?q=php+manual+static+self&newwindow=1&gbv=1&sei=xgRxWKpHw5GyAY2ftdAN
>>907523
Поправлю, подключают не композер, а сгенерированный им скрипт автозагрузчика классов.
>>907493
Почитай урок ОПа https://github.com/codedokode/pasta/blob/master/arch/di.md
>>907413
> Единственное непонятно почему в 3-ем примере возвращается window а не z, ведь fn это ссылка на метод объекта.
В JS нет такой сущности, как "ссылка на метод объекта". Там и методов-то нет - это просто функции, сохраненные в поле объекта. Потому конструкция
var fn = obj.method;
Просто копирует функцию из поля method в переменную. И при вызове fn() функция вызывается с this = window.
this определяется тем, какой конструкцией вызвана функция:
- obj.method() - this = object
- method() - this = window
- method.apply(x) - this = x
> Я должен пройти по сложному пути
Может тогда можно попросить какую-то дополнительную задачку, которая поможет разобраться? Или например можно спросить, что почитать по теме.
Знак | имеет очень низкий приоритет, то есть /a|bc/ значит /(a)|(bc)/, а не /(a|b)c/. У тебя получается выражение /(^+7)|(8xxxxxx)/. То есть неправильное, в общем-то выражение. И потому неправильный номер его прошел. Сгруппируй символы с помощью круглых скобок.
Также, после плюса минус идти не может.
Остальное в регулярке верно.
> В качестве решения использую [()\-\s] , но это приходится вставлять несколько раз по ходу регекспа — некрасиво.
Я не знаю способов, как обойтись без этого.
>>907384
Удобнее было бы выкладывать на сайты вроде github, чтобы мы могли просмотреть код не скачивая. Если кода немного, можно просто склеить в один файл или и выложить на pastebin.
Ну архив конечно лучше чем ничего.
>>907361
Потому что международный сайт, комментаторы со всего мира.
>>907358
Если есть возможность, лучше выкладывать код туда, где его можно просмотреть не скачивая.
Если не работает - ставь вар-дампы и эхо (или используй отладчик, если умеешь), чтобы увидеть, что выполняется, в каком порядке, и чему равны переменные.
Проверь, включен ли вывод ошибок, если нет, смотри логи Апача.
В конфиге стоит писать только параметры, которые может менять пользователь. Параметры вроде опций PDO пользователь менять не должен.
> require Router::load('routes.php')
> ->direct(Request::uri());
Непонятная конструкция, в первый раз такое вижу. Лучше это писать в несколько строчек, а не пытаться упихнуть в одну.
Также, советую поменьше использовать статические методы. Вместо Request::uri() лучше сделать обычный объект. Запросов ведь в теории может быть и несколько, или мы можем захотеть для теста создать временный объект запроса.
В путях надежнее указвать абсолютные пути.
Способ определения конфига в routes.php мне не очень нравится так как там непонятно, откуда берется переменная $router. Не лучше ли было хотя бы функцию с аргументом $router?
При отстутвии роута надо выдавать страницу 404, а не исключение.
Можешь объяснить назначение класса Connection? зачем он нужен, если мы можем сразу создать объект PDO? И почему тут опять статические методы? тут описаны их недостатки: https://github.com/codedokode/pasta/blob/master/arch/di.md#Чем-плохи-классы-из-статических-методов
Также, неправильно используются исключения при создании объекта PDO. Такой код явно пишут те, кто их не знает и привык к процедурному не-ООП коду. Вот у меня про них написано: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
Класс QueryBuilder на самом деле не построитель запросов, а скорее TableDataGateway. Неправильное название.
Знак | имеет очень низкий приоритет, то есть /a|bc/ значит /(a)|(bc)/, а не /(a|b)c/. У тебя получается выражение /(^+7)|(8xxxxxx)/. То есть неправильное, в общем-то выражение. И потому неправильный номер его прошел. Сгруппируй символы с помощью круглых скобок.
Также, после плюса минус идти не может.
Остальное в регулярке верно.
> В качестве решения использую [()\-\s] , но это приходится вставлять несколько раз по ходу регекспа — некрасиво.
Я не знаю способов, как обойтись без этого.
>>907384
Удобнее было бы выкладывать на сайты вроде github, чтобы мы могли просмотреть код не скачивая. Если кода немного, можно просто склеить в один файл или и выложить на pastebin.
Ну архив конечно лучше чем ничего.
>>907361
Потому что международный сайт, комментаторы со всего мира.
>>907358
Если есть возможность, лучше выкладывать код туда, где его можно просмотреть не скачивая.
Если не работает - ставь вар-дампы и эхо (или используй отладчик, если умеешь), чтобы увидеть, что выполняется, в каком порядке, и чему равны переменные.
Проверь, включен ли вывод ошибок, если нет, смотри логи Апача.
В конфиге стоит писать только параметры, которые может менять пользователь. Параметры вроде опций PDO пользователь менять не должен.
> require Router::load('routes.php')
> ->direct(Request::uri());
Непонятная конструкция, в первый раз такое вижу. Лучше это писать в несколько строчек, а не пытаться упихнуть в одну.
Также, советую поменьше использовать статические методы. Вместо Request::uri() лучше сделать обычный объект. Запросов ведь в теории может быть и несколько, или мы можем захотеть для теста создать временный объект запроса.
В путях надежнее указвать абсолютные пути.
Способ определения конфига в routes.php мне не очень нравится так как там непонятно, откуда берется переменная $router. Не лучше ли было хотя бы функцию с аргументом $router?
При отстутвии роута надо выдавать страницу 404, а не исключение.
Можешь объяснить назначение класса Connection? зачем он нужен, если мы можем сразу создать объект PDO? И почему тут опять статические методы? тут описаны их недостатки: https://github.com/codedokode/pasta/blob/master/arch/di.md#Чем-плохи-классы-из-статических-методов
Также, неправильно используются исключения при создании объекта PDO. Такой код явно пишут те, кто их не знает и привык к процедурному не-ООП коду. Вот у меня про них написано: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
Класс QueryBuilder на самом деле не построитель запросов, а скорее TableDataGateway. Неправильное название.
Просто это официальный сайт PHP и он давно существует.
>>907110
Я кстати вспомнил, что японцы назвают латиницу "йокомодзи", то есть дословно, буквы, которые пишутся горизонтально (а не вертикально).
>>907060
Надо в настройках phpstorm выставить правильную кодировку. Я советую utf-8, и если файлы не в ней, то перекодировать их.
>>906942
> с использованием AngularJS, так как он мне понравился, буду дальше его копать тоже
Не представляю, как использовать фреймворк для создания SPA приложений ради показа окон, так что интересно посмотреть.
>>906906
Квадратным скобками. В уроке вроде ведь написано.
>>906868
IDE = редактор кода + управление файлами в проекте + отладчик + умное автодополнение + иногла проверка синтаксиса + вызов интерпретатора + иногда управление сервером и тд. Этакий комбайн, содержащий в себе инструменты для работы над проектом. До появления IDE это было в виде отдельных программ.
Плохо, что ты не можешь читать исходники. Ты ведь этот фреймворк используешь и без этого какие-то аспекты его работы могут быть непонятны.
>>906784
Слим передает в коллбек определенные аргументы (Request, Response), а не то, что ты указал. Точно это описано в документации: https://www.slimframework.com/docs/objects/router.html#route-callbacks
Заметь, что если используется анонимная функция, то в ней $this указывает на DI контейнер:
> If you use a Closure instance as the route callback, the closure’s state is bound to the Container instance. This means you will have access to the DI container instance inside of the Closure via the $this keyword
Получить twig проще всего как $app->view или $app->twig или как-то так, то есть из встроенного в приложение DI контейнера. Про контейнер написано тут: https://www.slimframework.com/docs/concepts/di.html
Про подключение расширений для шаблонизатора (включая твиг) написано тут: https://www.slimframework.com/docs/features/templates.html
Все ссылки на англ, но с примерами кода.
>>906698
Вообще код оформлен плохо и тяжело читаемый. Например, зачем писать длинные строки, когда можно писать каждое действие на своей строке?
Вместо call_user_func_array([$this->db, 'placehold'] лучше сделать функцию, которая принимает массив значений.
Нет никакий защиты от SQL инъекций, то есть значения ключей массива вставляются прямо в запрос без проверок. Если это форма поиска то лучше указать возможные значения критериев поиска. Или даже сделать условия поиска в виде объекта.
Ничего не говорящие названия переменных вроде $a, $v. Зачем-то из массива удаляются элементы во время цикла по нему.
Непонятно, что делает explode. Что-то удаляет из строки?
Непонятно зачем там стоит return false. Кто-то будет проверять результат вызова? Скорее всего нет.
Название метода должно начинаться с глагола.
Плохо, что ты не можешь читать исходники. Ты ведь этот фреймворк используешь и без этого какие-то аспекты его работы могут быть непонятны.
>>906784
Слим передает в коллбек определенные аргументы (Request, Response), а не то, что ты указал. Точно это описано в документации: https://www.slimframework.com/docs/objects/router.html#route-callbacks
Заметь, что если используется анонимная функция, то в ней $this указывает на DI контейнер:
> If you use a Closure instance as the route callback, the closure’s state is bound to the Container instance. This means you will have access to the DI container instance inside of the Closure via the $this keyword
Получить twig проще всего как $app->view или $app->twig или как-то так, то есть из встроенного в приложение DI контейнера. Про контейнер написано тут: https://www.slimframework.com/docs/concepts/di.html
Про подключение расширений для шаблонизатора (включая твиг) написано тут: https://www.slimframework.com/docs/features/templates.html
Все ссылки на англ, но с примерами кода.
>>906698
Вообще код оформлен плохо и тяжело читаемый. Например, зачем писать длинные строки, когда можно писать каждое действие на своей строке?
Вместо call_user_func_array([$this->db, 'placehold'] лучше сделать функцию, которая принимает массив значений.
Нет никакий защиты от SQL инъекций, то есть значения ключей массива вставляются прямо в запрос без проверок. Если это форма поиска то лучше указать возможные значения критериев поиска. Или даже сделать условия поиска в виде объекта.
Ничего не говорящие названия переменных вроде $a, $v. Зачем-то из массива удаляются элементы во время цикла по нему.
Непонятно, что делает explode. Что-то удаляет из строки?
Непонятно зачем там стоит return false. Кто-то будет проверять результат вызова? Скорее всего нет.
Название метода должно начинаться с глагола.
Вообще нет, так как это довольно простая тема, есть только маленькая статья: https://github.com/codedokode/pasta/blob/master/js/ajax.md
Если нужны задачи, то вот они:
- сделай аякс-голосование за статью (лайк/дизлайк). Запрос отправляется на сервер и возможны разные варианты: голос принят, пользователь уже голосовал, пользователь не имеет права голосовать, и тд. Серверная часть не особо важна, можно просто сделать простой php-скрипт, возвращающий случайно один из вариантов.
- сделай форму с аякс- и обычными проверками полей по мере ввода данных. Желательно не хардкодить правила проверки, а например, передавать в код JSON-конфиг с правилами валидации полей, либо указывать их как data-атрибуты HTML-тегов, то есть сделать универсальное решение.
- если делать аякс-запросы с индикатором загрузки, выводом ошибок, то можно обнаружить, что каждый раз приходится копипастить похожий код. Попробуй написать функции, упрощающие отправку AJAX запросов.
Можно использовать jQuery, можно другие библиотеки. Дублирования кода надо избегать.
Проджект https://github.com/grigoryMovchan/blog_mvc
Шаблон https://github.com/grigoryMovchan/blog_mvc/blob/master/application/views/template_view.php
Разве не логично как раз дать возможность ставить ему прайват что бы нельзя было к нему обращаться $class::__consruct(); и прочее
private на конструктор ставят, чтобы нельзя было создавать класс через $instance = new Class(). Тогда тебе придется написать в классе функцию:
public static function fromParameters($parameter1, $parameter2) {
if (self::parametersAreValid($parameter1,$parameter2) {
return new self();
}
throw new \Exception('ошибка создания класса');
}
Т.е. класс можно будет создать только через специальный метод. И только если параметры пройдут валидацию, иначе кинет exception. Вариант с публичным конструктором ничего такого не гарантирует.
а зачем это вообще нужно? То есть выставка private на конструкторе делает по сути весь класс приватным? Что бы левый вася в коде не мог не просто к полям и методам твоего класса обращаться, а вообще не мог класс создать прям? И только если входные данные верны, то тогда даем возможность создать класс?
<link type="text/css" href="/../../css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="/../../js/bootstrap.min.js"></script>
У тебя пути неверные)
бля, ну попробуй путь целиком прописать, ну там http://localhost/blog/css/blabla.css и тд
лень поднимать твой проект у себя на локали, я в сериальчик залипаю
Есть предложение, с нефтью)
с путем целиком он конечно будет работать, я об этом и писал
не буду же для каждого файла так делать, а потом переписывать на другом хостинге
а хотя нет, с путем на другой сайт он будет работать, а на тот же нет, все равно все через роутер пойдет
>тот же нет, все равно все через роутер пойдет
бля, судя по хтакцсесу у тебя на index.php и, соответственно, на роутинг - уходят только те пути, по которым не найдено реальных файлов или папок. Если файл или папка по пути из запроса существуют - апач должен отдать их, а не реврайтить на index.php. Копай в эту сторону, если че - спрашивай
Да, именно так. Левый вася вынужден будет использовать наши статитеские функции для создания класса, и не накосячит, потому что там валидаторы. Если валидация не прошла, экземпляр класса он просто не получит.
Не могу понять как вытащить куки из $app. Делаю дамп и нахожу там ее значение, а request stack пустой и в сам request не пойму как пробиться. В общем. надеюсь исправить все до проверки опом.
https://github.com/anotherCodeMunkey/fileshare
сам подумай, какая разница между обьектом с приватными свойствами, но с геттером-сеттером для каждого свойства, и обьектом с публичными свойствами, но без дурацкого бойлерплейта геттеров-сеттеров.
инкапсуляция имеет смысл только тогда, когда у тебя нет сеттеров, а вместо них методы с поведением сущности. Если у сущности поведения нет - нафиг этот бред
Есть одна задача, она не хочет принимать моё решение - http://ideone.com/J4G8Ms. Почему?
Есть все в php5. Все свойства должны быть приватными или защищенными, иначе будет порнография с левыми васями, меняющими свойства в твоем классе как угодно. С сеттером ты по крайней мере имеешь контроль над поведением Васи, который не поломает функциональность класса и свой код. Если решишь вдруг, что свойство нельзя менять или менять можно только по определенным правилам - дописываешь просто проверку в сеттере.
> не хочет принимать моё решение
Так как твоя программа направильно находит символ для последовательности ["O", "Q", "R", "S"];
> $missing = ord($findMissingLetter[0]);
Но ведь в этой переменной не хранится код пропущенной буквы, а ты называешь её missing. $bd и $db вносят путаницу, как и цикл foreach, который не использует ни $key, ни $value. 2 счётчика не нужны, достаточно одного, который будет проходить поэлементно, сравнивать код текущего символа с кодом следующего и если их разница != 1, то
- получить код текущего символа, увеличить на единицу
- вернуть символ, соответствующий коду.
>Все ссылки на англ, но с примерами кода.
По поводу твига. Уже читал эту ссылку https://www.slimframework.com/docs/features/templates.html но так как тут описано у меня не работает. Команда composer require slim/twig-view не работает, говорит что чего-то не хватает(завтра могу скрин показать). Однако, я посмотрел в стандартном пакете слима уже есть твиг, и я попросту создал новый объект твига по инструкции из его документации, закинул в контейнер и юзаю в колбеке. При этом у меня нет в объекте app методов типа view или twig, получилось работать либо из контейнера, или из замыкания.
Алсо еще вопрос по слиму. В колбеке не работает стандартная ф-я header(), ну что бы сделать переадресацию после записи в БД. Т.е. раньше я делал по принципу "если через пост-запрос что-то пришло, то сделать запись в БД и перенаправить на ту же страницу с помощью header('Location: /'). Я так понимаю, тут это как-то по своему реализовано, как мне сделать правильно?
Был кривой .htaccess или просто его старый синтаксис. Правильный:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [NC,OR]
RewriteCond %{REQUEST_FILENAME} -d [NC]
RewriteRule .* - [L]
>> Единственное непонятно почему в 3-ем примере возвращается window а не z, ведь fn это ссылка на метод объекта.
>В JS нет такой сущности, как "ссылка на метод объекта". Там и методов-то нет - это просто функции, сохраненные в поле объекта. Потому конструкция
>
>var fn = obj.method;
>
>Просто копирует функцию из поля method в переменную. И при вызове fn() функция вызывается с this = window.
Но почему копирует? Функция это же объект и должна быть ссылка!
В мануале мазилы, кстати, сказано, что это методы, но наверно это так просто для простоты написано. Поэтому я их так назвал.
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/this#В_методе_объекта
>Может тогда можно попросить какую-то дополнительную задачку, которая поможет разобраться? Или например можно спросить, что почитать по теме.
Если у вас есть время, то давайте. Только не очень большую. Я в принципе начинаю понимать, просто нужно убедиться что нету пробелов в понимании, как например с примером выше.
Рад, что разобрался таки)
Как разрешить такую ситуацию? То есть объект я создаю 1 раз, а потом 2 ссылки на него могу добавить в массив. Как избегать такого? Или это из разряда о чем не стоит греть голову? И так нормально оставлять, ведь никто не будет пытаться 1 объект 2 раза затолкать в 1 и то же место?
Но ведь ты не создаешь ссылок я так понимаю, ты просто в свойство-массив departments добавляешь еще одну ячейку.
в чем проблема допилить метод addDepartment для проверки есть ли уже такой департамент, если есть - не добавлять/выкидывать exception?
А, ну вроде да. Так тебя беспокоит то, что таким образом можно добавлять одни и те же данные? Тогда сделай простую проверку же.
Тут бы еще по логике хорошо или исключение, или просто сообщение какое-то возвращать.
ну, это уже зависит от требований в конкретной задаче)
вот с исключением) http://ideone.com/UGbElq
как же я всё таки плох в программировании не смотря на то, что уже год работаю веб-мартыхой :(
Смотри какое я дерьмо нагородил и оно еще и не работает почему-то
http://ideone.com/8MjoiC
Наверное потому что нельзя форичить пустое своей свойство, в общем хоть с работы увольняйся и сиди дома вот так с анончиками постигай пробелы в пхп и ООП.
Алсо что касается второго твоего примера, я вот не совсем понял почему у тебя там нет ошибки. Ведь любой код насколько я знаю который может выкинуть исключения должен быть обернут в try блок (читал только об этом, сам не юзал)
У меня во всяком случае тупое выкидывание исключения давало эрор.
Ты там какой-то дикой хуйни нагородил. Алсо, я так понял не работает потому что ты сравниваешь по типу, т.е. раз ты проверяешь объект === объект то $exist всегда будет тру, лол. Попробуй просто по значению == сравнивать, хотя все равно не понятно зачем ты такую хуиту нагородил.
блять, хорошо еще, что не битрикс-мартыхой, пиздец, 2 месяца как устроился, а уже охуеваю от этого говна.
О, братишка. Меня один знакомый агитирует утроится к нему в говноконтору макакировать на битриксе. Только я сомневаюсь потому что из каждого утюга кричат что битрикс это лютейший говнокодище и явно не лучший выбор что бы начать карьеру. Можешь пояснить за суть?
ETO PIZDEC NE LEZ SOZHRET)
>>907997
http://ideone.com/UGbElq есть ошибка, хз где ты увидел что нету)
бля, я хз как в кратце рассказать про это говнище.
там просто все хуево и все не так, как должно быть в 2к17, лол. например, шаблон, который тянет компонент - может лежать примерно в 20 (!!!!!!!!) разных местах - и ты хуй когда узнаешь, где он, пока не переберешь все пути. ну там конструкторы в которые передается 25 переменных по ссылке - классика. мешанина логики, хтмл/цсс - на каждом шагу. ВСЕ БЛЯТЬ ЧЕРЕЗ ЖОПУ, ПОНИМАЕШЬ? открываешь блять какой-нибудь класс, а там хуяк - 2к строк кода с ифами уровней на 15 вложенности ОЛОЛО СУКА НЕ НАПОМИНАЙ БЛЯТЬ СКОРО НА РАБОТУ СУКА!!!11111))))))
Теорию впаривать не надо будет почти просто решать со мной задания ОПа, хочу файлообменник сделать своим первым пет проектом и в диплом его отправить.
Нихуя не понял как так. Там что, нет mvc? Зачем по ссылке столько данных передавать?
Даже я понял о чем он.
Это когда ты хуяришь функцию по типу:
public function save_user_to_db($id, $name, $email, $password, $avatar, $balance, $huy, $pizda, $jigurda) {
...
}
а когда таблица расширяется, то в эту функцию добавляют еще 1 параметр)))
нет, поверь, все НАМНОГО хуже)
Да я то понял. Только нахуя это все гет-запросом передавать?(я понял он это имел ввиду). И если данные так разрастаются - почему нельзя аргументы передавать массивом и нормально внутри метода это разбирать? Это же не тяжело такое по людски реализовать.
Вопрос наверное звучит глупо и труднообъяснимо, но иногда когда я пишу на своём неуважаемом в этом треде Codeigniter'e у меня возникает именно желание из модели вызывать другую модель, такие дела.
1 - 1
2 - 2
3 - 3
4 - 4
5 - 5
6 - 6
7 - 7
8 - 8
9 - 9
10 - A
11 - B
12 - C
13 - D
14 - E
15 - F
16 - 10 //вот тут интересненькое пошло
17 - 11
18 - 12
19 - 13
20 - 14
21 - 15
22 - 16
23 - 17
24 - 18
25 - 19
26 - 1A
27 - 1B
1 - 1
2 - 2
3 - 3
4 - 4
5 - 5
6 - 6
7 - 7
8 - 8
9 - 9
10 - A
11 - B
12 - C
13 - D
14 - E
15 - F
16 - 10 //вот тут интересненькое пошло
17 - 11
18 - 12
19 - 13
20 - 14
21 - 15
22 - 16
23 - 17
24 - 18
25 - 19
26 - 1A
27 - 1B
В школе разве не рассказывают про это? Просто я десять лет как окончил, может поменялось чего.
>>907780
private на конструкторе запрещает создание объектов за пределами класса. Мы можем сделать публичную статическую функцию и создавать (или не создавать) объекты через нее. Или может это класс, реализующий паттерн Utility class и его объекты нельзя создавать в принципе.
Проверку параметров можно сделать и в конструкторе. А вот статический метод позволяет например вместо создания объекта иногда делать что-то другое, например, возвращать ранее созданный (паттерн Singleton).
>>907789
Проверку параметров можно сделать и в конструкторе. А вот статический метод позволяет например вместо создания объекта иногда делать что-то другое, например, возвращать ранее созданный (паттерн Singleton).
>>907758
надо настроить htaccess, чтобы обращения к существующим файлам не обрабатывались в index.php. Обычно это флаги RewriteCond ... -f и -d. Также, можно определять файлы по расширению или по шаблону URL.
>>907871
Прочитай урок https://github.com/codedokode/pasta/blob/master/network/urls.md , в ссылке не обязательно указывать домен.
Кеши бывают разные, думаю речь о кеше метаданных. Доктрина читает информацию о замапленных классах и полях либо из yml-конфигов, либо из аннотаций в PHP комментариях. Процесс чтения не бесплатный и требует времени на разбор, и т.д. Соответственно есть идея кешировать эти метаданные, чтобы не читать их каждый раз при запуске скрипта.
Есть 2 режима:
- девеломпент (режим разработчика) - доктрина будет проверять дату модификации PHP файла и автоматически перегенерировать кеш при изменении или при отсутствии.
- продакшен (рабочий режим) - время обновления PHP файла не проверяется. Кеш сам не создается. Метаданные нужно создать явно, вызвав специальный метод подогрева кеша. Это можно делать например, скриптом, при деплое (выгрузке кода на сайт). При деплое мы вкладываем код в новую папку, генерируем в ней кеш и переключаем веб-сервер на эту папку. А затем удаляем старую.
Есть и другие кеши, например, кеш DQL-запросов. Разбор DQL кода и преобразование в SQL не бесплатное. Надо настроить кеширование этих данных (тут заранее их сгенерировать нельзя и кеш генеруется по мере надобности).
Ты должен рассмотреть существующие варианты реализации кеша, и выбрать наиболее оптимальный.
Бывают еще другие кеши - например, кеширование результатов запроса - держись от них подальше, от них больше проблем, чем пользы.
Теперь насчет прокси. Прокси-классы - это автоматически сгенерированные классы-наследники сущностей (моделей из твоего кода), обеспечивающие "ленивую" загрузку. Прокси-класс можно создать, указав тип и id сущности ($em->getReference(...)). При попытке вызова любого метода прокси-класса, кроме getId(), сначала произойдет подгрузка данных из БД в поля объекта и только потом будет вызван метод предка. То есть твой код не должен ничего знать о ленивой загрузке, это делается прозрачно.
Зачем нужна ленивая загрузка? Представь, у тебя есть объект Комментарий, у него поля Статья и Пользователь. Если нет ленивой загрузки, мы должны в каждый комментарий проставить эти объекты (а у них могут быть свои связи) и в итоге мы должны будем загрузить базу целиком в память. Вместо реальных Статьи и Пользователя мы создаем прокси и эконоим на запросах в БД, при этом при обращении к ним данные сами подгрузятся, как будто они всегда там и были.
Почему нужно наследование? Чтобы объект-прокси был совместим с исходным классом и проходил тайп-хинты, проверки на instanceof итд. По правилу Лисков (объект-наследник можно использовать вместо объекта-предка).
Почему кодогенерация? Ну не руками же писать.
Как и с кешем, там тоже есть 2 режима, автоматическая генерация и генерация явным вызовом скрипта.
Прокси-классы создаются разумеется на диске в отдельной папке.
> Не могу понять как вытащить куки из $app.
Возможно надо это делать в контроллере или функции-обработчике.
>>907913
Проблема в том, что поведение может появиться позже, и в большом приложении переписывать доступ к полям на вызов методов сложно.
>>907951
> Команда composer require slim/twig-view не работает, говорит что чего-то не хватает(завтра могу скрин показать).
Надо разобраться, почему.
> Однако, я посмотрел в стандартном пакете слима уже есть твиг, и я попросту создал новый объект твига по инструкции из его документации,
Если это skeleton, то это сборка, я бы советовал лучше Слим учиться с нуля через композер ставить.
> В колбеке не работает стандартная ф-я header(), ну что бы сделать переадресацию после записи в БД.
Там у response можно заголовки добавлять и может даже есть метод для редиректа. Ты должен заполнять объект response, а не выводить все сам.
Кеши бывают разные, думаю речь о кеше метаданных. Доктрина читает информацию о замапленных классах и полях либо из yml-конфигов, либо из аннотаций в PHP комментариях. Процесс чтения не бесплатный и требует времени на разбор, и т.д. Соответственно есть идея кешировать эти метаданные, чтобы не читать их каждый раз при запуске скрипта.
Есть 2 режима:
- девеломпент (режим разработчика) - доктрина будет проверять дату модификации PHP файла и автоматически перегенерировать кеш при изменении или при отсутствии.
- продакшен (рабочий режим) - время обновления PHP файла не проверяется. Кеш сам не создается. Метаданные нужно создать явно, вызвав специальный метод подогрева кеша. Это можно делать например, скриптом, при деплое (выгрузке кода на сайт). При деплое мы вкладываем код в новую папку, генерируем в ней кеш и переключаем веб-сервер на эту папку. А затем удаляем старую.
Есть и другие кеши, например, кеш DQL-запросов. Разбор DQL кода и преобразование в SQL не бесплатное. Надо настроить кеширование этих данных (тут заранее их сгенерировать нельзя и кеш генеруется по мере надобности).
Ты должен рассмотреть существующие варианты реализации кеша, и выбрать наиболее оптимальный.
Бывают еще другие кеши - например, кеширование результатов запроса - держись от них подальше, от них больше проблем, чем пользы.
Теперь насчет прокси. Прокси-классы - это автоматически сгенерированные классы-наследники сущностей (моделей из твоего кода), обеспечивающие "ленивую" загрузку. Прокси-класс можно создать, указав тип и id сущности ($em->getReference(...)). При попытке вызова любого метода прокси-класса, кроме getId(), сначала произойдет подгрузка данных из БД в поля объекта и только потом будет вызван метод предка. То есть твой код не должен ничего знать о ленивой загрузке, это делается прозрачно.
Зачем нужна ленивая загрузка? Представь, у тебя есть объект Комментарий, у него поля Статья и Пользователь. Если нет ленивой загрузки, мы должны в каждый комментарий проставить эти объекты (а у них могут быть свои связи) и в итоге мы должны будем загрузить базу целиком в память. Вместо реальных Статьи и Пользователя мы создаем прокси и эконоим на запросах в БД, при этом при обращении к ним данные сами подгрузятся, как будто они всегда там и были.
Почему нужно наследование? Чтобы объект-прокси был совместим с исходным классом и проходил тайп-хинты, проверки на instanceof итд. По правилу Лисков (объект-наследник можно использовать вместо объекта-предка).
Почему кодогенерация? Ну не руками же писать.
Как и с кешем, там тоже есть 2 режима, автоматическая генерация и генерация явным вызовом скрипта.
Прокси-классы создаются разумеется на диске в отдельной папке.
> Не могу понять как вытащить куки из $app.
Возможно надо это делать в контроллере или функции-обработчике.
>>907913
Проблема в том, что поведение может появиться позже, и в большом приложении переписывать доступ к полям на вызов методов сложно.
>>907951
> Команда composer require slim/twig-view не работает, говорит что чего-то не хватает(завтра могу скрин показать).
Надо разобраться, почему.
> Однако, я посмотрел в стандартном пакете слима уже есть твиг, и я попросту создал новый объект твига по инструкции из его документации,
Если это skeleton, то это сборка, я бы советовал лучше Слим учиться с нуля через композер ставить.
> В колбеке не работает стандартная ф-я header(), ну что бы сделать переадресацию после записи в БД.
Там у response можно заголовки добавлять и может даже есть метод для редиректа. Ты должен заполнять объект response, а не выводить все сам.
>>907959
> Но почему копирует? Функция это же объект и должна быть ссылка!
Конечно, копирует ссылку на функцию.
> В мануале мазилы, кстати, сказано, что это методы, но наверно это так просто для простоты написано. Поэтому я их так назвал.
Возможно что даже где-то в стандартах JS есть слово "методы". Но фактически-то это просто функции, записанные в поля объекта (хотя в ES6 для них есть специальный синтаксис в определении класса). Тут имеется в виду "this указывает на объект, если метод был вызван с помощью синтаксиса obj.method()".
Кстати, там упомянут еще метод bind() из ES5, создающий функцию, вызвающую исходную функцию с заданным this.
Есть предложение в ES7 сделать специальный оператор для упрощения привязки this к функции: https://github.com/tc39/proposal-bind-operator
> Если у вас есть время, то давайте. Только не очень большую. Я в принципе начинаю понимать, просто нужно убедиться что нету пробелов в понимании, как например с примером выше.
Вот по возрастанию сложности:
Задача 1: написать функцию bindContext(fn, that). Она создает новую функцию, которая при вызове вызывает fn с указанным this и переданным аргументами. То по сути есть привязывает произвольное значение this к функции. Использование:
function x(a, b, c) { return [this.test, a, b, c]; };
var that = { test: 1 };
var result = bindContext(x, that)(10, 20, 30);
console.log(result); // [1, 10, 20, 30]
Использовать Function.prototype.bind из ES5 нельзя.
Задача 2: сделать функцию addProperty(object, name, initialValue) для создания приватных свойств с геттерами и сеттерами на объекте или прототипе объекта.
Функция добавляет 2 метода для доступа к свойству - геттер getName() и сеттер setName(x). Само свойство должно быть приватным и не доступно для внешнего кода никаким образом.
Использование:
var obj = { };
addProperty(obj, 'test', 10);
console.log(obj.getTest()); // 10
obj.setTest(100);
console.log(obj.getTest()); // 100
console.log(obj); // { getTest:..., setTest: ...} - самого свойства тут нет
Аналогично можно добавлять свойства в прототип:
function User() { };
addProperty(User.prototype, 'name', 'Иван');
var u1 = new User;
console.log(u1.getName()); // 'Иван'
Задача 3: сделать функцию для добавления в объект или прототип нового метода addMethod(object, name, fn). Использование:
var o = {};
addMethod(o, 'test', function () { return 1; });
console.log(o.test()); // 1
Это конечно просто и неинтересно, потому добавим еще один пункт: если мы добавляем метод в класс-наследник, должна быть возможность обратиться к методу из класса-родителя с помощью слова super(...) (если предка нет, то бросаем RuntimeError или что-нибудь аналогичное):
function Parent() { this.x = 1; };
Parent.prototype.test = function (a) { return [this.x, a]; };
function Child() { Parent.call(this); this.x = 2; };
Child.prototype = Object.create(Parent.prototype);
addMethod(Child.prototype, 'test', function (a) {
var array = super(20);
array.push(a);
return array;
});
var ch = new Child;
console.log(ch.test(30)); // [ 2, 20, 30]
В JS такой возможности вызывать родительский одноименный метод нет, так что она нам пригодится.
Я еще хотел сделать пример с наследованием от встроенного класса (например, Array), но судя по статье http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ (англ), это требует каких-то сложных хаков. Невозможность наследования от встроенных классов напоминает нам, что в JS все же есть прототипы, а не полноценное наследование в привычном виде.
Хорошая задачка получилась, правда? Может ее в список задач как бонусную добавить?
>>907976
Искать нет ли добавляемого департамента в массиве. Например, циклом или через in_array c третьим аргументом.
>>907994
По умолчанию in_array делает нестрогое сравнение (аналогично ==), то есть просто проверяет что у объектов одинаковые значения полей, а не то что это один и тот же объект. Надо использовать специальный третий аргумент, см мануал.
>>907997
Лишний знак доллара поставил после this.
> Ведь любой код насколько я знаю который может выкинуть исключения должен быть обернут в try блок (читал только об этом, сам не юзал)
Почитай урок https://github.com/codedokode/pasta/blob/master/php/exceptions.md
Я бы тебе советовал порешать наши задачи, ООП и далее про студентов.
> У меня во всяком случае тупое выкидывание исключения давало эрор.
Так часто и должно быть.
>>907999
Надо сравнивать именно через ===, что это один и тот же объект.
>>907959
> Но почему копирует? Функция это же объект и должна быть ссылка!
Конечно, копирует ссылку на функцию.
> В мануале мазилы, кстати, сказано, что это методы, но наверно это так просто для простоты написано. Поэтому я их так назвал.
Возможно что даже где-то в стандартах JS есть слово "методы". Но фактически-то это просто функции, записанные в поля объекта (хотя в ES6 для них есть специальный синтаксис в определении класса). Тут имеется в виду "this указывает на объект, если метод был вызван с помощью синтаксиса obj.method()".
Кстати, там упомянут еще метод bind() из ES5, создающий функцию, вызвающую исходную функцию с заданным this.
Есть предложение в ES7 сделать специальный оператор для упрощения привязки this к функции: https://github.com/tc39/proposal-bind-operator
> Если у вас есть время, то давайте. Только не очень большую. Я в принципе начинаю понимать, просто нужно убедиться что нету пробелов в понимании, как например с примером выше.
Вот по возрастанию сложности:
Задача 1: написать функцию bindContext(fn, that). Она создает новую функцию, которая при вызове вызывает fn с указанным this и переданным аргументами. То по сути есть привязывает произвольное значение this к функции. Использование:
function x(a, b, c) { return [this.test, a, b, c]; };
var that = { test: 1 };
var result = bindContext(x, that)(10, 20, 30);
console.log(result); // [1, 10, 20, 30]
Использовать Function.prototype.bind из ES5 нельзя.
Задача 2: сделать функцию addProperty(object, name, initialValue) для создания приватных свойств с геттерами и сеттерами на объекте или прототипе объекта.
Функция добавляет 2 метода для доступа к свойству - геттер getName() и сеттер setName(x). Само свойство должно быть приватным и не доступно для внешнего кода никаким образом.
Использование:
var obj = { };
addProperty(obj, 'test', 10);
console.log(obj.getTest()); // 10
obj.setTest(100);
console.log(obj.getTest()); // 100
console.log(obj); // { getTest:..., setTest: ...} - самого свойства тут нет
Аналогично можно добавлять свойства в прототип:
function User() { };
addProperty(User.prototype, 'name', 'Иван');
var u1 = new User;
console.log(u1.getName()); // 'Иван'
Задача 3: сделать функцию для добавления в объект или прототип нового метода addMethod(object, name, fn). Использование:
var o = {};
addMethod(o, 'test', function () { return 1; });
console.log(o.test()); // 1
Это конечно просто и неинтересно, потому добавим еще один пункт: если мы добавляем метод в класс-наследник, должна быть возможность обратиться к методу из класса-родителя с помощью слова super(...) (если предка нет, то бросаем RuntimeError или что-нибудь аналогичное):
function Parent() { this.x = 1; };
Parent.prototype.test = function (a) { return [this.x, a]; };
function Child() { Parent.call(this); this.x = 2; };
Child.prototype = Object.create(Parent.prototype);
addMethod(Child.prototype, 'test', function (a) {
var array = super(20);
array.push(a);
return array;
});
var ch = new Child;
console.log(ch.test(30)); // [ 2, 20, 30]
В JS такой возможности вызывать родительский одноименный метод нет, так что она нам пригодится.
Я еще хотел сделать пример с наследованием от встроенного класса (например, Array), но судя по статье http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ (англ), это требует каких-то сложных хаков. Невозможность наследования от встроенных классов напоминает нам, что в JS все же есть прототипы, а не полноценное наследование в привычном виде.
Хорошая задачка получилась, правда? Может ее в список задач как бонусную добавить?
>>907976
Искать нет ли добавляемого департамента в массиве. Например, циклом или через in_array c третьим аргументом.
>>907994
По умолчанию in_array делает нестрогое сравнение (аналогично ==), то есть просто проверяет что у объектов одинаковые значения полей, а не то что это один и тот же объект. Надо использовать специальный третий аргумент, см мануал.
>>907997
Лишний знак доллара поставил после this.
> Ведь любой код насколько я знаю который может выкинуть исключения должен быть обернут в try блок (читал только об этом, сам не юзал)
Почитай урок https://github.com/codedokode/pasta/blob/master/php/exceptions.md
Я бы тебе советовал порешать наши задачи, ООП и далее про студентов.
> У меня во всяком случае тупое выкидывание исключения давало эрор.
Так часто и должно быть.
>>907999
Надо сравнивать именно через ===, что это один и тот же объект.
Там код как учат в старых учебниках по PHP: куча глобальных переменных и функций. Ни ООП нормального, ни MVC. Ни PSR. Стиль кода - адский.
Сама архитектура там "ядро - компоненты" вместо MVC (как в древних CMS). Соответственно в "компоненте" вместе идут и модель, и контроллер, и в запущенных случаях, вью.
Посмотри официальные уроки от Битрикса с примерами кода (ну чтобы не говорили, что это я сочинил):
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&CHAPTER_ID=04609&LESSON_PATH=3913.4609
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2902
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=3223
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2975&LESSON_PATH=3913.4565.2975
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2898
Обрати внимание на стиль кода, var из PHP4, и кучу рутинного кода по копированию файлов. Какой еще композер? Изобретай свой велосипед.
Данные передаются в каких-то массивах неизвестной структуры.
И еще это:
> Модуль необходимо создавать в кодировке windows-1251, при установке его на сайт с кодировкой UTF-8 происходит автоматическая перекодировка.
> Помните, что только языковые файлы из папки /ru/ конвертируются в кодировку сайта
То есть часть сайтов у них в одной кодировке, часть в другой.
Вот еще мнение:
- http://www.intervolga.ru/blog/projects/bitrix-for-programmmer-oop-orm-mvc-patterns/
- https://habrahabr.ru/post/282317/
В общем, погуглите сами, не ленитесь. Ну и может какой доброанон, который знаком с этим своеобразным миром, еще какие-нибудь интересные ссылочки подкинет.
>>908021
Там массивы и используются, только я боюсь там постоянно надо вар-дампом выяснять, что в них находится. Я такое в Друпале видел, сотни массивов, нигде толком не документировнных, и что самое интересное, часть элементов иногда есть, а иногда нет, и понять, почему, очень непросто.
>>908024
Кроме десятичной системы, где используется 10 цифр, и после числа 9 идет число 10, есть и другие. В 16-чной системе 16 цифр (0-9, a-f) и перед числом 10 идет число f. В двоичной - две цифры (0, 1) и перед 10 идет 1.
Важно понимать, что числа те же самые, просто записываются в другом виде и другими цифрами.
>>908026
Можно, почему нет. Только у тебя там наверно не модели (представляющие какие-то сущности), а сервисы (классы, содержащие разные методы с бизнес-логикой).
> Вопрос наверное звучит глупо и труднообъяснимо, но иногда когда я пишу на своём неуважаемом в этом треде Codeigniter'e у меня возникает именно желание из модели вызывать другую модель,
А, ты про CI? Там "моделями" называют ведь классы работы с БД? Тогда лучше сделать сервис, и пусть он вызывает методы 2 моделей. А вообще, CI за образец брать не стоит, там же ад, контроллеры наследуются от основного класса фреймворка.
Там код как учат в старых учебниках по PHP: куча глобальных переменных и функций. Ни ООП нормального, ни MVC. Ни PSR. Стиль кода - адский.
Сама архитектура там "ядро - компоненты" вместо MVC (как в древних CMS). Соответственно в "компоненте" вместе идут и модель, и контроллер, и в запущенных случаях, вью.
Посмотри официальные уроки от Битрикса с примерами кода (ну чтобы не говорили, что это я сочинил):
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&CHAPTER_ID=04609&LESSON_PATH=3913.4609
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2902
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=3223
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2975&LESSON_PATH=3913.4565.2975
- http://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2898
Обрати внимание на стиль кода, var из PHP4, и кучу рутинного кода по копированию файлов. Какой еще композер? Изобретай свой велосипед.
Данные передаются в каких-то массивах неизвестной структуры.
И еще это:
> Модуль необходимо создавать в кодировке windows-1251, при установке его на сайт с кодировкой UTF-8 происходит автоматическая перекодировка.
> Помните, что только языковые файлы из папки /ru/ конвертируются в кодировку сайта
То есть часть сайтов у них в одной кодировке, часть в другой.
Вот еще мнение:
- http://www.intervolga.ru/blog/projects/bitrix-for-programmmer-oop-orm-mvc-patterns/
- https://habrahabr.ru/post/282317/
В общем, погуглите сами, не ленитесь. Ну и может какой доброанон, который знаком с этим своеобразным миром, еще какие-нибудь интересные ссылочки подкинет.
>>908021
Там массивы и используются, только я боюсь там постоянно надо вар-дампом выяснять, что в них находится. Я такое в Друпале видел, сотни массивов, нигде толком не документировнных, и что самое интересное, часть элементов иногда есть, а иногда нет, и понять, почему, очень непросто.
>>908024
Кроме десятичной системы, где используется 10 цифр, и после числа 9 идет число 10, есть и другие. В 16-чной системе 16 цифр (0-9, a-f) и перед числом 10 идет число f. В двоичной - две цифры (0, 1) и перед 10 идет 1.
Важно понимать, что числа те же самые, просто записываются в другом виде и другими цифрами.
>>908026
Можно, почему нет. Только у тебя там наверно не модели (представляющие какие-то сущности), а сервисы (классы, содержащие разные методы с бизнес-логикой).
> Вопрос наверное звучит глупо и труднообъяснимо, но иногда когда я пишу на своём неуважаемом в этом треде Codeigniter'e у меня возникает именно желание из модели вызывать другую модель,
А, ты про CI? Там "моделями" называют ведь классы работы с БД? Тогда лучше сделать сервис, и пусть он вызывает методы 2 моделей. А вообще, CI за образец брать не стоит, там же ад, контроллеры наследуются от основного класса фреймворка.
11. дан список вида «страна, город, население»
> a['population']
Можно писать a.population.
Решено верно.
12. Некая сеть фастфудов предлагает несколько видов гамбургеров
> if (Error.captureStackTrace) {
Ага, в JS так просто не унаследуешь исключения, и вообще встроенные классы, известная проблема. Напоминает о том, что в JS все же прототипное ООП, а не классическое.
Далее тут (спам лист): http://pastebin.ru/ZddazILb
Только сейчас шел с трени и хотел в треде про друпал спросить. Потому что год назад ходил на недельное обучение по друпалу и охерел от того что там всё как-то очень непонятно было. Всё на хуках каких-то построено, сложно было для меня. Правда тогда вроде как вышла новая версия в которой обещали друпал перекатить на ООП и что теперь в нем всё будет ок. Интересно в треде кто-нибудь работает на нем?
>А, ты про CI? Там "моделями" называют ведь классы работы с БД?
Ну по большей части да, простейшая логика на игнайтере выглядит так примерно:
http://ideone.com/StsLy7
Модели - просто наверное эволюция functions.php, в которых лежат методы которые что-то отдадут / посчитают /сохранят / изменят. Никаких сущностей там нет. Там по вездесущему $this есть гигантский синглтон, в котором можно вот так вот всё вызывать.
Контроллер - в 50% случаев спрашивает что-то у модели и передает это во вьюху, еще в 30% смотрит что пришло в каком-нибудь $_POST/$_GET и отдаст после обработки в модель что бы та засейвила в базу.
Собственно это управляющая логика с проверками и прочим.
Вьюха - эволюция template.php в котором html смешан с <?=$name?> и так далее.
Собственно простейшие приложухи на этой логике делать безумно просто и быстро (я бы даже студентов написал наверное за час на игнайтере, они как будто под него и сделаны, но попытаюсь написать абстрагировавшись от игнайтеровского стиля), но иногда такая вот цепочка уже не работает. Например сделал юзер заказ, есть моделька которая засейвит в базу то что был совершен заказ. И есть "моделька" которая сгенерирует pdf например с квитанцией на оплату.
И тут передо мной открываются собственно 2 пути, либо вызывать из одной модели другую сразу, либо возвращать данные в контроллер из первой, и эти же данные передавать из контроллера во вторую при вызове.
Это наверное как раз когда у тебя уже не хватает игнайтеровских "рамок" а рвется наружу простое ООП, где классы должны обмениваться друг с другом данными как им удобно, а не обязательно быть загнанны в такой вот "MVC паттерн".
В общем спасибо тем кто прочел этот высер. Постараюсь дальше расти и вкуривать ООП, что бы мочь нормально написать с 0 студентов и прочий файлообменник.
Только сейчас шел с трени и хотел в треде про друпал спросить. Потому что год назад ходил на недельное обучение по друпалу и охерел от того что там всё как-то очень непонятно было. Всё на хуках каких-то построено, сложно было для меня. Правда тогда вроде как вышла новая версия в которой обещали друпал перекатить на ООП и что теперь в нем всё будет ок. Интересно в треде кто-нибудь работает на нем?
>А, ты про CI? Там "моделями" называют ведь классы работы с БД?
Ну по большей части да, простейшая логика на игнайтере выглядит так примерно:
http://ideone.com/StsLy7
Модели - просто наверное эволюция functions.php, в которых лежат методы которые что-то отдадут / посчитают /сохранят / изменят. Никаких сущностей там нет. Там по вездесущему $this есть гигантский синглтон, в котором можно вот так вот всё вызывать.
Контроллер - в 50% случаев спрашивает что-то у модели и передает это во вьюху, еще в 30% смотрит что пришло в каком-нибудь $_POST/$_GET и отдаст после обработки в модель что бы та засейвила в базу.
Собственно это управляющая логика с проверками и прочим.
Вьюха - эволюция template.php в котором html смешан с <?=$name?> и так далее.
Собственно простейшие приложухи на этой логике делать безумно просто и быстро (я бы даже студентов написал наверное за час на игнайтере, они как будто под него и сделаны, но попытаюсь написать абстрагировавшись от игнайтеровского стиля), но иногда такая вот цепочка уже не работает. Например сделал юзер заказ, есть моделька которая засейвит в базу то что был совершен заказ. И есть "моделька" которая сгенерирует pdf например с квитанцией на оплату.
И тут передо мной открываются собственно 2 пути, либо вызывать из одной модели другую сразу, либо возвращать данные в контроллер из первой, и эти же данные передавать из контроллера во вторую при вызове.
Это наверное как раз когда у тебя уже не хватает игнайтеровских "рамок" а рвется наружу простое ООП, где классы должны обмениваться друг с другом данными как им удобно, а не обязательно быть загнанны в такой вот "MVC паттерн".
В общем спасибо тем кто прочел этот высер. Постараюсь дальше расти и вкуривать ООП, что бы мочь нормально написать с 0 студентов и прочий файлообменник.
Для сборки запроса по частям есть паттерн Query Builder (урок https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md ).
Также, набор условий для поиска можно представить в виде объекта. Это требует описания класса, зато код будет намного понятнее, можно добавлять методы, и в классе можно написать подробные комментарии. Коллеги будут благодарны.
>>906514
И ты прочти про Query Builder: https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
>>906483
> https://github.com/fidnex/filehost/blob/master/dump.sql#L24
> FOREIGN KEY fk_parent (`parent_id`) REFERENCES comments (`id`)
> ON DELETE SET NULL,
То есть при удалении родителя дети становятся комментариями верхнего уровня? Так и задумано или ошибка?
По поводу CLI - я глянул https://github.com/fidnex/filehost/commit/2a3493d3321f346a90b5be20e13fc0092a8bce5f - на мой взгляд ты немного переусложняешь, может не стоит делать для CLI контроллеры и прочее, может проще прсто сделать скрипит вида
<?php
require '...bootstrap.php';
...
код
Хотя в той же Симфони например предусмотрен специальный класс для создания консольных команд:
- http://symfony.com/doc/current/components/console.html (англ)
- http://symfony.com/doc/current/console.html
можешь глянуть, как у них сделано.
> Без нее функция path_for во вьюшке не хочет работать.
Тогда ладно. Хотя конечно request лучше бы в контейнер не засовывать с точки зрения логики.
>>906456
Это по какой-то задаче вопрос или в общем? Если в общем, то обычно серый оверлей предназначен для 2 целей:
- сконцентрировать внимание пользователя на попапе
- не позволить ему случайно нажать что-то вне попапа (по хорошему надо еще перемеситить фокус ввода на попап и не давать табом уйти из попапа)
Для сборки запроса по частям есть паттерн Query Builder (урок https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md ).
Также, набор условий для поиска можно представить в виде объекта. Это требует описания класса, зато код будет намного понятнее, можно добавлять методы, и в классе можно написать подробные комментарии. Коллеги будут благодарны.
>>906514
И ты прочти про Query Builder: https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
>>906483
> https://github.com/fidnex/filehost/blob/master/dump.sql#L24
> FOREIGN KEY fk_parent (`parent_id`) REFERENCES comments (`id`)
> ON DELETE SET NULL,
То есть при удалении родителя дети становятся комментариями верхнего уровня? Так и задумано или ошибка?
По поводу CLI - я глянул https://github.com/fidnex/filehost/commit/2a3493d3321f346a90b5be20e13fc0092a8bce5f - на мой взгляд ты немного переусложняешь, может не стоит делать для CLI контроллеры и прочее, может проще прсто сделать скрипит вида
<?php
require '...bootstrap.php';
...
код
Хотя в той же Симфони например предусмотрен специальный класс для создания консольных команд:
- http://symfony.com/doc/current/components/console.html (англ)
- http://symfony.com/doc/current/console.html
можешь глянуть, как у них сделано.
> Без нее функция path_for во вьюшке не хочет работать.
Тогда ладно. Хотя конечно request лучше бы в контейнер не засовывать с точки зрения логики.
>>906456
Это по какой-то задаче вопрос или в общем? Если в общем, то обычно серый оверлей предназначен для 2 целей:
- сконцентрировать внимание пользователя на попапе
- не позволить ему случайно нажать что-то вне попапа (по хорошему надо еще перемеситить фокус ввода на попап и не давать табом уйти из попапа)
Чтобы делать сайты, учи параллельно HTML - тогда ты сможешь делать простые статичные странички. А когда ты захочешь сделать их интерактивными, тебе понадобятся классы, база данных, MVC - все то, что ты будешь изучать в курсе по PHP, когда дойдешь до студентов. И поиск строк тоже понадобится.
>>906015
Опция DirectoryIndex в конфиге Апача задает, какие файлы должны использоваться, если URL соответствует папке на диске. Там обычно стоит DirectoryIndex index.php index.html
https://www.google.ru/search?q=apache+directoryindex&btnG=Поиск&newwindow=1&gbv=1
>>905993
Они в глобальном, но, для удобства доступны и из файлов в другоим неймспейсе. Подробнее в оф мануале.
>>905876
Надо прочитать в оф мануале.
>>905863
Верно, но можно было обойтись без копипасты первых 2 строк с помощью цикла.
>>905861
Для удобства встроенные классы и функции доступны в любом неймспейсе.
>>905850
use \PDOException;
Но проще наверно бекслеш поставить.
>>905072
> А сущность Студент это ведь глупое хранилище данных, какие там детали реализации?
Это у тебя простой проект. А на практике получаются такие вещи:
- у Студента нужна Дата Модификации, и она должна проставляться при любых изменениях
- надо вести историю изменения какого-нибудь свойства
- есть какое-то свойство, которое вычисляется из другого или еще как-то от него зависит (в таких случаях лучше конечно не делать вообще такое свойство, но иногда оно нужно по соображениям оптимизации БД)
Чтобы делать сайты, учи параллельно HTML - тогда ты сможешь делать простые статичные странички. А когда ты захочешь сделать их интерактивными, тебе понадобятся классы, база данных, MVC - все то, что ты будешь изучать в курсе по PHP, когда дойдешь до студентов. И поиск строк тоже понадобится.
>>906015
Опция DirectoryIndex в конфиге Апача задает, какие файлы должны использоваться, если URL соответствует папке на диске. Там обычно стоит DirectoryIndex index.php index.html
https://www.google.ru/search?q=apache+directoryindex&btnG=Поиск&newwindow=1&gbv=1
>>905993
Они в глобальном, но, для удобства доступны и из файлов в другоим неймспейсе. Подробнее в оф мануале.
>>905876
Надо прочитать в оф мануале.
>>905863
Верно, но можно было обойтись без копипасты первых 2 строк с помощью цикла.
>>905861
Для удобства встроенные классы и функции доступны в любом неймспейсе.
>>905850
use \PDOException;
Но проще наверно бекслеш поставить.
>>905072
> А сущность Студент это ведь глупое хранилище данных, какие там детали реализации?
Это у тебя простой проект. А на практике получаются такие вещи:
- у Студента нужна Дата Модификации, и она должна проставляться при любых изменениях
- надо вести историю изменения какого-нибудь свойства
- есть какое-то свойство, которое вычисляется из другого или еще как-то от него зависит (в таких случаях лучше конечно не делать вообще такое свойство, но иногда оно нужно по соображениям оптимизации БД)
> но назвал методы не setId, а insertId (где он вообще видел такое???)
Он наверно начинающий и много чужого кода пока не видел.
>>904773
Потому что он упоминает ООП, менеджеры пакетов и тд. Если есть книга лучше - советуй.
>>904732
Лучше ставить в функции return индекса, а вывод делать снаружи. А то твою функцию в программе использовать нельзя и что-то делать с результатом работы.
>>904725
Надо разобраться с общими идеями асинхронной работы (stream_set_blocking, stream_select), затем посмотреть React\Stream и React\EventLoop ну и дальше смотреть сокеты.
>>904974
Надо научиться чистить кеш браузера или ставить галочку в инструментах разработчика.
>>904636
Может через неделю-две будет время, там до тебя еще есть люди (список в начале треда есть).
>>904614
git diff дает примерно то же, только не так красиво.
>>904601
Цикл + стек/очередь.
>>904440
Учись.
> но назвал методы не setId, а insertId (где он вообще видел такое???)
Он наверно начинающий и много чужого кода пока не видел.
>>904773
Потому что он упоминает ООП, менеджеры пакетов и тд. Если есть книга лучше - советуй.
>>904732
Лучше ставить в функции return индекса, а вывод делать снаружи. А то твою функцию в программе использовать нельзя и что-то делать с результатом работы.
>>904725
Надо разобраться с общими идеями асинхронной работы (stream_set_blocking, stream_select), затем посмотреть React\Stream и React\EventLoop ну и дальше смотреть сокеты.
>>904974
Надо научиться чистить кеш браузера или ставить галочку в инструментах разработчика.
>>904636
Может через неделю-две будет время, там до тебя еще есть люди (список в начале треда есть).
>>904614
git diff дает примерно то же, только не так красиво.
>>904601
Цикл + стек/очередь.
>>904440
Учись.
Как правило, исключения по умолчанию никто не ловит и делается один обработчик непойманных исключений который логгирует их и выводит страницу-заглушку 503 для пользователя.
>>904315
В одном методе нет смысла писать throw и catch так как проще поставить if. Throw пишут для случаев, когда выбрасывается в одной функции, а ловится в другой.
Рассматривай исключение как способ функции сказать вызывающему ее, что что-то не так и она не может выполнить свою работу.
>>904314
> Но непойманное исключение прекратит выполнение программы, причём — покажет голую информацию о коде пользователю, разве это допустимо?
В PHP если поставить display_errors = 0, то покажет пустую страницу (и запишет исключение в лог). Но это лучше чем продолжать выполнять код. Конечно, это ошибка проектирования PHP: непойманное исключение должно приводить к выводу страницы-заглушки с HTTP кодом 503. Плоховато разработчики PHP знали HTTP тогда.
Обычно ставят один глобальынй обработчик исключений, который их логгирует и выводит заглушку 503.
В десктопных приложениях иногда обработчик показывает окно с предложением отправить сообщение об ошибке разработчикам.
> Представь, что у тебя в участке кода вызывается несколько методов класса, в каждом из которых может возникнуть ошибка, с которой нужно будет предпринимать какое-то действие: изменить порядок будущего исполнения программы или её свойства, вывести сообщение пользователю, откатиться на ранее сохранённый (безопасный) этап работы с сайтом. Чтобы различать ошибку каждого отдельного метода, придётся либо использовать много try..catch'ей, либо вызывать в каждом методе свой тип исключений, либо вызывать один тип исключений с разными кодами в них (и потом использовать switch для выбора конкретного действия); мне кажется, в такой ситуации простейший if, проверящий успех/неуспех (false) операции, выглядит проще и красивее.
На практике без исключений придется ставить if после каждого вызова функции и это утомительно. То, что ты привел - это частный случай, и там можно либо ставить catch, либо ифы. В случае с catch, если исключения разные, можно даже написать так:
try {
} catch (E1 $e) {
...
} catch (E2 $e) {
...
} finally {
...
}
Иногда можно исплоьзовать if вместо исключения, если мы ожидаем, что будет ошибка и если гарантированно проверяем резлуьтат вызова функции. Но по умолчанию надо использовать исклюяения, так как они ипозволяют например писать цепочечные вызовы:
a(b($x), c($x), d($x));
$obj->someMethod()->somethingElse();
С ифами так не получится.
Вообще, эту тему я разобрал в своем уроке https://github.com/codedokode/pasta/blob/master/php/exceptions.md
>>904180
так и надо делать, ты замучаешься их по методам искать.
>>904114
Как бы ты решал проблему, что пользователи работают от администратора и программы пишутся с расчетом на это?
>>904108
Видимо не от администратора запустил. Проверь, можешь ли ты редактировать hosts.
>>904055
> 1. В PHP функции, не выбрасывающие исключение при исключительных ситуациях как минимум уродливы тем, что часть из них возвращает null, а часть - false
Это плохо спроектировано. Ошибок вообще не должно быть, только исключения, так как неправильно продолжать выполнение кода при необработанной ошибке.
> Не надуманный пример с 5-ю ифами, а рельная задача - нужно сделать так, чтобы все ошибки логгировались. В случае с исключениями можно сделать
По идее конечно PHP сам умеет логгировать ошибки, есть опции, и по умолчанию они даже включены.
Как правило, исключения по умолчанию никто не ловит и делается один обработчик непойманных исключений который логгирует их и выводит страницу-заглушку 503 для пользователя.
>>904315
В одном методе нет смысла писать throw и catch так как проще поставить if. Throw пишут для случаев, когда выбрасывается в одной функции, а ловится в другой.
Рассматривай исключение как способ функции сказать вызывающему ее, что что-то не так и она не может выполнить свою работу.
>>904314
> Но непойманное исключение прекратит выполнение программы, причём — покажет голую информацию о коде пользователю, разве это допустимо?
В PHP если поставить display_errors = 0, то покажет пустую страницу (и запишет исключение в лог). Но это лучше чем продолжать выполнять код. Конечно, это ошибка проектирования PHP: непойманное исключение должно приводить к выводу страницы-заглушки с HTTP кодом 503. Плоховато разработчики PHP знали HTTP тогда.
Обычно ставят один глобальынй обработчик исключений, который их логгирует и выводит заглушку 503.
В десктопных приложениях иногда обработчик показывает окно с предложением отправить сообщение об ошибке разработчикам.
> Представь, что у тебя в участке кода вызывается несколько методов класса, в каждом из которых может возникнуть ошибка, с которой нужно будет предпринимать какое-то действие: изменить порядок будущего исполнения программы или её свойства, вывести сообщение пользователю, откатиться на ранее сохранённый (безопасный) этап работы с сайтом. Чтобы различать ошибку каждого отдельного метода, придётся либо использовать много try..catch'ей, либо вызывать в каждом методе свой тип исключений, либо вызывать один тип исключений с разными кодами в них (и потом использовать switch для выбора конкретного действия); мне кажется, в такой ситуации простейший if, проверящий успех/неуспех (false) операции, выглядит проще и красивее.
На практике без исключений придется ставить if после каждого вызова функции и это утомительно. То, что ты привел - это частный случай, и там можно либо ставить catch, либо ифы. В случае с catch, если исключения разные, можно даже написать так:
try {
} catch (E1 $e) {
...
} catch (E2 $e) {
...
} finally {
...
}
Иногда можно исплоьзовать if вместо исключения, если мы ожидаем, что будет ошибка и если гарантированно проверяем резлуьтат вызова функции. Но по умолчанию надо использовать исклюяения, так как они ипозволяют например писать цепочечные вызовы:
a(b($x), c($x), d($x));
$obj->someMethod()->somethingElse();
С ифами так не получится.
Вообще, эту тему я разобрал в своем уроке https://github.com/codedokode/pasta/blob/master/php/exceptions.md
>>904180
так и надо делать, ты замучаешься их по методам искать.
>>904114
Как бы ты решал проблему, что пользователи работают от администратора и программы пишутся с расчетом на это?
>>904108
Видимо не от администратора запустил. Проверь, можешь ли ты редактировать hosts.
>>904055
> 1. В PHP функции, не выбрасывающие исключение при исключительных ситуациях как минимум уродливы тем, что часть из них возвращает null, а часть - false
Это плохо спроектировано. Ошибок вообще не должно быть, только исключения, так как неправильно продолжать выполнение кода при необработанной ошибке.
> Не надуманный пример с 5-ю ифами, а рельная задача - нужно сделать так, чтобы все ошибки логгировались. В случае с исключениями можно сделать
По идее конечно PHP сам умеет логгировать ошибки, есть опции, и по умолчанию они даже включены.
Чтобы разобраться в ООП, надо изучать теорию, читать примеры, решать задачи. В ОП посте есть учебник PHP, там есть глава по ООП с задачами. Советую их решить, можно не на PHP.
Есть такая ссылка: http://learn.javascript.ru/internal-external-interface
После основ можно браться за более сложные конструкции, изучать паттерны, смореть код компонентов Симфони.
Наверно есть какаие-то книги по ООП, но я не знаю, какие.
> Дело в том, что я не всегда четко представляю себе, что нужно делать объектом,
То, что является частью предметной области, о чем задача. Тебе надо сделать учет сотрудников компании - сделай класс Сотрудник, объект которого представляет одного сотдрудника. Делаешь программу бронирования номеров в отделе - будут классы Номер, Постоялец, может класс Бронь и тд.
Вообще, при решении задач на ООП нало ответить на такие вопросы:
- какие сущности (модели) нужны для решения задачи (Номер, Постоялец)?
- какие у них есть свойства (Номер - число мест, этаж, стоимость и тд)?
- что они умеют/с ними можно делать? (т.е. какие будут методы)
- как они связаны между собой (Бронь связывает Номер и Постояльца)
Иногда кроме классов-моделей делают еще классы-сервисы, которые не представляют никакой сущности, но содержат методы с бизнес-логикой. Обычно это классы вроде КалькуляторСкидки, ПроверятельВведенныхДанных, СохранятельВБазу, МенеджерБронированияОтеля и т.д.
> когда нужно наследовать и даже, как это ни смешно, что именно от чего должно наследоваться.
Наследование A от B значит, что A - это улучшенная/измененная версия B. Используют наследование, когда есть однотипные классы, отличающиеся поведением. Например, у нас есть класс Компания и ГосУчреждение - возможно, их стоит унаследовать от ЮридическоеЛицо, если в задаче это даст какую-то выгоду. Например, если у них есть общие свойства и методы, можно перенести их в базовый класс.
> Кроме того, меня постоянно тянет оптимизировать придуманные структуры. Кто-то постоянно нашептывает мне: "Эта переменная не нужна, эта функция тоже, а вот это вообще можно побитово хранить".
Если не нужна, то и не храни. Насчет побитово - в PHP это точно не дает выгоды, только запутывает код. Но вообще, при инкапсуляции, неважно как хранятся данные внутри класса, так как снаружи это не видно, ты исплоьзуешь методы для доступа к ним, и они скрывают сложность внутренней реализации.
> Там от класса-родителя наследуются, кроме прочих, PowerLine (линии электропередач), при этом в PowerLine не используются ни поля класса-родителя, ни его методы, таким образом, наследование чисто логические.
А, я специально сделал задачу, которая сложно ложится на ООП. В реальных задачах редко бывает так же, как в учебнике, все иделально.
Тут надо определиться, что общего есть у сущностей и что мы вынесем в базовый класс. Это такие особенности:
- все элементы сети можно подключить к сети
- все элементы сети вносят вклад в баланс (хотя PowerLine - неизвестно сколько, пока мы не рассчитаем баланс. Проще всего присвоить ей нулевую мощность и считать покупку/продажу отдельно).
Я бы сделал так:
- базовый класс - ЭлементСети, конструктор NetworkElement(daypower, nightpower), умеет сообщать вклад в энергобаланс днем и ночью (getDayPower/getNightPower). При желании можно сделать один метод getPower(time). Конструктор принимает значения dayPower, nightPower.
- наследники, вызывая конструктор предка, задают значения вклада в сеть
В наследники мы выносим то, что индивидуально для данного элемента. Ну например, у Жилого Дома потребление зависит от числа квартир и формула расчета - это как раз индивидуальная особенность дома. Я бы сделал так:
function House(apartments) {
var dayPower = ...;
bvar nightPower = ...;
// вызываем конструктор предка
NetworkElement.call(this, dayPower, nightPower);
}
Если надо, можно еще добавить свойство и метод для получения числа квартир (в задаче не требуется).
Аналогично реализуется электростанция и солнечная панель.
PowerLine умеет передавать электричество, потому ей мы добавим свойства и методы для получения/задания цены и пропускной способности. Если цена не постоянная и зависит от чего-то (от налогов, времени года, цены на нефть и тд), может стоит ее не делать свойством PowerLine, а считать в другом классе.
Кстати, классы еще можно объединять через интерфейсы без наследования, но в JS их как бы нет.
> при этом в PowerLine не используются ни поля класса-родителя, ни его методы, таким образом, наследование чисто логические.
PowerLine объединяет с другими то, что ее можно подключить в сети, и она вносит вклад в нее, но ради упрощения кода проще принять его равным нулю, так как посчитать его она не способна (она лишь передает энергию, но не принимает решение о закупке). Можно считать вклад снаружи и задавать через сеттер, но это ухудшит код - например при добавлении нового элеимента сети мы обязаны как-то перерассчитать этот вклад. Гораздо проще просто принять вклад равным нулю и считать его в другом месте.
Стоиит избегать случаев, когда в свойстве может быть недостоверное или устаревшее значение - это приведет к куче проблем.
> Однако, вместо добавления нового поля _transmittedPower можно хранить величину в уже имеющемся _generateDayPower, и мне кажется, что это хорошо, в конце концов линии электропередач по условию тоже в некотором роде поставщики/потребители энергии, и я не вижу в таком использовании логической ошибки.
ЛЭП знает только пропускную способность и цену, но сколько фактически по ней передается, она посчитать не может так как не принимает решение о закупке.
> Но так ли это? Вообще, когда нужно следить, чтоб лишних полей не было, а когда можно махнуть на них рукой? Где почитать про это?
Не знаю. Порешай задачки на ООП, можно наши, если тех, что есть мало, я придумаю еще.
> Вот как я бы организовал данные
В общем логично, но с реализацией PowerLine не согласен.
> //PowerLine суть тоже поставщик/потребитель энергии, количество которой можно хранить в _generateDayPower, что логически вроде бы правильно (по-моему), поэтому нужно всего 1 дополнительное поле для стоимости
Еще для пропускной способности надо поле. Пропускная способность != фактически переданный объем.
>>903459
Исключение лишь значит что функция не может выполнить свою работу и вернуть результат, потому она прерывает свое выполнение и возвращает ошибку. Исключения дают отдельный (out-of-band) канал для передачи информации об ошибке, в то время как return false использует тот же канал (in-band) что и результат, и это менее удобно.
Чтобы разобраться в ООП, надо изучать теорию, читать примеры, решать задачи. В ОП посте есть учебник PHP, там есть глава по ООП с задачами. Советую их решить, можно не на PHP.
Есть такая ссылка: http://learn.javascript.ru/internal-external-interface
После основ можно браться за более сложные конструкции, изучать паттерны, смореть код компонентов Симфони.
Наверно есть какаие-то книги по ООП, но я не знаю, какие.
> Дело в том, что я не всегда четко представляю себе, что нужно делать объектом,
То, что является частью предметной области, о чем задача. Тебе надо сделать учет сотрудников компании - сделай класс Сотрудник, объект которого представляет одного сотдрудника. Делаешь программу бронирования номеров в отделе - будут классы Номер, Постоялец, может класс Бронь и тд.
Вообще, при решении задач на ООП нало ответить на такие вопросы:
- какие сущности (модели) нужны для решения задачи (Номер, Постоялец)?
- какие у них есть свойства (Номер - число мест, этаж, стоимость и тд)?
- что они умеют/с ними можно делать? (т.е. какие будут методы)
- как они связаны между собой (Бронь связывает Номер и Постояльца)
Иногда кроме классов-моделей делают еще классы-сервисы, которые не представляют никакой сущности, но содержат методы с бизнес-логикой. Обычно это классы вроде КалькуляторСкидки, ПроверятельВведенныхДанных, СохранятельВБазу, МенеджерБронированияОтеля и т.д.
> когда нужно наследовать и даже, как это ни смешно, что именно от чего должно наследоваться.
Наследование A от B значит, что A - это улучшенная/измененная версия B. Используют наследование, когда есть однотипные классы, отличающиеся поведением. Например, у нас есть класс Компания и ГосУчреждение - возможно, их стоит унаследовать от ЮридическоеЛицо, если в задаче это даст какую-то выгоду. Например, если у них есть общие свойства и методы, можно перенести их в базовый класс.
> Кроме того, меня постоянно тянет оптимизировать придуманные структуры. Кто-то постоянно нашептывает мне: "Эта переменная не нужна, эта функция тоже, а вот это вообще можно побитово хранить".
Если не нужна, то и не храни. Насчет побитово - в PHP это точно не дает выгоды, только запутывает код. Но вообще, при инкапсуляции, неважно как хранятся данные внутри класса, так как снаружи это не видно, ты исплоьзуешь методы для доступа к ним, и они скрывают сложность внутренней реализации.
> Там от класса-родителя наследуются, кроме прочих, PowerLine (линии электропередач), при этом в PowerLine не используются ни поля класса-родителя, ни его методы, таким образом, наследование чисто логические.
А, я специально сделал задачу, которая сложно ложится на ООП. В реальных задачах редко бывает так же, как в учебнике, все иделально.
Тут надо определиться, что общего есть у сущностей и что мы вынесем в базовый класс. Это такие особенности:
- все элементы сети можно подключить к сети
- все элементы сети вносят вклад в баланс (хотя PowerLine - неизвестно сколько, пока мы не рассчитаем баланс. Проще всего присвоить ей нулевую мощность и считать покупку/продажу отдельно).
Я бы сделал так:
- базовый класс - ЭлементСети, конструктор NetworkElement(daypower, nightpower), умеет сообщать вклад в энергобаланс днем и ночью (getDayPower/getNightPower). При желании можно сделать один метод getPower(time). Конструктор принимает значения dayPower, nightPower.
- наследники, вызывая конструктор предка, задают значения вклада в сеть
В наследники мы выносим то, что индивидуально для данного элемента. Ну например, у Жилого Дома потребление зависит от числа квартир и формула расчета - это как раз индивидуальная особенность дома. Я бы сделал так:
function House(apartments) {
var dayPower = ...;
bvar nightPower = ...;
// вызываем конструктор предка
NetworkElement.call(this, dayPower, nightPower);
}
Если надо, можно еще добавить свойство и метод для получения числа квартир (в задаче не требуется).
Аналогично реализуется электростанция и солнечная панель.
PowerLine умеет передавать электричество, потому ей мы добавим свойства и методы для получения/задания цены и пропускной способности. Если цена не постоянная и зависит от чего-то (от налогов, времени года, цены на нефть и тд), может стоит ее не делать свойством PowerLine, а считать в другом классе.
Кстати, классы еще можно объединять через интерфейсы без наследования, но в JS их как бы нет.
> при этом в PowerLine не используются ни поля класса-родителя, ни его методы, таким образом, наследование чисто логические.
PowerLine объединяет с другими то, что ее можно подключить в сети, и она вносит вклад в нее, но ради упрощения кода проще принять его равным нулю, так как посчитать его она не способна (она лишь передает энергию, но не принимает решение о закупке). Можно считать вклад снаружи и задавать через сеттер, но это ухудшит код - например при добавлении нового элеимента сети мы обязаны как-то перерассчитать этот вклад. Гораздо проще просто принять вклад равным нулю и считать его в другом месте.
Стоиит избегать случаев, когда в свойстве может быть недостоверное или устаревшее значение - это приведет к куче проблем.
> Однако, вместо добавления нового поля _transmittedPower можно хранить величину в уже имеющемся _generateDayPower, и мне кажется, что это хорошо, в конце концов линии электропередач по условию тоже в некотором роде поставщики/потребители энергии, и я не вижу в таком использовании логической ошибки.
ЛЭП знает только пропускную способность и цену, но сколько фактически по ней передается, она посчитать не может так как не принимает решение о закупке.
> Но так ли это? Вообще, когда нужно следить, чтоб лишних полей не было, а когда можно махнуть на них рукой? Где почитать про это?
Не знаю. Порешай задачки на ООП, можно наши, если тех, что есть мало, я придумаю еще.
> Вот как я бы организовал данные
В общем логично, но с реализацией PowerLine не согласен.
> //PowerLine суть тоже поставщик/потребитель энергии, количество которой можно хранить в _generateDayPower, что логически вроде бы правильно (по-моему), поэтому нужно всего 1 дополнительное поле для стоимости
Еще для пропускной способности надо поле. Пропускная способность != фактически переданный объем.
>>903459
Исключение лишь значит что функция не может выполнить свою работу и вернуть результат, потому она прерывает свое выполнение и возвращает ошибку. Исключения дают отдельный (out-of-band) канал для передачи информации об ошибке, в то время как return false использует тот же канал (in-band) что и результат, и это менее удобно.
>>903173
Давай я тебе дам задачу. Есть какая-то сущность и коллекция этих сущностей (Работник и список работников, Ученик и классный журнал, Товар и Корзина, Товар и Склад). Можешь представить их как объекты или массивы, если не знаешь ООП.
Напиши функцию, определяющую сколько сущностей в коллекции соответствуют какому-то критерию. Критерий может быть любой. Ну например, сколько учеников выше 160 см или имеют среднюю оценку или чья фамилия начинается на гласную.
Напиши функцию, отбирающую массив сущностей, соответствующих определенному критерию.
Напиши функцию, находящую лучшую сущность по произвольному критерию (ученик с самой большой суммой оценок, с самой короткой фамилией, самый тяжелый товар, товар с самой большой скидкой, итд)
Напиши функцию, принимающую на вход массив сущностей и возвращающую массив вычисленных из них по произвольной формуле значений (например: на вход подаем товары с ценой и скидкой, на выходе - массив цен с учетом скидки, на вход - ученики, на выходе - массив средних баллов).
Как ты представишь в своем коде "критерий" или "формула"? Как передать в функцию этот критерий?
>>903173
> как валидировать пользовательский ввод?
либо писать функцию/класс, принимающую данные и выдающую ошибки. Либо делать классы-примитивные ограничения и из них собирать комплексный валидатор. Ну и есть готовые библиотеки:
http://symfony.com/doc/current/components/validator.html (англ)
Вообще, советую попробовать идею с сборкой валидатора из примитивных объектов-ограничений. Ну например:
$validator = new Validator();
$validator->addConstraint('name', new Constraint\NonEmpty()); // имя обязательно
$validator->addConstraint('name', new Constraint\MaxLength(100)); // длина имени не больше 100
$validator->addConstraint('birtyear', new Constraint\Range(1900, date('y'))); // год рождения
$validator->addConstraint('gender', new Constraint\OneOf([GENDER_MALE, GENDER_FEMALE]));
$errors = $validator->validate($user);
// выводим список требований:
echo getRequirementsAsText($validator);
// - поле name обязательно
// - поле name имеет длину не более 100 символов
// ...
там дальше можно это все улучшать, например, добавить сообщения об ошибках с плесйхолдерами вроде "В поле {name} можно ввести не более {count} символов", генерацию форм по валидатору и тд.
Попробуй сделать.
>>903173
Давай я тебе дам задачу. Есть какая-то сущность и коллекция этих сущностей (Работник и список работников, Ученик и классный журнал, Товар и Корзина, Товар и Склад). Можешь представить их как объекты или массивы, если не знаешь ООП.
Напиши функцию, определяющую сколько сущностей в коллекции соответствуют какому-то критерию. Критерий может быть любой. Ну например, сколько учеников выше 160 см или имеют среднюю оценку или чья фамилия начинается на гласную.
Напиши функцию, отбирающую массив сущностей, соответствующих определенному критерию.
Напиши функцию, находящую лучшую сущность по произвольному критерию (ученик с самой большой суммой оценок, с самой короткой фамилией, самый тяжелый товар, товар с самой большой скидкой, итд)
Напиши функцию, принимающую на вход массив сущностей и возвращающую массив вычисленных из них по произвольной формуле значений (например: на вход подаем товары с ценой и скидкой, на выходе - массив цен с учетом скидки, на вход - ученики, на выходе - массив средних баллов).
Как ты представишь в своем коде "критерий" или "формула"? Как передать в функцию этот критерий?
>>903173
> как валидировать пользовательский ввод?
либо писать функцию/класс, принимающую данные и выдающую ошибки. Либо делать классы-примитивные ограничения и из них собирать комплексный валидатор. Ну и есть готовые библиотеки:
http://symfony.com/doc/current/components/validator.html (англ)
Вообще, советую попробовать идею с сборкой валидатора из примитивных объектов-ограничений. Ну например:
$validator = new Validator();
$validator->addConstraint('name', new Constraint\NonEmpty()); // имя обязательно
$validator->addConstraint('name', new Constraint\MaxLength(100)); // длина имени не больше 100
$validator->addConstraint('birtyear', new Constraint\Range(1900, date('y'))); // год рождения
$validator->addConstraint('gender', new Constraint\OneOf([GENDER_MALE, GENDER_FEMALE]));
$errors = $validator->validate($user);
// выводим список требований:
echo getRequirementsAsText($validator);
// - поле name обязательно
// - поле name имеет длину не более 100 символов
// ...
там дальше можно это все улучшать, например, добавить сообщения об ошибках с плесйхолдерами вроде "В поле {name} можно ввести не более {count} символов", генерацию форм по валидатору и тд.
Попробуй сделать.
Элитный теперь Симфони 3.
>>908080
Потому что авторы первого друпала, как и многих других CMS, были не очень хорошие программисты, им просто надо было на коленке слепить админку для управления сайтом, не беспокоясь о качестве кода. Потому они и наизобретали хуков и сделали все на массивах, потому что не смогли придумать архитектуру для большого, сложного проекта. Мы-то знаем, что хорошо организовать сложный код иногла можно с помощью ООП, а для веба хорошо подходит архитектура MVC.
> Правда тогда вроде как вышла новая версия в которой обещали друпал перекатить на ООП и что теперь в нем всё будет ок.
Вроде пока не перекатили все, хотя компоненты Симфони кое-где добавили.
Кодеигнайтер очень старый и там много неправильного. Весь этот $this->load заменяется автозагрузчиком и DI контйенером.
> И тут передо мной открываются собственно 2 пути, либо вызывать из одной модели другую сразу, либо возвращать данные в контроллер из первой, и эти
тебе надо разделить сервисы на 2 слоя, и высокоуровневые сервисы управляют более низкоуровневыим. То есть есть сохранятель в базу, есть посылатель писем, а есть высокоуровневый сервис, который ими командует.
> а рвется наружу простое ООП, где классы должны обмениваться друг с другом данными как им удобно, а не обязательно быть загнанны в такой вот "MVC паттерн".
MVC никак не говорит, что модель должна быть представлена однотипным набором классов и 1 слоем. Ты по моему тут делаешь ошибку. MVC лишь говорит что надо разделить отдельно бизнес-логику, вывод данных и обработку пользовательских действий/запросов.
Реально выучить самому, чтобы ещё на работу взяли, или это как подготовка для ВУЗа, без которого не обойтись?
Для работы в обычной такой веб студии вуз не нужен. Могут взять стажером за еду, если осилишь формочку аяксом отправить
>Надо разобраться, почему.
Вот такая ошибка. Не очень понимаю что не так.
>Если это skeleton, то это сборка, я бы советовал лучше Слим учиться с нуля через композер ставить.
Не знаю что за скелетон, устанавливал командой composer require slim/slim "^3.0" как тут написано.
Ну там же все написано. Кинь сюда свой composer.json. Там какие-то проблемы с версией твоего твига.
return $response->withAddedHeader('location', '/some_page');
Правда может и лучше можно сделать, хуй знает.
{
"require": {
"slim/slim": "^3.0",
"twig/twig": "~2.0"
}
}
Вот такое у меня там написано. Правда не пойму в чем проблема, если и без того в стандартном пакете слима есть твиг.
попробуй "twig/twig": "^1.18"
Лол, ну лучше скажи зачем нужна нода, если пыха в большинстве случаев проще и удобней.
Скорость работы. Нода близка к компилируемым языкам. А где там пхп?
Меньше говнокода. Его, конечно, тоже много, но и не близко в сравнении с пхп.
Ну и "чувак асинхронность".
Я ебал. Завезите нормально нормалный фишки типа пространства имен и ООП(а не то что вы ООП называете), потом может и взлетит.
>пространства имен
Ну это такое, да.
>ООП
ES6/TypeScript к твоим услугам. Ну и поговаривают, что функциональное программирование для богов, ооп не нужно, но это не точно
Как в ноде с тайп-хинтами и с статической типизацией вообще? Что будет, если опечататься в свойстве объекта? Как унаследоваться от Error или Array?
> Скорость работы. Нода близка к компилируемым языкам. А где там пхп?
Пруфы конечно не требуются, мы ведь верим на слово.
> Меньше говнокода.
Серьезно? Я могу согласиться разве что с тем, что на ноде написано меньше кода вообще.
> асинхронность
в php тоже есть немного, хотя для сайтов это не нужно. Подход с синхронным выполнением скрипта проще.
>ES6/TypeScript к твоим услугам
А шо, оопэшные фишки ЕС6 кто-то использует? А то я смотрю там все продолжают ебаться с прототипами.
>Ну и поговаривают, что функциональное программирование для богов,
Не смеши меня. Что в пыхе, что в жыэс функциональщина крайне куцая. Иди расскажи всяким лиспоебам про свою функциональщину, пускай пацаны посмеются.
>ооп не нужно, но это не точно
У тебя монокль шоли выпадает? Не, я на самом деле ничего против жс не имею. Просто это кому как. Меня тошнит от его расхлябанности, от того что все делается методом стучания хуем по клаве. Ну вот для своих задач не слезу с пыхи ради него. Лучше уж аспа или джанго.
> Забыл исправить замечания...
Все верно.
>>899034
>>903312
>>908265
> DOM, который построил Джек. Задача 2.
> table#field {
table тут лишнее, если есть id
> table = createField(table);
Тут наверно возвращать ничего не требуется из функции.
Решено верно.
>>901678
> Алсо вспомнил про синглтон - я когда читал учил ООП понял как его делать, но не очень понял зачем он вообще нужен.
Идея в том, что синглтон позволяет запретить создавать больше 1 экземпляра объекта. Где это нужно - сам не знаю. Использовать его для соединения с БД глупо, так как соединений с БД может быть больше одного, и вообще, надо использовать DI для таких вещей.
Упоминают синглтон часто так как это очень простой паттерн, и на некоторых собеседованиях спрашивают про паттерны, вот люди его и учат, чтобы им было что ответить. Можешь при случае, если речь зайдет, спросить собеседующего, чем сингтон лучше использования DI, почему не может быть более 1 соединения с БД, как при исплоьзовании синглтона дать классу другой объект соединения с БД.
> Забыл исправить замечания...
Все верно.
>>899034
>>903312
>>908265
> DOM, который построил Джек. Задача 2.
> table#field {
table тут лишнее, если есть id
> table = createField(table);
Тут наверно возвращать ничего не требуется из функции.
Решено верно.
>>901678
> Алсо вспомнил про синглтон - я когда читал учил ООП понял как его делать, но не очень понял зачем он вообще нужен.
Идея в том, что синглтон позволяет запретить создавать больше 1 экземпляра объекта. Где это нужно - сам не знаю. Использовать его для соединения с БД глупо, так как соединений с БД может быть больше одного, и вообще, надо использовать DI для таких вещей.
Упоминают синглтон часто так как это очень простой паттерн, и на некоторых собеседованиях спрашивают про паттерны, вот люди его и учат, чтобы им было что ответить. Можешь при случае, если речь зайдет, спросить собеседующего, чем сингтон лучше использования DI, почему не может быть более 1 соединения с БД, как при исплоьзовании синглтона дать классу другой объект соединения с БД.
Обычно делают класс для работы с формами, в нем задают набор полей (либо он создается на основе модели, для которой предназначена форма), а имея список полей, мы можем перенести данные из GET/POST в объект модели.
Валидацию удобно делать, сделав примитивные объекты-ограничения (NonEmtpy, MaxLength) и далее создавая правила валидации из таких объектов:
$validator = new Validator;
$validator->addRule('name', new Constraint\MaxLength(100));
$errors = $validator->validate($data);
Иногда делают объекты, представляющие разные виды полей, и собирают форму из них:
$form = new Form;
$form->add(new TextField('name', 'Имя'));
...
Можешь глянуть компоненты Symfony Validation и Symfony Forms.
> Нормально будет, если под форму на стороне разработчика будет создаваться модель со свойствами равными полям формы.
Часто форма используется для редактирования модели из БД и отдельная модель формы не требуется.
Вообще, у меня еще есть урок про формы: https://github.com/codedokode/pasta/blob/master/forms.md
>>901702
Пусть заполняются. Не понимаю, в чем проблема.
>>902113
Есть анонимные функции и ссылки на методы.
>>902274
там просто сервер настроен так, что не пытается искать файл, соответствующий URL, а сразу вызывает например index.php.
>>902289
Почитай документацию по Юи, Симфони.
Обычно делают класс для работы с формами, в нем задают набор полей (либо он создается на основе модели, для которой предназначена форма), а имея список полей, мы можем перенести данные из GET/POST в объект модели.
Валидацию удобно делать, сделав примитивные объекты-ограничения (NonEmtpy, MaxLength) и далее создавая правила валидации из таких объектов:
$validator = new Validator;
$validator->addRule('name', new Constraint\MaxLength(100));
$errors = $validator->validate($data);
Иногда делают объекты, представляющие разные виды полей, и собирают форму из них:
$form = new Form;
$form->add(new TextField('name', 'Имя'));
...
Можешь глянуть компоненты Symfony Validation и Symfony Forms.
> Нормально будет, если под форму на стороне разработчика будет создаваться модель со свойствами равными полям формы.
Часто форма используется для редактирования модели из БД и отдельная модель формы не требуется.
Вообще, у меня еще есть урок про формы: https://github.com/codedokode/pasta/blob/master/forms.md
>>901702
Пусть заполняются. Не понимаю, в чем проблема.
>>902113
Есть анонимные функции и ссылки на методы.
>>902274
там просто сервер настроен так, что не пытается искать файл, соответствующий URL, а сразу вызывает например index.php.
>>902289
Почитай документацию по Юи, Симфони.
Угол между стрелками: http://ideone.com/4KXN1O
Верно.
Вывод знака зодиака: http://ideone.com/Fg7RMI
> Тогда и номер месяца можно убрать, если его можно получить из индекса знака зодиака в массиве?
Можно, хотя, возможно, читать такой список будет немного труднее.
> // PHP не поддерживает негативные индексы для массивов, поэтому приходится извращаться
отрицательные индексы допустимы, только это будет отдельный элемент.
Решено верно.
Дерево
Вот таике длинные строки лучше разбить на две команды:
> stack = stack.concat(list.filter(node => node.parentId === current.id));
> но не совсем понятно, как выводить список вложенным, а не просто показывать список посещённых элементов
нужно класть в очередь еще и глубину
> Сделал очень костыльно с добавлением ключа depth к каждому элементу массива: https://jsfiddle.net/0gpbzo8y/2/
Наверно лучше не модифицировать объект, а создавать обертку вида { node: ..., depth: ... }.
Вместо обнуления счетчика (что неверно) надо брать depth из current.
> Алсо я вспомнил про задачу на джуна, которую тут вбрасывали пару тредов назад, приложил скрин к посту. Как правильно реализовать третью часть? На запрос вида /catalog/samsung/s7 мне нужно выдавать товар, у которого path = s7, path родителя samsung, path его родителя = catalog.
Проще всего сделать несколько запросов, ища запись по path + parentId. Там глубина все равно не будет большая.
Твой код с подзапросами можно переписать на джойны, что даст больше свободы для оптимизации, но суть останется та же.
Твой код получается сложным и запутанным. В такой ситуации можно использовать Query Builder, но проще просто делать несколько запросов.
Дом 2
> (var i = 0; i < 6; i++){
лучше было сделать i < WIDTH.
> if (cell.classList.contains('marked')) {
Вроде есть метод toggle() ?
Учти что mousedown срабатывает на любую клавишу мыши включая по моему нажатие на колесо.
Решено верно.
N рабочих дней
Да, алгоритм в твоем решении вполне допустим (хотя оно не проверяет субботу, воскресенье и отмененные выходные). Хотя его можно оптимизировать, а именно:
- найти ближайший праздник (если они отсортированы, то можно делением пополам)
- вычислить, сколько до него дней
- уменьшить $n на min($n, $daysLeft), одновременно прибавив дату
С выходными и отмененными выходными это конечно усложнится (хотя... может и тут можно как-то оптимизровать, например автоматически добавляя 2 дня на каждые 5). Не уверен, стоит ли заморачиваться.
Для больших интервалов (1000 рабочих дней) алгоритм будет не очень быстрый, но его можно оптимизировать для таких случаев:
- создать список, где рассчитано число рабочих дней в каждом месяце, неделе или может годе
- дойти по дням до начала ближайшего месяца
- пропустить нужное число месяцев
- пройти остаток по дням
>>908238
Тебе надо зайти в packagist.org и посмотреть, какие зависимости у указанных тобой версий. Скорее всего проблема в том, что для указанных тобой версий пакетов там в зависимостях стоит twig выше 2.1, но он помечен как нестабильный, а у тебя по умолчанию стоит настройка ставиить только стабильные версии. Надо либо ее отключить, либо поставить менее жесткие требования к версиям слима и слим-твига.
Либо у тебя старый PHP, и новый твиг на нем не работает. Все это указано в composer.json нужной библиотеки.
Ты требуешь новый слим версии выше 3, а для него наверно нужны новые библиотеки и новая версия PHP.
Примерно это композер и пытается сказать - он тебе даже пишет конкретные версии, которые подходят под требования.
Попробуй еще добавить опцию -v при выове композера - может он подробнее напишет?
>>908245
Еще код 3xx надо добавить. Вообще, тебе надо изучить PSR-7, там описан объект ответа.
https://www.google.ru/search?q=psr-7&newwindow=1&gbv=1&sei=cVlyWNm1KIWTsgHymI-YBw
Угол между стрелками: http://ideone.com/4KXN1O
Верно.
Вывод знака зодиака: http://ideone.com/Fg7RMI
> Тогда и номер месяца можно убрать, если его можно получить из индекса знака зодиака в массиве?
Можно, хотя, возможно, читать такой список будет немного труднее.
> // PHP не поддерживает негативные индексы для массивов, поэтому приходится извращаться
отрицательные индексы допустимы, только это будет отдельный элемент.
Решено верно.
Дерево
Вот таике длинные строки лучше разбить на две команды:
> stack = stack.concat(list.filter(node => node.parentId === current.id));
> но не совсем понятно, как выводить список вложенным, а не просто показывать список посещённых элементов
нужно класть в очередь еще и глубину
> Сделал очень костыльно с добавлением ключа depth к каждому элементу массива: https://jsfiddle.net/0gpbzo8y/2/
Наверно лучше не модифицировать объект, а создавать обертку вида { node: ..., depth: ... }.
Вместо обнуления счетчика (что неверно) надо брать depth из current.
> Алсо я вспомнил про задачу на джуна, которую тут вбрасывали пару тредов назад, приложил скрин к посту. Как правильно реализовать третью часть? На запрос вида /catalog/samsung/s7 мне нужно выдавать товар, у которого path = s7, path родителя samsung, path его родителя = catalog.
Проще всего сделать несколько запросов, ища запись по path + parentId. Там глубина все равно не будет большая.
Твой код с подзапросами можно переписать на джойны, что даст больше свободы для оптимизации, но суть останется та же.
Твой код получается сложным и запутанным. В такой ситуации можно использовать Query Builder, но проще просто делать несколько запросов.
Дом 2
> (var i = 0; i < 6; i++){
лучше было сделать i < WIDTH.
> if (cell.classList.contains('marked')) {
Вроде есть метод toggle() ?
Учти что mousedown срабатывает на любую клавишу мыши включая по моему нажатие на колесо.
Решено верно.
N рабочих дней
Да, алгоритм в твоем решении вполне допустим (хотя оно не проверяет субботу, воскресенье и отмененные выходные). Хотя его можно оптимизировать, а именно:
- найти ближайший праздник (если они отсортированы, то можно делением пополам)
- вычислить, сколько до него дней
- уменьшить $n на min($n, $daysLeft), одновременно прибавив дату
С выходными и отмененными выходными это конечно усложнится (хотя... может и тут можно как-то оптимизровать, например автоматически добавляя 2 дня на каждые 5). Не уверен, стоит ли заморачиваться.
Для больших интервалов (1000 рабочих дней) алгоритм будет не очень быстрый, но его можно оптимизировать для таких случаев:
- создать список, где рассчитано число рабочих дней в каждом месяце, неделе или может годе
- дойти по дням до начала ближайшего месяца
- пропустить нужное число месяцев
- пройти остаток по дням
>>908238
Тебе надо зайти в packagist.org и посмотреть, какие зависимости у указанных тобой версий. Скорее всего проблема в том, что для указанных тобой версий пакетов там в зависимостях стоит twig выше 2.1, но он помечен как нестабильный, а у тебя по умолчанию стоит настройка ставиить только стабильные версии. Надо либо ее отключить, либо поставить менее жесткие требования к версиям слима и слим-твига.
Либо у тебя старый PHP, и новый твиг на нем не работает. Все это указано в composer.json нужной библиотеки.
Ты требуешь новый слим версии выше 3, а для него наверно нужны новые библиотеки и новая версия PHP.
Примерно это композер и пытается сказать - он тебе даже пишет конкретные версии, которые подходят под требования.
Попробуй еще добавить опцию -v при выове композера - может он подробнее напишет?
>>908245
Еще код 3xx надо добавить. Вообще, тебе надо изучить PSR-7, там описан объект ответа.
https://www.google.ru/search?q=psr-7&newwindow=1&gbv=1&sei=cVlyWNm1KIWTsgHymI-YBw
http://lurkmore.to/Копипаста:Программирование#.D0.92.D1.8B.D1.81.D1.88.D0.B5.D0.B5_.D0.BE.D0.B1.D1.80.D0.B0.D0.B7.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D0.B5_.D0.B2_IT_.D1.81.D0.B2.D0.BE.D0.B8.D0.BC.D0.B8_.D1.80.D1.83.D0.BA.D0.B0.D0.BC.D0.B8_v2
в яватреде кажется видел как анон неделю назад ебался с такой же проблемой. Сходи там посмотри как решили ну и на вскидку ты должен быть суперадмином на компе + загугли как попасть в управление файлами. (вангую что правой кнопкой по Мой_компьютер -> управление -> а дальше хз)
Ладно, пусть будет. Оп, посмотри пожалуйста. https://github.com/greenTea242/Minesweeper_JS
У меня один вопрос пока что. Почему-то неправильно центрируется попап (я знаю, что еще дополнительно надо сделать margin на половину offsetWidth/offsetHeight, вышесказанное можно раскомментировать в коде. Оставил так, потому что более видно проблему. Красный кружок - там где должен быть попап).
Ну вдобавок почему-то не все элементы отсюда https://unicode-table.com/ru/#miscellaneous-symbols хотели отображаться, взял которые работали, думаю не смертельно.
Еще в моем коде меня заботит алгоритм метода Field.prototype._openTheCell(), при большом поле он показывает не самые быстрые результаты (все может создаваться две секунды и больше).
Первый ответ: https://toster.ru/q/321702
Читать исходники готовых роутеров:
http://symfony.com/doc/current/routing.html
https://github.com/auraphp/Aura.Router
Благодарю.
Ребят тут есть кто уже решает оповские задания?
Хочу найти народ в команду по изучению PHP.
Будем вместе сидеть в скайпе и кодить, так веселей и продуктивней, чтобы не лениться.
Начнем с решения задачи про студентов.
Есть базовый кастомный фреймворк и самописный MVC под него.
Надо чтобы вы уже знали HTML, CSS, JS, PHP на элементарном уровне, чтобы отбросить теорию и приступить к практике.
Оставляйте контакты я вам напишу.
kxS(alter07ANUSgmb&0ailPUNCTUMc^ITom
Доделываю задачки по ООП. HTML, CSS знаю основы, JS еще не изучал.
Отписал.
https://github.com/codedokode/pasta/blob/master/db/patterns-oop.md
Поясни за Table Data Gateway. Непонятный момент такой: В TDG как я понял каждый класс отвечает за одну таблицу. Что делать, если таблиц куча и практически каждый запрос задействует множество сложных джойнов? Любой запрос получается не к одной таблице, а сразу к многим. Где писать тогда эти запросы и как организовывать TDG? Один класс TDG для всех таблиц сразу? А если еще любой insert сразу инсертит в несколько таблиц? Куда тогда эти инсерты пихать? Если делать один большущий класс TDG на все таблицы, то это полный беспорядок в коде будет.
Не совсем, одна сущность - одна таблица, вот так вот. Т.е. вот есть у тебя студент - значит все методы для работы со студентом в БД лежат в одном классе. Так что насколько я понимаю, тут уже похуй что у тебя там с джойнами.
Но лучше пусть ОП еще подскажет. Алсо, если дохуя сущностей-таблиц то лучше уже посматривать в сторону active record.
Насчет джойнов - надо смотреть по основной (с точки зрения логики) таблице.
Насчет инсертов - один SQL запрос не может инсертить в несколько таблиц сразу. Если при вставке в одну таблицу надо делать вставки еще в другую, то можно либо сделать как с джойнами, либо сделать класс-сервис, вызывающий методы двух TDG по очереди. Вариант с сервисом хорошо подходит, если там еще что-то кроме вставки в БД надо делать.
А разве этив рекорд не нужен как раз что бы не было такого гемороя? Типа сделал CRUD в основном классе, а там уже наследуйся @ перегружай методы.
Бамп проблеме. Гуглил много, судя по всему этот флажок убрать невозможно. Тогда как скриптом записывать файлы?
я пока нашел teamcity, но он нереально огромный, тормозной (для моих задач) и мне там нужна всего одна функция выгрузки через фтп по некоему триггеру.
Лел, разобрался с проблемой, оказывается это я просто не правильно указывал директорию(надо было еще к директории приписывать имя файла). Теперь парочка вопросов по поводу архитектуры файлообменника:
1. Можно ли из контроллере(т.е. в колбеке метода get() слима) сразу же отправлять принмаемый из формы файл в нужную директорию, или это грубое нарушение MVC и это надо обязательно отдавать в модель? Просто в первом случае несколько попроще будет, меньше всего в модель передавать.
2. Как хранить файлы на стороне сервера? Просто все кидать в одну папку типа uploads, или внутри как разбивать в зависимости от типа файла?
И как лучше - с текущими знаниями выполнять какие-то заказы и параллельно изучать или сейчас все свободное время посвятить своим идеям/проектам и потом уже идти на известные сайты?
Я поставил предлагаемую библиотеку, вшитые туда примеры работают как надо, но описанные в мануале по ссылке при этом не работают. Может ли кто из знающих сказать что я делаю не так и что нужно сделать чтобы заработало?
Про Angularjs:
> как модуль {директива + контроллер + сервис}
Однако, чем больше я копаюсь в нем, тем чётче складывается представление что это шоколадная посыпка на торте из говна.
Я даже не знаю. Единственное, чему можно радоваться - пришло понимание MVC. (Теперь я понимаю, почему ОП советует копать фреймворки перед исследованием iпаттернов etc.) Спасибо, ОП.
Ты по моему не очень понимаешь, что такое AR. AR это когда класс/объект представляет и строку из базы данных, и содержит методы для ее загрузки/сохранения в БД. Ты уверен, что ничего не перепутал?
>>908341
Опиши, что именно не так. Что ты делаешь, что должно произойти, и что происходит вместо этого. А ты привел только сообщение об ошибке, не написав как оно появляется.
Насчет прав на папку - если я не путаю, флажок "только для чтения" не запрещает запись и является лишь рекомендацией для программ. Чтобы его поменять, наверно надо иметь права на смену атрибутов папки.
Реально права задаются в ACL, это вкладка "доступ" в свойствах папки.
>>908929
> 1. Можно ли из контроллере(т.е. в колбеке метода get() слима) сразу же отправлять принмаемый из формы файл в нужную директорию, или это грубое нарушение MVC и это надо обязательно отдавать в модель
Это плохая идея. Лучше иметь сервис, в котором есть методы для проверки и сохранения файла. Чтобы мы всегда могли бы программно сохранить файл (даже если мы не собираемся это делать, просто чтобы код был логически разделен на част). Вот тебе дополнительное задание, чтобы ты понял:
- сделай скрипт для командной строки, который загружает указанный файл в файлообменник и выводит ссылку на страницу с ним. Например, так:
> php bin/upload.php c:/tmp/file.jpg
http://example.com/file/123
Скрипт должен проверять файл так же, как он проверяется при загрузке через веб-интерфейс (например, не превышен ли размер). При этом избегай копипасты, код сохранения файла не должен быть скопипащен 2 раза.
> 2. Как хранить файлы на стороне сервера? Просто все кидать в одну папку типа uploads, или внутри как разбивать в зависимости от типа файла?
В комментариях к задаче написано, что надо учитывать при сохранении файла:
- безопасность, чтобы нельзя было сохранить файл с расширением php или именем htaccess
- уникальность, чтобы у каждого файла было уникальное имя и они не перезаписали друг друга
- желательно ограничить число файлов в папке и для этого раскладывать их в подпапки, например по дате или по номеру
Ты по моему не очень понимаешь, что такое AR. AR это когда класс/объект представляет и строку из базы данных, и содержит методы для ее загрузки/сохранения в БД. Ты уверен, что ничего не перепутал?
>>908341
Опиши, что именно не так. Что ты делаешь, что должно произойти, и что происходит вместо этого. А ты привел только сообщение об ошибке, не написав как оно появляется.
Насчет прав на папку - если я не путаю, флажок "только для чтения" не запрещает запись и является лишь рекомендацией для программ. Чтобы его поменять, наверно надо иметь права на смену атрибутов папки.
Реально права задаются в ACL, это вкладка "доступ" в свойствах папки.
>>908929
> 1. Можно ли из контроллере(т.е. в колбеке метода get() слима) сразу же отправлять принмаемый из формы файл в нужную директорию, или это грубое нарушение MVC и это надо обязательно отдавать в модель
Это плохая идея. Лучше иметь сервис, в котором есть методы для проверки и сохранения файла. Чтобы мы всегда могли бы программно сохранить файл (даже если мы не собираемся это делать, просто чтобы код был логически разделен на част). Вот тебе дополнительное задание, чтобы ты понял:
- сделай скрипт для командной строки, который загружает указанный файл в файлообменник и выводит ссылку на страницу с ним. Например, так:
> php bin/upload.php c:/tmp/file.jpg
http://example.com/file/123
Скрипт должен проверять файл так же, как он проверяется при загрузке через веб-интерфейс (например, не превышен ли размер). При этом избегай копипасты, код сохранения файла не должен быть скопипащен 2 раза.
> 2. Как хранить файлы на стороне сервера? Просто все кидать в одну папку типа uploads, или внутри как разбивать в зависимости от типа файла?
В комментариях к задаче написано, что надо учитывать при сохранении файла:
- безопасность, чтобы нельзя было сохранить файл с расширением php или именем htaccess
- уникальность, чтобы у каждого файла было уникальное имя и они не перезаписали друг друга
- желательно ограничить число файлов в папке и для этого раскладывать их в подпапки, например по дате или по номеру
На сайтах по фрилансу видел по работе в WP, по нему знания можно получить именно из литературы, приведённой в треде или что-то отдельно изучать?
Хз, хуйня всё это, просто садись и учи уже блядь. Никто не гарантирует тебе нихуя тут ничего. Я учу с дикими перерывами два года уже.
Если бы задротил нон стоп то за три месяца достиг бы всего что знаю за эти два года. Иногда тупо лень, иногда после работы уже нет сил нихуя делать (2 веб-мартышко работы сменил уже, на которых не особо качается скилл) пока учу всю хуйню из оп-поста.
Учить реально дохера. В вузе тоже тебе бы дали бы основы любого языка + какие-нибудь базы данных а дальше ебись сам. Другое дело что там за ручку водят и знания с тебя требуют, а тут ты сам себе хозяин. Если сложный вопрос вкидываешь то никто и не поможет ибо такие же нубы. ОП тоже не всегда доступен, но часто отвечает, ДО СИХ ПОР СПУСТЯ 3 ГОДА, за что ему респект.
Много. После php7 еще и больше стало, язык снова популярность набирает.
Я бы не сказал, что в ВУЗе научат чему-то такому, чему ты не можешь научиться сам. Если ВУЗ не один из специализирующихся на компьютерных науках (вроде ИТМО итд, команды из которых выигрывают олимпиады по программированию) то там ничему особенному не научат.
Но не везде и нужны победители олимпиад.
>>909092
Не водят в вузе за ручку. Препод нудным голосом читает что-то с листочка, а потом на практике требует от тебя написать код. Понятно что 95% либо списывают либо потом год ходят на пересдачи. Оставшиеся 5% вроде ОПа сами изучают что им нужно и легко сдают зачет.
>>909082
6-8 месяцев это среднее между теми, кто изучает за несколько месяцев и теми, кто изучает год.
Я не знаю, но почему бы тебе не посидеть 2-3 недели, поучить и за это время и оценить темп изучения?
Проще всего сделать на сервере клон репозитория с гитхаба и делать там pull чтобы забрать изменения с гитхаба.
Тут есть 2 варианта: можно этот репозиторий поместить в папку веб-сервера, но тогда надо закрыть доступ к папке .git (а то смогут скачать исходники из файлов репозитория) и в процессе обновления файлов могут быть баги (условно, половина файлов заменилась на новые, а половина еще старая).
Если ты используешь композер и поменял что-то в composer.lock, то тебе надо на сервере делать composer install для обновления зависимостей. На время обновления в папке vendor может отстутсовать часть файлов и сайт может потому не работать.
Для изменений в БД советую использовать скрипты миграции базы данных.
Если посетителей мало, то ошибку в момент обновления вряд ли кто заметит, но все равно, неаккуратно. Некоторые потому перед обновлением вешают на сервер заглушку с кодом 503, обновляют код, снимают заглушку. Разумеется, bash-скриптом, а не руками.
Я бы советовал учиться делать "бесшовный" деплой. Он делается так:
- делаем где-то отдельно клон репозитория
- делаем pull, чтобы забрать последние новые файлы
- создаем (скриптом) новую папку, например, /var/www/site/v2/
- в нее копируем код из локального репозитория
- прогоняем composer install
- выполняем прогрев (генерацию) кеша, если это нужно
- в конфиге веб-сервера меняем корневую папку на новую
- делаем серверу reload, чтобы он перечитал конфиг
При таком подходе мы атомарно переключаемся на новую версию кода и ни один клиент не должен получить ошибки.
В простейшем случае это делается баш-скриптами, но можно попробовать использовать и системы вроде ansible (хотя я их не использовал).
Проще всего сделать на сервере клон репозитория с гитхаба и делать там pull чтобы забрать изменения с гитхаба.
Тут есть 2 варианта: можно этот репозиторий поместить в папку веб-сервера, но тогда надо закрыть доступ к папке .git (а то смогут скачать исходники из файлов репозитория) и в процессе обновления файлов могут быть баги (условно, половина файлов заменилась на новые, а половина еще старая).
Если ты используешь композер и поменял что-то в composer.lock, то тебе надо на сервере делать composer install для обновления зависимостей. На время обновления в папке vendor может отстутсовать часть файлов и сайт может потому не работать.
Для изменений в БД советую использовать скрипты миграции базы данных.
Если посетителей мало, то ошибку в момент обновления вряд ли кто заметит, но все равно, неаккуратно. Некоторые потому перед обновлением вешают на сервер заглушку с кодом 503, обновляют код, снимают заглушку. Разумеется, bash-скриптом, а не руками.
Я бы советовал учиться делать "бесшовный" деплой. Он делается так:
- делаем где-то отдельно клон репозитория
- делаем pull, чтобы забрать последние новые файлы
- создаем (скриптом) новую папку, например, /var/www/site/v2/
- в нее копируем код из локального репозитория
- прогоняем composer install
- выполняем прогрев (генерацию) кеша, если это нужно
- в конфиге веб-сервера меняем корневую папку на новую
- делаем серверу reload, чтобы он перечитал конфиг
При таком подходе мы атомарно переключаемся на новую версию кода и ни один клиент не должен получить ошибки.
В простейшем случае это делается баш-скриптами, но можно попробовать использовать и системы вроде ansible (хотя я их не использовал).
Опиши подробнее, что ты делаешь, что должно произойти, и что происходит не так. Ты даже не написал, как ты запускаешь код (через веб-сервер или в консоли). Не написал, есть ли что в логе ошибок. Чему у тебя равна настройка display_errors.
>>908639
Скайп хотя бы номер телефона не требует. Хотя насколько я знаю, он уязвим и там легко можно блокировать аккаунты, а также определять IP. Так что лучше заведите отдельный аккаунт, который не жалко.
>>908365
Удобнее на гитхаб. Если лень создавать репозиторий, можно в gist.github.com, там можно несколько файлов добавить.
>>908334
Паста длинная, просмотрел по диагонали. Человек советует изучать компьютерные науки очень основательно. На практике во многих областях настолько глубоких знаний не требуется, хотя потихоньку изучать их параллельно с работой не помешало бы.
Ну и не думай, что можно немножечко поучиться, потом начать работать и бросить обучение. Учиться надо и дальше, иначе твоя карьера быстро остановится, а может и пойдет вниз. IT - не работа на конвеере, где достаточно посмотреть и повторять увиденное.
Может кому-то, кто тоже с нуля изучает, будет интересно почитать.
Хочу только предупредить, хоть он и пытается с помощью своеобразной манеры письма притвориться обычным неграмотным аноном, на самом деле он как раз довольно сообразительный, и если у вас получается все не так быстро, как у него, то это нормально.
На пыхе работ больше, в жопу питон.
Алсо пока я не нахуярил кучу кучу бессмысленной хуйни предлагаю помочь мне и направить в нужное русло, чекните пока там всего нихуя строк и так же помогите разобраться в чем сейчас там ошибка:
http://ideone.com/z9jbtF
Знак доллара лишний в строке, где ошибка. Он берет имя свойства из несуществующей переменной.
спасибо большое! Постоянно на этом дерьме ошибаюсь и не могу еще и потом заметить :(
>Ты по моему не очень понимаешь, что такое AR. AR это когда класс/объект представляет и строку из базы данных, и содержит методы для ее загрузки/сохранения в БД. Ты уверен, что ничего не перепутал?
Просто я видел такую фишку, когда делали абстрактный класс model в котором просто были сделаны операции типа insert и delete, а под конкретную типа student от него наследовались, называли это актив рекорд.
>Опиши, что именно не так.
Уже разобрался, моя ошибка.
>Лучше иметь сервис, в котором есть методы для проверки и сохранения файла. Чтобы мы всегда могли бы программно сохранить файл (даже если мы не собираемся это делать, просто чтобы код был логически разделен на част).
О, отлично. Только это же все равно логически относится к моделям, правда?
>сделай скрипт для командной строки, который загружает указанный файл в файлообменник и выводит ссылку на страницу с ним.
Наверное когда основный фичи сделаю, а то сейчас я не умею рабоать с консолью в пхп.
>безопасность, чтобы нельзя было сохранить файл с расширением php или именем htaccess
Такие файлы стоит просто запрещать загружать или же менять им расширение?
Challenge accepted
Тут вроде бывали ребята, которые даже не делая студентов устраивались, так то. Правда мне тяжело себе представить что это за работа может быть.
error_reporting(-1);
$creditBalance = 40000; / Долг анона перед банком /
$percent = 1.03; / Банк начисляет 3% в месяц от суммы /
$servicePayment = 1000; / А также 1000 рублей в месяц комиссии за обслуживание счета /
$monthlyPayment = 5000; / Анон платит 5000 р в месяц, это все, что ему дает мама на завтраки /
$paymentTotal = 0; / Сколько всего отдал банку анон /
/ Посчитаем расходы 20 раз на 20 месяцев вперед /
for ($month = 1; $month <= 20; $month ++) {
$creditBalance = ( $creditBalance * $percent ) + $servicePayment - $monthlyPayment;
$paymentTotal = $paymentTotal + $monthlyPayment;
if ($creditBalance >= $monthlyPayment) {
echo "{$month} месяц спустя: долг = {$creditBalance} руб, выплачено всего {$paymentTotal} руб. \n";}
else {$paymentTotal = $paymentTotal + $creditBalance;
$creditBalance=0;
echo "{$month} месяц спустя: долг = {$creditBalance} руб, выплачено всего {$paymentTotal} руб. \n";
break;}
Можете подсказать, почему конечный результат отличается?
В последней итерации в $paymentTotal падает платеж в 5к и следом, в блоке else, к нему добавляется остаток по кредиту.
Где phpMyAdmin хранит комментарии к ячейкам таблицы?
Участвуют ли они в запросах?
Есть ли разница в производительности запросов с ними / без?
Какой движок (MyIsam, InnoDb, другой?) подойдет лучше для ситуации:
2 таблицы, очень активная выборка из обеих (иногда даже ВСЁ берется), примерно 100000 записей в год в каждую (таблицы обнуляются каждый год! То есть максимум, который в них хранится = приблизительно 100000 записей).
Не будут ли тормозить запросы в конце года?
Я читал про статические таблицы, насколько понял, привел структуру полей в соответствие требованиям статической таблицы: поля типов char, int, decimal, date, timestamp (Если это важно для предыдущих вопросов).
Нормализация 3NF (Если это важно для предыдущих вопросов).
(Постарался все вопросы сразу задать, как просит ОП).
wordpress)))
http://ideone.com/3GlnfX
Форматирование - пиздец, ты не должен if сдвигать на табуляциюя влево просто потому что, как и каждый последудющий элсиф просто так фо лулз)))
Cпасибо. Думал что даже никто и не посмотрит на моё говно. А что не так с константами, конфиг отдельно выносить?
Всё у тебя так с константами. Вообще пока не стал писать сложные проекты с хотя бы отдельными функциями можешь ничего никуда не выносить, просто хуярь скрипты целиком в 1 файле.
А так далее по логике. Я бы сначала проверял на пустоту, а потом бы уже фильтровал данные.
Далее у тебя идет цепочка ошибок, когда сработает всегда только 1 условие. А ты попробуй сделать так, что сколько незаполненных / неправильно заполненных полей, то столько и ошибок выводится сразу пользователю.
Далее:
if($_SERVER['REQUEST_METHOD'] == 'GET'){
$sql = 'DELETE FROM post WHERE id = ' . $_GET['delete'] . '';
mysqli_query($connect,$sql);
}
ну я будучи кем угодно могу удалить чей угодно пост и там уязывимость, я могу написать гет запрос delete=qweqweqwe
получить эрор который мне потроха твоей базы выдаст с названиями таблиц, далее я пишу второй гет запрос в духе:
delete=1;drop table bla bla и рушу твою гостевуху к хуям, надо тут хотя бы сделать проверку что бы ничего кроме чисел не пропускалось.
А в целом всё норм, доебаться можно много до чего, но если у тебя работает то и найс. Сам смотри как бы ты хотел улучшать.
За ООП пока не стоит браться? если я смогу например простой бложик написать или простейшую борду
данные не записываются в файл. Юзаю класс SimpleXMLElement
вот код
$dom = simplexml_load_file('DB/DB.xml');
$templates = $dom->templates;
$template = $templates->addChild("template", 'dsfgdsheshj tshs hsh shh sssssshrthrth');
print_r($dom);
$dom->asXML();
выводит объект нормально, но в файл не записывает.
Что делать?
сам файл с XML
<?xml version="1.0" encoding="UTF-8"?>
<data>
<poligons>
<poligon id="1">
<id>E1</id>
<canvas>
<width>800</width>
<height>800</height>
</canvas>
<coords>{ "poligon": [{"x": 50,"y": 50},{"x": 750,"y": 50},{"x": 750,"y": 600},{"x": 650,"y": 600},{"x": 650,"y": 750},{"x": 150,"y": 750},{"x": 150,"y": 600},{"x": 50,"y": 600}] }</coords>
</poligon>
<poligon id="2">
<id>E2</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
<poligon id="3">
<id>E5</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
<poligon id="4">
<id>E9</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
</poligons>
<templates>
</templates>
</data>
сам файл с XML
<?xml version="1.0" encoding="UTF-8"?>
<data>
<poligons>
<poligon id="1">
<id>E1</id>
<canvas>
<width>800</width>
<height>800</height>
</canvas>
<coords>{ "poligon": [{"x": 50,"y": 50},{"x": 750,"y": 50},{"x": 750,"y": 600},{"x": 650,"y": 600},{"x": 650,"y": 750},{"x": 150,"y": 750},{"x": 150,"y": 600},{"x": 50,"y": 600}] }</coords>
</poligon>
<poligon id="2">
<id>E2</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
<poligon id="3">
<id>E5</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
<poligon id="4">
<id>E9</id>
<canvas>
<width>500</width>
<height>500</height>
</canvas>
<coords/>
</poligon>
</poligons>
<templates>
</templates>
</data>
Стена кода без разбиения на функции. тебе надо научиться разбивать код на отдельные действия и оформлять их как функции с четко определенными входными и выходными значениями. Позже - учиться разбивать код на классы.
Обработка ошибок сделана неправильно. Ты показываешь ошибку пользователю (которому она не нужна), но не пишешь в лог (который нужен тебе).
Также, при ошибке ты все равно продолжаешь выполнять скрипт. Открой для себя исключения, у меня есть урок по ним https://github.com/codedokode/pasta/blob/master/php/exceptions.md
В некоторых случаях после вызова функций работы с БД информация об ошибке не запрашивается и не сохраняется никуда.
filterStr используется неправильно. Надо экранировать только данные, передаваемые в БД, и лучше использовтаь плейсхолдеры и подготовленные запросы: https://github.com/codedokode/pasta/blob/master/security/sql-injection.md
После успешной обработки POST запроса надо делать редирект, чтобы при обновлении страницы он не отправлялся заново.
Я бы советовал тебе подучить наш учебник (только то, что ты еще не знаешь), и затем делать студентов из ОП поста, или хотя бы почитать подробные комментарии в этой задаче.
Пока уровень очень слабый, тебе надо многому учиться. Код низкого качества, его тяжело поддерживать и понимать. Я например не могу сказать быстро, правильно ли сделана проверка данных, или нет.
А, пост выше - я тебе написал. И прочитай еще про XSS: https://github.com/codedokode/pasta/blob/master/security/xss.md
Ну и HTML надо вынести в отдельный файл.
Константы лучше не использовать для настроек БД, либо обычные переменные, либо конфиг.
Данные в форме при ошибке надо сохранять. Почитай урок https://github.com/codedokode/pasta/blob/master/forms.md
Удаление надо делать через POST.
>>909604
Без ООП у тебя и блог получится скорее всего не очень хороший, не говоря о более сложных проектах. Я такие видел - стена лапши и копипасты.
Если ты думаешь о работе, то без ООП можешь даже не приходить на собеседование.
Кстати наш учебник в ОП посте не так и плохо объясняет ООП - я вижу, что после него у анонов получается вполне терпимый код.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
Ну что, пора бы проверить файлообменник.
> https://github.com/never3ver/fileshare/blob/master/copyBootstrap.php#L4
Вместо opendir/readdir лучше исплоьзовать scandir() (readdir выгоден если файлов очень много и не хочется их названия все сразу грузить в память, а хочется обрабатывать по одному). Либо же использовать ООП-ориентированный и немного странно спроектированный класс DirectoryIterator.
Для рекурсивного обхода папки есть еще RecursiveDirectoryIterator.
Скрипт не проверяет результат вызова copy (да и opendir) - PHP при ошибке по умолчанию просто выведет варнинг и продолжит скрипт.
> https://github.com/never3ver/fileshare/blob/master/test_fileshare.sql
> `name` varchar(45) DEFAULT NULL,
> `tmpName` varchar(45) DEFAULT NULL,
> `type` varchar(45) DEFAULT NULL,
Вот к таким колонкам стоило бы добавить COMMENT '...' с описанием, что именно там хранится, так как не очень понятно. И надеюсь, ограничение на 45 символов проверяется в коде?
https://github.com/never3ver/fileshare/blob/master/public/.htaccess#L7
> AddEncoding gzip .gz
Это конечно хорошая идея, добавить поддержку gzip, но тогда надо дописать генератор сжатых версий файлов (и тщательно протестировать это в разных браузерах).
В условия, кроме проверки наличия сжатого файла, стоит еще добавить проверку существования оригинала.
Я не помню, давал я тебе такую задачу или нет. Чтобы лучше понять идею MVC, надо бы сделать альтернативный контроллер и вью для той же задачи. А именно: надо сделать скрипт для командной строки, позволяющий загружать файлы в файлообменник с локального диска и выводящий ссылку (ссылки) на них. Пример использования:
> php cli/upload.php /tmp/file1.jpg /tmp/file2.jpg
http://example.com/view/100
http://example.com/view/101
(кстати, в некоторых линуксовых консолях ссылки распознаются и делаются кликабельными, очень удобно).
При желании можешь дополнить программу возможностью удаления файлов. Опции можно разбирать с помощью стандартной функции getopt() или поискать библиотеку, если хочется больше возможностей.
При решении этой задачи надо избежать дублирования кода и обеспечить его разделение так, чтобы Модель можно было бы использовать из любого окружения - как из веб-скриптов, так и из cli-скриптов.
Сейчас у меня ощущение, что бизнес-логика сохранения файла у тебя вписана в контроллер и повторно использовать этот код нельзя.
Для соединения с PDO стоит явно задавать кодировку как utf-8 (есть опция). И строгий режим MySQL.
https://github.com/never3ver/fileshare/blob/master/app/Helper.php#L17
> public static function createTmpName($dataGateway) {
Вот тут вот передача сервиса как аргумента выглядит странно. Обычно ведь сервисы-зависимости передают через DI. Если тебе лень делать отдельный сервис ради генерации имени, можешь (пока приложение маленькое) вставить эту функцию в TableDataGateway. Также, не очень эффективно делать 1000 запросов подряд, лучше уменьшить число попыток до 10-20 и увеличить разнообразие генерируемых имен. Также, еще эффективнее может быть проверять уникальность всех 20 имен одним запросом. Хотя, если мы ожидаем что чаще всего совпадений не будет, то выгоднее как раз проверять по одному варианту.
Вообще, мне кажется, тебе придется сделать сервис, отвечающий за работу с файлами и в него уже все эти функции класть.
https://github.com/never3ver/fileshare/blob/master/app/Helper.php#L26
> public static function getImagePath($tmpName) {
> return "../files/" . $tmpName;
плохо, что дается относительное имя, которое зависит от текущего каталога (см getcwd()/chdir()). Лучше бы генерировать полное имя. Определять полный путь к папке проще всего через __DIR__ или опции в конфиге.
Также, надо различать путь к файлу на диске и URL файла. Это разные вещи.
Также, возможно, для удобства стоило бы делать имена менее случайными, используя в них что-то человекочитаемое, чтобы при каких-то проблемах не надо было ковыряться в именах из случайных символов.
Также, желательно ограничить число файлов в одной папке. Некоторые программы начинают тормозить от большого числа (тысячи, десятки тысяч) файлов в папке.
> $url = $this->router->pathFor('error');
> $response = $response->withStatus(302)->withHeader('Location', $url);
лучше бы отображать страницу ошибки, а не редиректить на нее. Ведь тогда при нажатии F5 будет повторяться попытка загрузки файла. Редиректят обычно только после успешной обработки POST-запроса.
Тем более, ты редиректишь на страницу 404 ("не найдено"). Это нелогично, смотри сам:
- отправляем POST запрос, получаем ответ 302 (нужный контент размещен по другому адресу)
- переходим на него и получаем 404 (такой страницы не существует)
Явно неправильно используются HTTP коды.
Также, в Слиме есть объект Request из PSR-7, представляющий HTTP запрос от браузера. Изучи его возможности, есть информация в том числе на русском языке, и старайся использовать везде, чтобы освоить получше. Вот, например, там предусмотрен интерфейс для представления информации о загруженных файлах: http://www.php-fig.org/psr/psr-7/#uploaded-files
Исплоьзуй этот интерфейс вместо $_FILES.
Насчет типа файла - надо понимать, что он предоставлен браузером пользвоателя и особо доверять ему не стоит (то есть его можно использовать, но только там, где это не угрожает безопасности. Например, не стоит верить, что там картинка, если там указано image/png. Но можно выводить на странице и отдавать в Content-Type пользователям переданный тип).
https://github.com/never3ver/fileshare/blob/master/public/index.php#L81
В роуте /file/{id} нет проверки что такой файл вообще существует.
> $dataGateway = new FileDataGateway($this->db);
FileDataGateway надо положить в DI контейнер в $app.
https://github.com/never3ver/fileshare/blob/master/app/FileInfo.php#L11
> public function getDataForTemplate() {
Тут мне не очень нравится, что мы получаем из класса какой-то непонятный массив непонятного формата. Зачем массив, когда у тебя есть класс и можно сделать либо публичные свойства, либо методы-геттеры вроде isImage(), getPlaytime() и тд.
Хотя, можно сделать и метод, возвращающий массив вида ["Длительность" => "3:30"], для вывода циклом.
Преобразование в килобайты удобнее всего делать в шаблоне вызовом хелпера или функции. Не стоит помещать его в класс, так как его можно использовать и не имея файла.
> public function __construct($file) {
Нужен тайп-хинт.
Также, неэффективно анализировать файл при каждом запросе. Эффективнее в момент загрузки проанализировать и положить данные в БД. Удобно их там хранить как JSON (так как это стандарт и он не привязан к языку PHP). При правильном проектировании класса FileInfo нетрудно добавить в него методы для сериализации/восстановления объекта из JSON-данных.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
Ну что, пора бы проверить файлообменник.
> https://github.com/never3ver/fileshare/blob/master/copyBootstrap.php#L4
Вместо opendir/readdir лучше исплоьзовать scandir() (readdir выгоден если файлов очень много и не хочется их названия все сразу грузить в память, а хочется обрабатывать по одному). Либо же использовать ООП-ориентированный и немного странно спроектированный класс DirectoryIterator.
Для рекурсивного обхода папки есть еще RecursiveDirectoryIterator.
Скрипт не проверяет результат вызова copy (да и opendir) - PHP при ошибке по умолчанию просто выведет варнинг и продолжит скрипт.
> https://github.com/never3ver/fileshare/blob/master/test_fileshare.sql
> `name` varchar(45) DEFAULT NULL,
> `tmpName` varchar(45) DEFAULT NULL,
> `type` varchar(45) DEFAULT NULL,
Вот к таким колонкам стоило бы добавить COMMENT '...' с описанием, что именно там хранится, так как не очень понятно. И надеюсь, ограничение на 45 символов проверяется в коде?
https://github.com/never3ver/fileshare/blob/master/public/.htaccess#L7
> AddEncoding gzip .gz
Это конечно хорошая идея, добавить поддержку gzip, но тогда надо дописать генератор сжатых версий файлов (и тщательно протестировать это в разных браузерах).
В условия, кроме проверки наличия сжатого файла, стоит еще добавить проверку существования оригинала.
Я не помню, давал я тебе такую задачу или нет. Чтобы лучше понять идею MVC, надо бы сделать альтернативный контроллер и вью для той же задачи. А именно: надо сделать скрипт для командной строки, позволяющий загружать файлы в файлообменник с локального диска и выводящий ссылку (ссылки) на них. Пример использования:
> php cli/upload.php /tmp/file1.jpg /tmp/file2.jpg
http://example.com/view/100
http://example.com/view/101
(кстати, в некоторых линуксовых консолях ссылки распознаются и делаются кликабельными, очень удобно).
При желании можешь дополнить программу возможностью удаления файлов. Опции можно разбирать с помощью стандартной функции getopt() или поискать библиотеку, если хочется больше возможностей.
При решении этой задачи надо избежать дублирования кода и обеспечить его разделение так, чтобы Модель можно было бы использовать из любого окружения - как из веб-скриптов, так и из cli-скриптов.
Сейчас у меня ощущение, что бизнес-логика сохранения файла у тебя вписана в контроллер и повторно использовать этот код нельзя.
Для соединения с PDO стоит явно задавать кодировку как utf-8 (есть опция). И строгий режим MySQL.
https://github.com/never3ver/fileshare/blob/master/app/Helper.php#L17
> public static function createTmpName($dataGateway) {
Вот тут вот передача сервиса как аргумента выглядит странно. Обычно ведь сервисы-зависимости передают через DI. Если тебе лень делать отдельный сервис ради генерации имени, можешь (пока приложение маленькое) вставить эту функцию в TableDataGateway. Также, не очень эффективно делать 1000 запросов подряд, лучше уменьшить число попыток до 10-20 и увеличить разнообразие генерируемых имен. Также, еще эффективнее может быть проверять уникальность всех 20 имен одним запросом. Хотя, если мы ожидаем что чаще всего совпадений не будет, то выгоднее как раз проверять по одному варианту.
Вообще, мне кажется, тебе придется сделать сервис, отвечающий за работу с файлами и в него уже все эти функции класть.
https://github.com/never3ver/fileshare/blob/master/app/Helper.php#L26
> public static function getImagePath($tmpName) {
> return "../files/" . $tmpName;
плохо, что дается относительное имя, которое зависит от текущего каталога (см getcwd()/chdir()). Лучше бы генерировать полное имя. Определять полный путь к папке проще всего через __DIR__ или опции в конфиге.
Также, надо различать путь к файлу на диске и URL файла. Это разные вещи.
Также, возможно, для удобства стоило бы делать имена менее случайными, используя в них что-то человекочитаемое, чтобы при каких-то проблемах не надо было ковыряться в именах из случайных символов.
Также, желательно ограничить число файлов в одной папке. Некоторые программы начинают тормозить от большого числа (тысячи, десятки тысяч) файлов в папке.
> $url = $this->router->pathFor('error');
> $response = $response->withStatus(302)->withHeader('Location', $url);
лучше бы отображать страницу ошибки, а не редиректить на нее. Ведь тогда при нажатии F5 будет повторяться попытка загрузки файла. Редиректят обычно только после успешной обработки POST-запроса.
Тем более, ты редиректишь на страницу 404 ("не найдено"). Это нелогично, смотри сам:
- отправляем POST запрос, получаем ответ 302 (нужный контент размещен по другому адресу)
- переходим на него и получаем 404 (такой страницы не существует)
Явно неправильно используются HTTP коды.
Также, в Слиме есть объект Request из PSR-7, представляющий HTTP запрос от браузера. Изучи его возможности, есть информация в том числе на русском языке, и старайся использовать везде, чтобы освоить получше. Вот, например, там предусмотрен интерфейс для представления информации о загруженных файлах: http://www.php-fig.org/psr/psr-7/#uploaded-files
Исплоьзуй этот интерфейс вместо $_FILES.
Насчет типа файла - надо понимать, что он предоставлен браузером пользвоателя и особо доверять ему не стоит (то есть его можно использовать, но только там, где это не угрожает безопасности. Например, не стоит верить, что там картинка, если там указано image/png. Но можно выводить на странице и отдавать в Content-Type пользователям переданный тип).
https://github.com/never3ver/fileshare/blob/master/public/index.php#L81
В роуте /file/{id} нет проверки что такой файл вообще существует.
> $dataGateway = new FileDataGateway($this->db);
FileDataGateway надо положить в DI контейнер в $app.
https://github.com/never3ver/fileshare/blob/master/app/FileInfo.php#L11
> public function getDataForTemplate() {
Тут мне не очень нравится, что мы получаем из класса какой-то непонятный массив непонятного формата. Зачем массив, когда у тебя есть класс и можно сделать либо публичные свойства, либо методы-геттеры вроде isImage(), getPlaytime() и тд.
Хотя, можно сделать и метод, возвращающий массив вида ["Длительность" => "3:30"], для вывода циклом.
Преобразование в килобайты удобнее всего делать в шаблоне вызовом хелпера или функции. Не стоит помещать его в класс, так как его можно использовать и не имея файла.
> public function __construct($file) {
Нужен тайп-хинт.
Также, неэффективно анализировать файл при каждом запросе. Эффективнее в момент загрузки проанализировать и положить данные в БД. Удобно их там хранить как JSON (так как это стандарт и он не привязан к языку PHP). При правильном проектировании класса FileInfo нетрудно добавить в него методы для сериализации/восстановления объекта из JSON-данных.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
https://github.com/never3ver/fileshare/blob/master/app/FileDataGateway.php#L80
> $pdo = new PDO("mysql:host=127.0.0.1;port=9306");
Тут надо использовать DI. И наверно, сделать отдельный сервис для работы со сфинксом.
Кстати, в сфинксе неплохо бы подумать над тем, как добиться быстрых обновлений индекса. Чтобы не ждать, пока он по крону переиндексируется после загрузки файла. Обычно делают так:
Делают основной индекс + realtime-индекс для хранения данных, которые только что добавлены и пока не попали в основной индекс. Сфинкс также поддерживает воможность добавлять в realtime-индекс информацию об удалении файла, чтобы исключать его из результатов, даже если он еще есть в основном индексе.
> WHERE MATCH ('{$query}')";
Тут надо использовать плейсхолдеры.
https://github.com/never3ver/fileshare/blob/master/public/index.php#L101
Код отдачи файла надо бы вынести в отдельную функцию, а не писать стеной в контроллере. Также желательно предусмотреть поддержку отдачи файла без участия PHP, например, с помощью X-SendFile, если это расширение установлено.
https://github.com/never3ver/fileshare/blob/master/public/styles.css#L11
> .table
Этот класс используется в бутстрапе, как бы не было проблем из-за этого.
https://github.com/never3ver/fileshare/blob/master/templates/base.html.twig#L32
autoescape явно включать не требуется, оно включено по умолчанию.
Если идет поиск, то в форме поиска желательно выводить ранее введенное слово.
https://github.com/never3ver/fileshare/blob/master/templates/error.html.twig
С версткой тут беда, ну что тут за br и абсолютное позиционирование? Советую найти время и пройти наш курс задач по HTML в ОП посте. Хорошо бы, если бы все разработчики знали HTML хотя бы на уровне того курса.
> Bitrate: {{ bitrate/1024 }}kbps
Тут стоит добавлять окруление, а то может получиться число с кучей знаков после запятой
> <img class="leftmargin" height="200"
Это неправильно, ты принудительно растягиваешь маленькие картинки до 200px, они мылятся. Также, для превьюшки лучше сделать отдельную уменьшенную копию картинки, а не заставлять пользователя загружать огромную.
> <a href="/download/{{ file.getId() }}"
Ссылку лучше формировать в отдельном методе, а не копипастить по всему коду.
> $response = $response->withHeader('Content-Disposition', 'attachment; filename=' . $file->getName());
Это надежно работает только для имен из ASCII-символов (а другие в заголовках использовать нельзя). Надо делать ссылку, которая заканчивается на исходное имя файла: /download/123/file.txt и правильно экранировать специсимволы ыроде пробелов в имени. У меня был на гитхабе урок про ссылки.
А теперь перейдем к интересной части - к тестам - потому что их мало кто рискует делать, а зря.
> require_once 'vendor/autoload.php';
Нужно вместо этого сделать файл bootstrap.php, который подключит автозагрузку и подготовит все нужные объекты. Объекты вроде PDO надо хранить в контейнере (можно взять его из Слима), параметры БД - в конфиге.
https://github.com/never3ver/fileshare/blob/master/tests/app/HelperTest.php#L11
> self::$pdo = new PDO('mysql:host=localhost;dbname=test_fileshare', 'root', '');
Это должно быть в DI контейнере и конфиге.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
https://github.com/never3ver/fileshare/blob/master/app/FileDataGateway.php#L80
> $pdo = new PDO("mysql:host=127.0.0.1;port=9306");
Тут надо использовать DI. И наверно, сделать отдельный сервис для работы со сфинксом.
Кстати, в сфинксе неплохо бы подумать над тем, как добиться быстрых обновлений индекса. Чтобы не ждать, пока он по крону переиндексируется после загрузки файла. Обычно делают так:
Делают основной индекс + realtime-индекс для хранения данных, которые только что добавлены и пока не попали в основной индекс. Сфинкс также поддерживает воможность добавлять в realtime-индекс информацию об удалении файла, чтобы исключать его из результатов, даже если он еще есть в основном индексе.
> WHERE MATCH ('{$query}')";
Тут надо использовать плейсхолдеры.
https://github.com/never3ver/fileshare/blob/master/public/index.php#L101
Код отдачи файла надо бы вынести в отдельную функцию, а не писать стеной в контроллере. Также желательно предусмотреть поддержку отдачи файла без участия PHP, например, с помощью X-SendFile, если это расширение установлено.
https://github.com/never3ver/fileshare/blob/master/public/styles.css#L11
> .table
Этот класс используется в бутстрапе, как бы не было проблем из-за этого.
https://github.com/never3ver/fileshare/blob/master/templates/base.html.twig#L32
autoescape явно включать не требуется, оно включено по умолчанию.
Если идет поиск, то в форме поиска желательно выводить ранее введенное слово.
https://github.com/never3ver/fileshare/blob/master/templates/error.html.twig
С версткой тут беда, ну что тут за br и абсолютное позиционирование? Советую найти время и пройти наш курс задач по HTML в ОП посте. Хорошо бы, если бы все разработчики знали HTML хотя бы на уровне того курса.
> Bitrate: {{ bitrate/1024 }}kbps
Тут стоит добавлять окруление, а то может получиться число с кучей знаков после запятой
> <img class="leftmargin" height="200"
Это неправильно, ты принудительно растягиваешь маленькие картинки до 200px, они мылятся. Также, для превьюшки лучше сделать отдельную уменьшенную копию картинки, а не заставлять пользователя загружать огромную.
> <a href="/download/{{ file.getId() }}"
Ссылку лучше формировать в отдельном методе, а не копипастить по всему коду.
> $response = $response->withHeader('Content-Disposition', 'attachment; filename=' . $file->getName());
Это надежно работает только для имен из ASCII-символов (а другие в заголовках использовать нельзя). Надо делать ссылку, которая заканчивается на исходное имя файла: /download/123/file.txt и правильно экранировать специсимволы ыроде пробелов в имени. У меня был на гитхабе урок про ссылки.
А теперь перейдем к интересной части - к тестам - потому что их мало кто рискует делать, а зря.
> require_once 'vendor/autoload.php';
Нужно вместо этого сделать файл bootstrap.php, который подключит автозагрузку и подготовит все нужные объекты. Объекты вроде PDO надо хранить в контейнере (можно взять его из Слима), параметры БД - в конфиге.
https://github.com/never3ver/fileshare/blob/master/tests/app/HelperTest.php#L11
> self::$pdo = new PDO('mysql:host=localhost;dbname=test_fileshare', 'root', '');
Это должно быть в DI контейнере и конфиге.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
Поговорим еще про то, как именно писать тесты. Вот подводные камни, которых стоит избегать:
- надо стараться изолировать тесты от любых внешних зависимостей (вроде исплоьзования сети, базы, диска), где это возможно. Это снижает вероятность, что тесты упадут при наличии правильного кода. Ненадежные тесты - очень плохо, так как разработчику надоедает выяснять, в чем была причина падения.
- юнит-тест должен тестировать класс, а не его зависимости или внешние сервисы. Тест для FileInfo должен тестировать код в этом классе, а не проверять работу getID3.
- надо тестировать код так же, как его использует пользователь. В юнит тестах - использовать публичные инфтерфейсы класса и не закладывать в код знание каких-то внутренних особенностей работы класса. Это сделает тесты более надежными и их придется реже переписывать при изменениях в классе.
- надо понимать, что код будет меняться, и стараться не закладывать в тест подробности, которые не влияют на тестирование. Ну к примеру, не стоит закладывать в тест, что длина случайного имени файла равна 45 символам, так как это число могут поменять. А вот факт, что функция генерирует имя, и оно уникально, скорее всего не поменяется. Это и надо проверять.
- не стоит отдельно писать проверки под то, что проверяется средствами языка - например, тайп-хинтами.
Вообще, тесты обычно пишутся исходя из задания (на больших проектах часто ТЗ большое и подробное - и его дают и разработчику, и сразу же тестерам). Есть задача "сделать функцию, которая получает на вход X и выдает Y" - вот мы и пишем тесты, которые дают что-то на вход и проверяют результат. Ну и иногда еще пишут негативные сценарии, которые дают неправильные данные на вход и проверяют, что функция сообщит об ошибке. Это ведь тоже иногда требуется тестировать.
Ну и если мы обнаружили ошибку в коде, мы можем после ее закрытия написать тест, проверяющий, что она больше не встречается - это называется регрессионный тест.
В общем, вопрос "а как это протестировать" - это отдельная задача, требующая порой творческого подхода. Давай попробуем на примере твоего кода разобраться. Начнем с простого класса Helper.
Возьмем функцию createTmpName() и попробуем сформулировать, как бы мы поставили задание, если бы поручили ее написание, например, какому-нибудь стажеру:
- функция должна генерировать имя для сохранения файла
- имя должно быть уникальным
- имя не должно совпадать с ранее сохраненными в БД
Попробуем написать на основе этого тестовый сценарий. Что мы можем тут протестировать с разумными затратами сил?
Самый простой тест - smoke test - это просто вызвать функцию. Это уже проверяет, что в ходе ее работы не возникло ошибок, даже если мы не проверили результат. Это лучше, чем ничего.
Далее, попробуем проверить результат. Как проверить, что возвращенное значение - имя файла? В Линуксе имена файлов могут содержать почти любые символы, разве что кроме слеша и спецсимвола с кодом 0 (NUL). Проверить, что это строка, можно тайп-хинтом на результате функции. Мы конечно можем проверять, что строка имеет определенный формат, но это такие вещи, которые могут меняться со временем. Я бы ограничился проверкой того, что строка не пустая, а если будут какие-то баги, связанные с неверным форматом этой строки, тогда добавил бы дополнительные проверки.
Также, имя должно быть уникальным. Это требование в принципе нелегко проверить. Ведь функция может 10 раз вернуть уникальное имя, а на 11-й повториться. Проверить это нереально, потому я бы ограничился минимальной проверкой: сгенерировал бы 2 имени и проверил, что они не равны. Это пишется быстро и позволяет отловить часть ошибок.
Там еще есть требование на несовпадение с сохраненными именами, но его сложно проверить. Для этого нам надо вставить какие-то имена в базу, но у нас нет метода для этого, так как коду он не нужен. Я думаю, это требование лучше будет проверить интеграционным тестом, который загрузит несколько файлов подряд и убедится, что они не затерли друг друга.
В твоем тесте заложена проверка длины имени и набора символов. Я бы советовал ее убрать или сделать более свободной (например, проверять что нет запрещенных спецсимволов и проверять только минимальную/максимальную длину).
getImagePath()/getFilePath() можно проверить с помощью smoke test - то есть просто вызвать и проверить, что результат не пуст. Хотя, там такая простая логика, что ее можно не проверять.
convertBytesToKilobytes - аналогично или подав на вход число и проверив, что результат находится в определенном диапазоне. Или тоже не проверять.
По поводу FileInfoTest:
В данном случае протестировать класс проще всего, собрав пару образцов файлов (или создав их во временной папке перед тестом) с известными свойствами и дав их на вход классу FileInfo. Стоит предусмотреть, что представление данных может меняться со временем (раньше было 2.300Kb, стало 2Кб).
Вместо мока для File можно просто создать такой объект.
Важно написать тест так, чтобы тестировать именно FileInfo, а не getID3. То есть мы тестируем, что данные от getId3 передаются, но проверять, чему равна длительность аудиофайла, смысла особого нет.
Если ты сделаешь сериализацию/десериализацию объекта FileInfo, то можно будет написать тест, проверяющий, что свежесозданный и восстановленный из JSON объекты возвращают одинаковые данные.
> $this->assertInstanceOf('File', $fileStub);
Это ты тестируешь PhpUnit, а не свой код.
> public function testGetRowCount() {
> $this->assertEquals(2, $this->getConnection()->getRowCount('fileshare'));
Опять же, ты тут тестируешь не свой код, а правильно ли данные восстановлены из дампа.
В случае класса TDG, удобно тестировать код + БД по примерно такому принципу:
- сохранить сущность в БД
- убедиться, что она ищется по id
- убедиться, что количество сущностей увеличилось
- удалить сущность
- убедиться, что она не ищется
- проверить количество сущностей
Вот, что еще можно покрыть тестами:
- isTmpNameExisting()
- в случае добавления сфинкса - проверить, что сфинкс находит добавленные записи и не находит удаленные
- проверить код сохранения файла: что добавленный файл можно найти в БД и можно найти на диске
В тестах с использованием БД надо подумать о восстановлении/очистке тестовой базы перед каждым тестом, либо написать тесты, не зависящие от состояния БД.
Ну и еще конечно можно бы написать интерфейсным тесты, имитирущие использование сайта через браузер.
>>890416 | http://arhivach.org/thread/216627/#890416
>>899152
Поговорим еще про то, как именно писать тесты. Вот подводные камни, которых стоит избегать:
- надо стараться изолировать тесты от любых внешних зависимостей (вроде исплоьзования сети, базы, диска), где это возможно. Это снижает вероятность, что тесты упадут при наличии правильного кода. Ненадежные тесты - очень плохо, так как разработчику надоедает выяснять, в чем была причина падения.
- юнит-тест должен тестировать класс, а не его зависимости или внешние сервисы. Тест для FileInfo должен тестировать код в этом классе, а не проверять работу getID3.
- надо тестировать код так же, как его использует пользователь. В юнит тестах - использовать публичные инфтерфейсы класса и не закладывать в код знание каких-то внутренних особенностей работы класса. Это сделает тесты более надежными и их придется реже переписывать при изменениях в классе.
- надо понимать, что код будет меняться, и стараться не закладывать в тест подробности, которые не влияют на тестирование. Ну к примеру, не стоит закладывать в тест, что длина случайного имени файла равна 45 символам, так как это число могут поменять. А вот факт, что функция генерирует имя, и оно уникально, скорее всего не поменяется. Это и надо проверять.
- не стоит отдельно писать проверки под то, что проверяется средствами языка - например, тайп-хинтами.
Вообще, тесты обычно пишутся исходя из задания (на больших проектах часто ТЗ большое и подробное - и его дают и разработчику, и сразу же тестерам). Есть задача "сделать функцию, которая получает на вход X и выдает Y" - вот мы и пишем тесты, которые дают что-то на вход и проверяют результат. Ну и иногда еще пишут негативные сценарии, которые дают неправильные данные на вход и проверяют, что функция сообщит об ошибке. Это ведь тоже иногда требуется тестировать.
Ну и если мы обнаружили ошибку в коде, мы можем после ее закрытия написать тест, проверяющий, что она больше не встречается - это называется регрессионный тест.
В общем, вопрос "а как это протестировать" - это отдельная задача, требующая порой творческого подхода. Давай попробуем на примере твоего кода разобраться. Начнем с простого класса Helper.
Возьмем функцию createTmpName() и попробуем сформулировать, как бы мы поставили задание, если бы поручили ее написание, например, какому-нибудь стажеру:
- функция должна генерировать имя для сохранения файла
- имя должно быть уникальным
- имя не должно совпадать с ранее сохраненными в БД
Попробуем написать на основе этого тестовый сценарий. Что мы можем тут протестировать с разумными затратами сил?
Самый простой тест - smoke test - это просто вызвать функцию. Это уже проверяет, что в ходе ее работы не возникло ошибок, даже если мы не проверили результат. Это лучше, чем ничего.
Далее, попробуем проверить результат. Как проверить, что возвращенное значение - имя файла? В Линуксе имена файлов могут содержать почти любые символы, разве что кроме слеша и спецсимвола с кодом 0 (NUL). Проверить, что это строка, можно тайп-хинтом на результате функции. Мы конечно можем проверять, что строка имеет определенный формат, но это такие вещи, которые могут меняться со временем. Я бы ограничился проверкой того, что строка не пустая, а если будут какие-то баги, связанные с неверным форматом этой строки, тогда добавил бы дополнительные проверки.
Также, имя должно быть уникальным. Это требование в принципе нелегко проверить. Ведь функция может 10 раз вернуть уникальное имя, а на 11-й повториться. Проверить это нереально, потому я бы ограничился минимальной проверкой: сгенерировал бы 2 имени и проверил, что они не равны. Это пишется быстро и позволяет отловить часть ошибок.
Там еще есть требование на несовпадение с сохраненными именами, но его сложно проверить. Для этого нам надо вставить какие-то имена в базу, но у нас нет метода для этого, так как коду он не нужен. Я думаю, это требование лучше будет проверить интеграционным тестом, который загрузит несколько файлов подряд и убедится, что они не затерли друг друга.
В твоем тесте заложена проверка длины имени и набора символов. Я бы советовал ее убрать или сделать более свободной (например, проверять что нет запрещенных спецсимволов и проверять только минимальную/максимальную длину).
getImagePath()/getFilePath() можно проверить с помощью smoke test - то есть просто вызвать и проверить, что результат не пуст. Хотя, там такая простая логика, что ее можно не проверять.
convertBytesToKilobytes - аналогично или подав на вход число и проверив, что результат находится в определенном диапазоне. Или тоже не проверять.
По поводу FileInfoTest:
В данном случае протестировать класс проще всего, собрав пару образцов файлов (или создав их во временной папке перед тестом) с известными свойствами и дав их на вход классу FileInfo. Стоит предусмотреть, что представление данных может меняться со временем (раньше было 2.300Kb, стало 2Кб).
Вместо мока для File можно просто создать такой объект.
Важно написать тест так, чтобы тестировать именно FileInfo, а не getID3. То есть мы тестируем, что данные от getId3 передаются, но проверять, чему равна длительность аудиофайла, смысла особого нет.
Если ты сделаешь сериализацию/десериализацию объекта FileInfo, то можно будет написать тест, проверяющий, что свежесозданный и восстановленный из JSON объекты возвращают одинаковые данные.
> $this->assertInstanceOf('File', $fileStub);
Это ты тестируешь PhpUnit, а не свой код.
> public function testGetRowCount() {
> $this->assertEquals(2, $this->getConnection()->getRowCount('fileshare'));
Опять же, ты тут тестируешь не свой код, а правильно ли данные восстановлены из дампа.
В случае класса TDG, удобно тестировать код + БД по примерно такому принципу:
- сохранить сущность в БД
- убедиться, что она ищется по id
- убедиться, что количество сущностей увеличилось
- удалить сущность
- убедиться, что она не ищется
- проверить количество сущностей
Вот, что еще можно покрыть тестами:
- isTmpNameExisting()
- в случае добавления сфинкса - проверить, что сфинкс находит добавленные записи и не находит удаленные
- проверить код сохранения файла: что добавленный файл можно найти в БД и можно найти на диске
В тестах с использованием БД надо подумать о восстановлении/очистке тестовой базы перед каждым тестом, либо написать тесты, не зависящие от состояния БД.
Ну и еще конечно можно бы написать интерфейсным тесты, имитирущие использование сайта через браузер.
>>893571 | http://arhivach.org/thread/216627/#893571
>>899152
https://github.com/never3ver/students_list/blob/master/app/StudentsDataGateway.php#L87
Отладочный код оставил
https://github.com/never3ver/students_list/commit/ea01ba823b7656c3357d9d309387a5cb5505e399
Тут мне не очень нравится, что ты выставляешь public $cookie наружу. Зачем это? Если это нужно внешнему коду, лучше сделать метод getLoggedUserCookie(), а еще лучше - getLoggedUser()
Лучше стараться все, что связано с авторизацией, заключать внутри класса, и не выносить жти знания наружу. То есть внешний код в идеале не должен даже знать, как работает авторизация, а только обращаться к сервису: "проверь, залогинен ли пользователь", "зарегистрируй этого пользователя", итд.
Тут можно использовать разные подходы:
- можно сделать зоной ответственности класса только куки. То есть вставлять пользователя в БД не его задача, его задача - выставить и проверить куки.
- можно сделать зоной ответственности класса вообще регистриацию и авторизаию пользователей. То есть внешний код вызывает методы вроде "зарегистрировать пользователя", "залогинить пользователя", "получить текущего пользователя", а этот класс внутри у себя решает, что надо сделать, какие куки выставить, что в базу через TDG вставить.
> value="<?= isset($search) ? htmlspecialchars($search) : '' ?>"
isset - это проверка на null или проверка, что переменная существует? Второго быть не должно, а первое лучше делать через is_null или === null.
> Показаны результаты поиска по запросу: <?= $search ?></div>
Тут не XSS? Конечно, она и есть. Попробуй вставить в поле поиска <s>test. И перечитывай урок по XSS.
Насчет пагинации, я помню, что я тебе сначала говорил перенести ее в один класс, потом назад, в шаблон, конечно, все равно как-то сложновато пока выглядит. Можно было сделать так: возвращать из Paginator массив с описанием всех ссылок примерно такого вида:
['text' => '<<', 'page' => 1, 'isLink' => true],
['text' => '...', 'page' => null, 'isLink' => false],
['text' => 2, 'page' => 2, 'isLink' => true]
Шаблон проходит по массиву циклом и выводит пагинацию.
Так мы оставляем логику вычисления, какие номера показывать, в пагинаторе, а HTML-код будет в шаблоне.
Если тебе тоже кажется, что код сейчас запутанный, то можешь его так попробовать упростить.
По этой задаче, мы ее вроде давно уже делаем, и у тебя файлообменник еще есть. Подумай, может тебе захочется доработать авторизацию и пагинацию. Остальное все в принципе нормально работает. Если совсем времени нет, то можно конечно не доделвать, но я бы советовал его поискать.
Если будешь сдавать, напомни, что почти все готово.
>>893571 | http://arhivach.org/thread/216627/#893571
>>899152
https://github.com/never3ver/students_list/blob/master/app/StudentsDataGateway.php#L87
Отладочный код оставил
https://github.com/never3ver/students_list/commit/ea01ba823b7656c3357d9d309387a5cb5505e399
Тут мне не очень нравится, что ты выставляешь public $cookie наружу. Зачем это? Если это нужно внешнему коду, лучше сделать метод getLoggedUserCookie(), а еще лучше - getLoggedUser()
Лучше стараться все, что связано с авторизацией, заключать внутри класса, и не выносить жти знания наружу. То есть внешний код в идеале не должен даже знать, как работает авторизация, а только обращаться к сервису: "проверь, залогинен ли пользователь", "зарегистрируй этого пользователя", итд.
Тут можно использовать разные подходы:
- можно сделать зоной ответственности класса только куки. То есть вставлять пользователя в БД не его задача, его задача - выставить и проверить куки.
- можно сделать зоной ответственности класса вообще регистриацию и авторизаию пользователей. То есть внешний код вызывает методы вроде "зарегистрировать пользователя", "залогинить пользователя", "получить текущего пользователя", а этот класс внутри у себя решает, что надо сделать, какие куки выставить, что в базу через TDG вставить.
> value="<?= isset($search) ? htmlspecialchars($search) : '' ?>"
isset - это проверка на null или проверка, что переменная существует? Второго быть не должно, а первое лучше делать через is_null или === null.
> Показаны результаты поиска по запросу: <?= $search ?></div>
Тут не XSS? Конечно, она и есть. Попробуй вставить в поле поиска <s>test. И перечитывай урок по XSS.
Насчет пагинации, я помню, что я тебе сначала говорил перенести ее в один класс, потом назад, в шаблон, конечно, все равно как-то сложновато пока выглядит. Можно было сделать так: возвращать из Paginator массив с описанием всех ссылок примерно такого вида:
['text' => '<<', 'page' => 1, 'isLink' => true],
['text' => '...', 'page' => null, 'isLink' => false],
['text' => 2, 'page' => 2, 'isLink' => true]
Шаблон проходит по массиву циклом и выводит пагинацию.
Так мы оставляем логику вычисления, какие номера показывать, в пагинаторе, а HTML-код будет в шаблоне.
Если тебе тоже кажется, что код сейчас запутанный, то можешь его так попробовать упростить.
По этой задаче, мы ее вроде давно уже делаем, и у тебя файлообменник еще есть. Подумай, может тебе захочется доработать авторизацию и пагинацию. Остальное все в принципе нормально работает. Если совсем времени нет, то можно конечно не доделвать, но я бы советовал его поискать.
Если будешь сдавать, напомни, что почти все готово.
>>The Clean Architecture in PHP
>Ухты, а на русике нема?
Хорошая возможность подтянуть английский. Рекомендую. Сам так выучил.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L,QSA]
в index.php
$uri = $_SERVER["REQUEST_URI"];
echo $uri;
Виртуальный хост у меня students. Пишу в адресной строке students/asdf - получаю Object not found! Хотя должна строка /asdf быть. ЧЯДНТ?
1) Здесь тестирую форму: https://github.com/kubk/students/blob/master/tests/StudentTypeTest.php#L33
Правильно ли я понимаю, что это неправильно и лучше тестировать отдельно юзера? Тем более, что моя форма не добавляет новых правил. Как вообще понять, что тестировать, форму, Type, сущность? Где тут я тестирую свои классы, а где классы симфони?
2) Здесь у конструктора есть опциональный параметр, отвечающий за то, как помечать найденную строку: https://github.com/kubk/students/blob/master/src/StudentTwigExtension.php#L25
Это для того, чтобы тесты не падали, если будет нужно поменять теги <b></b>, на <div class='found'></found>, к примеру.
https://github.com/kubk/students/blob/master/tests/StudentTwigExtensionTest.php
Но по сути выходит, что аргумент добавлен исключительно ради тестов.
Ну и вообще в коде довольно много сомнительных мест, так что буду рад любым замечаниям.
Silex использую из-за удобных сервис-провайдеров, которые скрывают в себе все конфигурационные заморочки. Настраивать компоненты вручную не так-то просто: https://symfony.com/doc/current/components/form.html#twig-templating
Кстати в Silex класс App наследуется от контейнера: https://github.com/silexphp/Silex/blob/master/src/Silex/Application.php#L42
Как мне кажется, было бы удобней, чтобы контейнер можно было передавать в App как зависимость (так сделали в Slim). Можно сделать отдельный файл, где создаётся контейнер и инициализируются зависимости, которыми можно пользоваться, не поднимая класс приложения с его системой событий и роутингом.
Или в Silex всё не так просто и мне нужно разобраться с EventManager'ом, чтобы понять, зачем они предпочли наследование?
Ещё ты советовал использовать собственный класс на куках тем анонам, которые использовали slim'овский CSRF guard: https://github.com/slimphp/Slim-Csrf
И symfony/security-csrf тоже использует сессию, однако TokenStorage внедряется по интерфейсу: https://github.com/symfony/security-csrf/blob/master/CsrfTokenManager.php#L43
Поэтому можно попробовать сделать CookieTokenStorage и добавить его в контейнер как csrf.token_storage: https://github.com/silexphp/Silex/blob/master/src/Silex/Provider/CsrfServiceProvider.php
1) Здесь тестирую форму: https://github.com/kubk/students/blob/master/tests/StudentTypeTest.php#L33
Правильно ли я понимаю, что это неправильно и лучше тестировать отдельно юзера? Тем более, что моя форма не добавляет новых правил. Как вообще понять, что тестировать, форму, Type, сущность? Где тут я тестирую свои классы, а где классы симфони?
2) Здесь у конструктора есть опциональный параметр, отвечающий за то, как помечать найденную строку: https://github.com/kubk/students/blob/master/src/StudentTwigExtension.php#L25
Это для того, чтобы тесты не падали, если будет нужно поменять теги <b></b>, на <div class='found'></found>, к примеру.
https://github.com/kubk/students/blob/master/tests/StudentTwigExtensionTest.php
Но по сути выходит, что аргумент добавлен исключительно ради тестов.
Ну и вообще в коде довольно много сомнительных мест, так что буду рад любым замечаниям.
Silex использую из-за удобных сервис-провайдеров, которые скрывают в себе все конфигурационные заморочки. Настраивать компоненты вручную не так-то просто: https://symfony.com/doc/current/components/form.html#twig-templating
Кстати в Silex класс App наследуется от контейнера: https://github.com/silexphp/Silex/blob/master/src/Silex/Application.php#L42
Как мне кажется, было бы удобней, чтобы контейнер можно было передавать в App как зависимость (так сделали в Slim). Можно сделать отдельный файл, где создаётся контейнер и инициализируются зависимости, которыми можно пользоваться, не поднимая класс приложения с его системой событий и роутингом.
Или в Silex всё не так просто и мне нужно разобраться с EventManager'ом, чтобы понять, зачем они предпочли наследование?
Ещё ты советовал использовать собственный класс на куках тем анонам, которые использовали slim'овский CSRF guard: https://github.com/slimphp/Slim-Csrf
И symfony/security-csrf тоже использует сессию, однако TokenStorage внедряется по интерфейсу: https://github.com/symfony/security-csrf/blob/master/CsrfTokenManager.php#L43
Поэтому можно попробовать сделать CookieTokenStorage и добавить его в контейнер как csrf.token_storage: https://github.com/silexphp/Silex/blob/master/src/Silex/Provider/CsrfServiceProvider.php
В реализации PSR7 от слима есть вспомогательный метод, благодаря которому можно не указывать вручную заголовок location и HTTP метод для редиректа: https://github.com/slimphp/Slim/blob/3.x/Slim/Http/Response.php#L282
То есть вместо:
$response->withStatus(302)->withHeader('Location', $url);
Можно писать:
$response->withRedirect($url)
>>908365
>На jsfiddle никак не закинуть папки?
По несколько файлов можно добавлять на http://plnkr.co
http://plnkr.co/edit/GZMmUwVeTJ35MS6yQMal?p=preview
>>909800
Извиняюсь за тавтологию в начале поста.
Есть небольшой опыт кодинга на питоне и жс, но теперь прийдется дрочить и php так как опенкарт на нем. Начал я устанавливать LAMP и охуел напарртачил с апачем и phpmyadmin теперь они выкидывают ошибки при гуглении которых он меня нахуй посылает. Пасаны а нельзя для всей этой параши установить изолироное окружение? я теперь этот ебучий апач и mysql переустановить не могу .
>>909618
>>909619
>>909623
Спасибо, подумаю что к чему. Честно говоря, я захэллоуворлдил yii2 и сейчас читаю документацию, целясь на тестхаб, так что тесты наверно отложу до него, тем более они присутствуют в задаче, а вот остальное постараюсь доделать. Хотя после знакомства с большим фреймворком меня не покидает ощущение, что это всё никому не нужные велосипеды. Все твои ответы я себе сохраняю, так что простыня про тесты обязательно пойдет в дело. Еще раз спасибо.
Попробуй веб хуки. https://developer.github.com/webhooks/
Когда заливаешь комит на гитхаб, тот делает запрос на указанный тобой адрес, а там скриптик что-то проверяет и делает пулл мастер ветки
Как-то для сайтика приятеля такое писал: http://pastebin.com/FDvJrrD0
<?php
$uploadfile = "photos/".$_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile);
?>
____
<?php
if(isset($_POST['submit'])) {
$photo = $_POST['photo'];
$stmt = $pdo->query("INSERT INTO news VALUES('','$photo')");
}
?>
_____
<form method="post" action="editor.php" enctype ="multipart/form-data">
Фотография<br>
<input type="file" name="image"><br>
<input type="submit" name="submit" value="Загрузить файл"><br>
<input type="text" name="photo" style="width:400px;" required>
</form>
Второй инпут заносит имя файла в таблицу
p.s Сам я в php плохо шарю, поэтому прошу помощи умных людей
Алсо, как долго хранятся скрипты на идеоне?
Что делать?
67 http://arhivach.org/thread/139825/
треды 60-80 http://www.mediafire.com/file/urj17w5s4w00jqc/threads-60-80.zip
треды 69-70 http://www.mediafire.com/file/s12ktalf8r8rl48/pr-threads-69-70.zip
треды 69-74 http://www.mediafire.com/file/rcs9c460k6ywj41/pr-threads-69-74.zip
Тебе нужны с 66 по 72.
> как долго хранятся скрипты на идеоне?
Не знаю. Если они тебе нужны, советую скачать себе на диск, а также забекапить куда-нибудь. Например на gist.github.com.
Так я хотел её хотя бы вывести на экран чтобы убедиться, что всё работает. Все ж запросы должны в индекс посылаться, а значит я должен увидеть строку "/asdf". Или я чего-то не понял?
Попробуй проверить, работает ли .htaccess вообще. Впиши в него ерунду вроде zzzzz и посмотри, выпадет ошибка 503 + запись в логаха апача или нет.
совсем не апачееб, но хуй знает что ты делаешь не так, может у тебя мод_реврайт не включен, может хтаксес не туда засунул, может надо /index.php, слеш добавить в начало тоись. Всякая хуйня бывает, десу.
При этом всё что в моем старом быдлокоде было бы ассоциативным массивом - должно по сути в ООП быть объектом?
Вместо массивов юзай итераторы и splobjectstorage, вон раздел по ним:
http://php.net/manual/ru/book.spl.php
Ой блядь без фанатизма с этим дерьмом, когда в тех местах, где хватает форичем по массиву пройтись, начинают ебашить ИТЕРАТОРЫ по хардкору, у меня https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition перед глазами встает.
Итераторы юзать нормальная практика. Зато твои классы сразу будут получать готовые объекты, т.е. можно прописать интерфейс.
>>910239
>>910241
Так в задачу про Вектор захерачить департаменты и сотрудников в этот класс вместо массива или нет? Так то там форичем офк их обойти и всё.
Не только. Массивы - это универсальная структура, которая заменяет такие структуры, как вектор, стек, очередь, хеш-таблича, множество, из других языков. И потому у массива может быть много применений.
Но, если у тебя есть какая-то сущность, то ее лучше представить в виде объекта. Это делают не из слепой веры в ООП, а потому что это дает преимущества:
- в отличие от массива, у объекта известен список полей
- эти поля можно документировать комментариями
- у объекта могут быть методы
- у объекта можно реализовать инкапсуляцию (сокрытие) данных и логики внутри объекта
- в тайп-хинтах можно использовать имя класса
Соответственно, правильно написанный ООП-код легче читать и поддерживать, чем код, где передаются массивы непонятных форматов.
Итераторы нужны там, где мы хотим перебирать что-то, что не можем или не хотим поместить в массив. Например, список файлов на диске (их может быть много и на загрузку их списка в массив уйдет много времени), или последовательности чисел, генерируемые на ходу.
SplObjectStorage нужен, если ты хочешь сделать "множество" или "хеш-таблицу", где ключом будут объекты. Ну то есть ты хочешь сделать "список"объектов или сопоставить объекту какие-то данные.
>>910239
А ведь получился неплохой учебный пример проекта, можно посмотреть, как используется gradle, maven, spring (они там используют DI container), тесты.
Хотя мне не нравится, как у них сделаны тесты, как-то переусложнено. Можно было просто массив с ожидаемыми значениями сделать.
Это по моему написано в уроке, но я напишу еще раз. При решении задачи на ООП, надо решить:
- какие у нас будут сущности (для которых мы делаем классы). Ну тут просто - явно сущностями будут Сотрудник, Департамент, Компания. Больше в задаче вроде ничего нет.
- какие у них будут свойства? Например, у Сотрудника есть как минимум такое свойство, как ранг. Тут важно выбрать свойства правильно и отличать исходные свойства от вычисляемых из них значений. Ну например, если у нас есть сущность Студент и список его оценок, то мы не будем делать отдельное свойство среднийБалл, так как его всегда можно посчитать исходя из списка оценок.
- что можно делать с сущностями, то есть какие у них есть методы? Ну тут как минимум можно добавлять в Депаратмент нового Сотрудника, а также узнавать разную информацию: сколько Сотрудник выпил кофе, сколько произвел документов. Тут еще важно понять, к какому классу надо отнести тот или иной метод.
- как сущности связаны? Ну например, в Департаменте работают Сотрудники, и эту связь надо отразить в коде (например, сделать массив работающих в Департаменте сотрудников).
Также, советую почитать про инкапсуляцию:
- http://learn.javascript.ru/internal-external-interface (тут на примере JS, но суть, надеюсь, понятна)
Кроме классов-сущностей, бывают еще классы-сервисы. Они не представляют никакую сущность, и у них почти нет свойств, они просто содержат полезные методы. Обычно их называют "сервис чего-нибудь", "менеджер чего-нибудь", "хелпер чего-нибудь", "калькулятор чего-нибудь". В задаче таким классом может быть АнтикризисныйКомитет, который берет Компанию и применяет к ней разные меры.
Вот как-то так. Если по-прежнему непонятно, задавай дополнительные вопросы.
Ты бы хоть написал, какие ошибки. В Оп посте также есть небольшая статья про установку Апача и PHP, может тебе поможет.
И что значит "не могу переустановить"? Удалить через "панель управления - установленные программы" наверно можно ведь?
>>909858
Лучше помучаться.
>>909870
Это нужные велосипеды. Если бы ты не попробовал сделать приложение с нуля, ты бы сейчас плохо понимал, что делает фреймворк внутри. Ну и еще оно помогает изучить вещи вроде автозагрузки, работы с БД, MVC, вывод таблиц, работу с формами. Не так и мало!
>>910243
Да, массив хорошо подходит для хранения списка сотрудников.
>>910247
Класс с 2 методами (add/get) вполне допустим, почему нет? Наверняка в будущем в него еще добавят.
Методы, которые ты упомянул, нет особого смысла выносить в другой класс, так как они работают только со свойствами Департамента и логичнее всего их в нем и держать.
>>910307
Говнокодил всю ночь, проверяйте пожалуйста.
http://ideone.com/yH35aJ
В правильности решения не сомневаюсь даже, интересует целесообразно ли я классы описал и применил всякие ООП - фишечки
Алсо задача не сомтря на простоту очень выматывающая, потому что надо тупо много кода писать :(
Методы завтра напишу думаю.
Не работает. Раньше из коробки работал. В чём может быть проблема? Насчет способа «Вписать в конфигурационный файл апача» вроде знаю.
Поконкретнее
Проверь его имя, правильно ли написано, есть ли точка.
Проверь конфиг Апача, разрешен ли htaccess в нужном VirtualHost? По моему настройка называется AllowOverride или как-то так.
Глянь лог ошибок Апача на всякий случай.
О, вот у тебя интересные вопросы, давай разберемся.
Для начала почитай вот это вот, это к другой задаче, но тоже про тестирование: >>909619
> Тем более, что моя форма не добавляет новых правил. Как вообще понять, что тестировать, форму, Type, сущность? Где тут я тестирую свои классы, а где классы симфони?
У тебя там по сути используется класс форм Симфони, ты к нему добавляешь только конфигурацию полей и правила проверок. И тестировать код Симфони смысла нет (у них есть свои тесты), тут можно разве что тестировать твою конфигурацию.
В принципе, от такого тестирования может быть польза, например, это позволит отловить опечатку при правке конфигурации формы.
Но ради этих тестов нам надо многое усложнять. Например, ты вынужден делать мок TDG, и при этом ты в тест закладываешь знание о принципе работы формы (ты знаешь, что она обращается именно к методу findByToken/findByEmail и делаешь для них мок). Это не очень хорошо, так как в будущем при рефакторинге это могут поменять и тест придется переделывать. Ведь техническое задание на форму вряд ли говорит "форма должна вызывать метод fidByEmail", оно говорит "форма должна сравнить введенный email с имеющимися в базе".
Более того, ты там подменяешь сервис в DI контейнере. То есть закладываешь в тест знание того, к какому именно сервису обращается форма. Лучше бы не подменять данные в контейнере, а создать самому нужный сервис и передать в него мок вместо TDG.
Соответственно для того, чтобы убрать это знание о внутреннем устройстве формы, нам надо отказаться от мока и использовать вместо этого тестовую базу с заранее известными значениями токенов и email, которые мы сможем заложить в тест.
Но с базой тоже не все так просто, во-первых, ее структура будет меняться и надо соответственно обновлять тестовые данные (хотя возможно, это сделают миграции), во-вторых, надо перед каждым тестом очищать и заполнять базу, в-третьих со временем этих данных будет много, в четвертых, тест и данные для него (пользователь в тестовом дампе БД) находятся далеко друг от друга. Наверно, тут удобнее поступить по-другому:
- перед тестом вставить одного или нескольких пользователей с известным email/токенов (можно, делать это, открыв и не коммитя транзакцию)
- после теста удалить (или откатить транзакцию)
Тестовые пользователи понадобятся нам и в других тестах, так что метод регистрации пользователя пригодится еще не раз.
Тут конечно есть свой подвох: может произойти ошибка при вставке пользователя из-за неуникальности email или токена (если из-за какой-то ошибки старый тестовый пользователь останется в базе). Для борьбы с ним можно например сделать увеличивающийся счетчик, и использовать его при генерации email или токена. Ну или восстанавливать базу к известному состоянию перед тестом.
То есть мы пытаемся точнее имитировать поведение пользователей, которые регистрируются. Но тогда, опять же, возможно, лучше подняться на уровень выше, и тестировать форму через тестирование пользовательского интерфейса (то есть заполнять форму в эмуляторе браузера). Так мы протестируем больший объем кода. У меня ощущение, что тут лучше бы подошел браузерный тест, так как кода и логики в самой форме почти нет.
Далее, посмотрим на сервис авторизации и его тест https://github.com/kubk/students/blob/master/tests/AuthServiceTest.php
Как я понимаю, сам сервис авторизации у тебя довольно хорошо скрывает подробности внутренней реализации. Он принимает на вход HTTP запрос или модифицирует HTTP ответ. Тут явно можно использовать loopback-тест (loopback - это когда выход устройства, например, звуковой или сетевой карты, подключают ко входу и получают возможность протестировать как приемник, так и передатчик сигнала одновременно).
Loopback-тест можно попробовать реализовать так:
- создать студента
- вызвать метод "проверить регистрацию" с пустым запросом и убедиться что он показывает не-залогиненность пользователя
- вызвать метод "залогинить студента" и сохранить ответ с куками
- вызвать метод "проверить регистрацию", и подать на вход ранее созданный ответ, убедиться что мы получаем правильного студента
- вызвать метод "разлогинить студента", и сохранить ответ
- подать этот ответ на вход методу "проверить регистрацию"
Единтсвенная сложность тут - нужно научиться превращать HTTP-ответ в HTTP-запрос, то есть перенести куки с учетом возможности их удаления. Немного можно было бы упростить себе жизнь, если бы сервис авторизации работал не с Request/Response, а с коллекциями кук.
Также, нам понадобится возможность записи в БД, так как сервис при регистрации пишет в нее.
Твой тест, увы, опять полагается на внутреннюю реализацию. Ну вот посмотри:
> $this->authService->getTokenKey();
> assertResponseContainCookieWithKeyValue
А что, если завтра мы начнем использовать 2 куки? Что, если вместо токена мы начнем использовать email + цифровая подпись? Хеш от пароля? Тест придет в негодность.
Увы, ты тестируешь не публичный интерфейс, а детали внутренней реализации.
Насчет PaginatorTest - там мало логики, нелегко понять, что тестировать. Я бы просто тестировал по-минимуму, вроде "вызвать функцию getPageCount() и убедиться что она вернула число больше нуля". Твой тест тоже годится в принципе и позволит обнаружить ошибки в расчете числа страниц.
Насчет markSearch. Давай подумаем, какие ошибки в принципе могут быть в этой функции? Скорее всего, там будет что-то такое:
- часть данных теряется
- искомое слово не выделяется
Соответственно, попробуем, опять же, не закладывать знание о том, каким тегом выделяется искомое слово, а, например, напишем тест, ищущий любой тег (или просто угловые скобки) справа/слева от искомного слова. Или, если мы хотим еще сильнее абстрагироваться от деталей реализации (а вдруг слово выделяется не тегом?) - можно просто проверять, что при наличии искомого слова функция возвращает другой результат.
Ну и дополнительно можно проверить, что функция не теряет слова при выделении.
И еще, что спецсимволы экранируются.
Насчет pathToRoute - мне кажется, там незачем писать мок, можно просто ограничиться вызовом функции и при желании, проверить, что результат похож на URL. Там же логики в ней - одна строчка, стоит ли ради нее мок писать?
> Здесь у конструктора есть опциональный параметр, отвечающий за то, как помечать найденную строку:
> Но по сути выходит, что аргумент добавлен исключительно ради тестов.
Это плохо, лучше переделать тест. Иногда приходится добавлять в код какие-то тестовые опции (я например использовал опции для отключения кеширования), но лучше этого избегать. Тут надо взвешивать плюсы/минусы.
> Настраивать компоненты вручную не так-то просто
Есть такое, у некоторых библиотек сложная инициализация, но там все же ООП, тайп-хинты и DI, так что иногда можно и так догадаться. Согласен, что там сложно, там еще и рефлекшен используют, чтобы имя файла с классом определить.
> Как мне кажется, было бы удобней, чтобы контейнер можно было передавать в App как зависимость (так сделали в Slim). Можно сделать отдельный файл, где создаётся контейнер и инициализируются зависимости, которыми можно пользоваться, не поднимая класс приложения с его системой событий и роутингом.
Ну это микрофреймворк и в таком случае можно отдельно сделать заполнение контейнера, а отдельно - простановку роутов. Хотя в принципе, если run() не вызвать, то роуты не сильно мешают. Даже наоборот, они могут понадобиться, если ты захочешь генерировать URL, это же часть конфигурации.
То есть я думаю, тебе все же нужен $app целиком с роутами даже для CLI скриптов.
> Или в Silex всё не так просто и мне нужно разобраться с EventManager'ом, чтобы понять, зачем они предпочли наследование?
Думаю, ради упрощения, микрофреймворки обычно и выглядят как один объект.
> Ещё ты советовал использовать собственный класс на куках тем анонам, которые использовали slim'овский CSRF guard: https://github.com/slimphp/Slim-Csrf
> И symfony/security-csrf тоже использует сессию, однако TokenStorage внедряется по интерфейсу
Согласен, можно. А может такой класс уже даже есть? Просто мне кажется, сессию заводить ради одного значения - перебор.
Код самого приложения я не проверял, только тесты.
Вообще, ты правильно делаешь, что изучаешь и Симфони, и тестирование. Это поможет, если тебе придется работать с большими и сложными приложениями.
О, вот у тебя интересные вопросы, давай разберемся.
Для начала почитай вот это вот, это к другой задаче, но тоже про тестирование: >>909619
> Тем более, что моя форма не добавляет новых правил. Как вообще понять, что тестировать, форму, Type, сущность? Где тут я тестирую свои классы, а где классы симфони?
У тебя там по сути используется класс форм Симфони, ты к нему добавляешь только конфигурацию полей и правила проверок. И тестировать код Симфони смысла нет (у них есть свои тесты), тут можно разве что тестировать твою конфигурацию.
В принципе, от такого тестирования может быть польза, например, это позволит отловить опечатку при правке конфигурации формы.
Но ради этих тестов нам надо многое усложнять. Например, ты вынужден делать мок TDG, и при этом ты в тест закладываешь знание о принципе работы формы (ты знаешь, что она обращается именно к методу findByToken/findByEmail и делаешь для них мок). Это не очень хорошо, так как в будущем при рефакторинге это могут поменять и тест придется переделывать. Ведь техническое задание на форму вряд ли говорит "форма должна вызывать метод fidByEmail", оно говорит "форма должна сравнить введенный email с имеющимися в базе".
Более того, ты там подменяешь сервис в DI контейнере. То есть закладываешь в тест знание того, к какому именно сервису обращается форма. Лучше бы не подменять данные в контейнере, а создать самому нужный сервис и передать в него мок вместо TDG.
Соответственно для того, чтобы убрать это знание о внутреннем устройстве формы, нам надо отказаться от мока и использовать вместо этого тестовую базу с заранее известными значениями токенов и email, которые мы сможем заложить в тест.
Но с базой тоже не все так просто, во-первых, ее структура будет меняться и надо соответственно обновлять тестовые данные (хотя возможно, это сделают миграции), во-вторых, надо перед каждым тестом очищать и заполнять базу, в-третьих со временем этих данных будет много, в четвертых, тест и данные для него (пользователь в тестовом дампе БД) находятся далеко друг от друга. Наверно, тут удобнее поступить по-другому:
- перед тестом вставить одного или нескольких пользователей с известным email/токенов (можно, делать это, открыв и не коммитя транзакцию)
- после теста удалить (или откатить транзакцию)
Тестовые пользователи понадобятся нам и в других тестах, так что метод регистрации пользователя пригодится еще не раз.
Тут конечно есть свой подвох: может произойти ошибка при вставке пользователя из-за неуникальности email или токена (если из-за какой-то ошибки старый тестовый пользователь останется в базе). Для борьбы с ним можно например сделать увеличивающийся счетчик, и использовать его при генерации email или токена. Ну или восстанавливать базу к известному состоянию перед тестом.
То есть мы пытаемся точнее имитировать поведение пользователей, которые регистрируются. Но тогда, опять же, возможно, лучше подняться на уровень выше, и тестировать форму через тестирование пользовательского интерфейса (то есть заполнять форму в эмуляторе браузера). Так мы протестируем больший объем кода. У меня ощущение, что тут лучше бы подошел браузерный тест, так как кода и логики в самой форме почти нет.
Далее, посмотрим на сервис авторизации и его тест https://github.com/kubk/students/blob/master/tests/AuthServiceTest.php
Как я понимаю, сам сервис авторизации у тебя довольно хорошо скрывает подробности внутренней реализации. Он принимает на вход HTTP запрос или модифицирует HTTP ответ. Тут явно можно использовать loopback-тест (loopback - это когда выход устройства, например, звуковой или сетевой карты, подключают ко входу и получают возможность протестировать как приемник, так и передатчик сигнала одновременно).
Loopback-тест можно попробовать реализовать так:
- создать студента
- вызвать метод "проверить регистрацию" с пустым запросом и убедиться что он показывает не-залогиненность пользователя
- вызвать метод "залогинить студента" и сохранить ответ с куками
- вызвать метод "проверить регистрацию", и подать на вход ранее созданный ответ, убедиться что мы получаем правильного студента
- вызвать метод "разлогинить студента", и сохранить ответ
- подать этот ответ на вход методу "проверить регистрацию"
Единтсвенная сложность тут - нужно научиться превращать HTTP-ответ в HTTP-запрос, то есть перенести куки с учетом возможности их удаления. Немного можно было бы упростить себе жизнь, если бы сервис авторизации работал не с Request/Response, а с коллекциями кук.
Также, нам понадобится возможность записи в БД, так как сервис при регистрации пишет в нее.
Твой тест, увы, опять полагается на внутреннюю реализацию. Ну вот посмотри:
> $this->authService->getTokenKey();
> assertResponseContainCookieWithKeyValue
А что, если завтра мы начнем использовать 2 куки? Что, если вместо токена мы начнем использовать email + цифровая подпись? Хеш от пароля? Тест придет в негодность.
Увы, ты тестируешь не публичный интерфейс, а детали внутренней реализации.
Насчет PaginatorTest - там мало логики, нелегко понять, что тестировать. Я бы просто тестировал по-минимуму, вроде "вызвать функцию getPageCount() и убедиться что она вернула число больше нуля". Твой тест тоже годится в принципе и позволит обнаружить ошибки в расчете числа страниц.
Насчет markSearch. Давай подумаем, какие ошибки в принципе могут быть в этой функции? Скорее всего, там будет что-то такое:
- часть данных теряется
- искомое слово не выделяется
Соответственно, попробуем, опять же, не закладывать знание о том, каким тегом выделяется искомое слово, а, например, напишем тест, ищущий любой тег (или просто угловые скобки) справа/слева от искомного слова. Или, если мы хотим еще сильнее абстрагироваться от деталей реализации (а вдруг слово выделяется не тегом?) - можно просто проверять, что при наличии искомого слова функция возвращает другой результат.
Ну и дополнительно можно проверить, что функция не теряет слова при выделении.
И еще, что спецсимволы экранируются.
Насчет pathToRoute - мне кажется, там незачем писать мок, можно просто ограничиться вызовом функции и при желании, проверить, что результат похож на URL. Там же логики в ней - одна строчка, стоит ли ради нее мок писать?
> Здесь у конструктора есть опциональный параметр, отвечающий за то, как помечать найденную строку:
> Но по сути выходит, что аргумент добавлен исключительно ради тестов.
Это плохо, лучше переделать тест. Иногда приходится добавлять в код какие-то тестовые опции (я например использовал опции для отключения кеширования), но лучше этого избегать. Тут надо взвешивать плюсы/минусы.
> Настраивать компоненты вручную не так-то просто
Есть такое, у некоторых библиотек сложная инициализация, но там все же ООП, тайп-хинты и DI, так что иногда можно и так догадаться. Согласен, что там сложно, там еще и рефлекшен используют, чтобы имя файла с классом определить.
> Как мне кажется, было бы удобней, чтобы контейнер можно было передавать в App как зависимость (так сделали в Slim). Можно сделать отдельный файл, где создаётся контейнер и инициализируются зависимости, которыми можно пользоваться, не поднимая класс приложения с его системой событий и роутингом.
Ну это микрофреймворк и в таком случае можно отдельно сделать заполнение контейнера, а отдельно - простановку роутов. Хотя в принципе, если run() не вызвать, то роуты не сильно мешают. Даже наоборот, они могут понадобиться, если ты захочешь генерировать URL, это же часть конфигурации.
То есть я думаю, тебе все же нужен $app целиком с роутами даже для CLI скриптов.
> Или в Silex всё не так просто и мне нужно разобраться с EventManager'ом, чтобы понять, зачем они предпочли наследование?
Думаю, ради упрощения, микрофреймворки обычно и выглядят как один объект.
> Ещё ты советовал использовать собственный класс на куках тем анонам, которые использовали slim'овский CSRF guard: https://github.com/slimphp/Slim-Csrf
> И symfony/security-csrf тоже использует сессию, однако TokenStorage внедряется по интерфейсу
Согласен, можно. А может такой класс уже даже есть? Просто мне кажется, сессию заводить ради одного значения - перебор.
Код самого приложения я не проверял, только тесты.
Вообще, ты правильно делаешь, что изучаешь и Симфони, и тестирование. Это поможет, если тебе придется работать с большими и сложными приложениями.
Еще советы:
> https://github.com/kubk/students/blob/master/public/index.php#L12
> if ($code === 404) {
Я бы проверял еще что $e instanceof класса, отвечающего за HTTP ошибки, так как код 404 теоретически может быть и в не HTTP исключении. (например, код ошибки в PDOException).
Ну и ты там при 404 ошибке отдаешь страницу с кодом 503. Надо проверять, что исключение содержит HTTP код ошибки и возвращать его наверно.
И интересный способ с генерацией женских фамилий:
> $surname = $faker->lastName() . 'a';
Я полез в исходники фейкера и обнаружил там такой же подход: https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/ru_RU/Person.php
тут https://github.com/kubk/students/blob/master/src/LinkGenerator.php#L54 HTML в коде, и ссылка не экранируется как положено.
Лучше было бы использовать для HTML кода макрос твига, а генерировать в PHP только сам URL.
Здесь https://github.com/kubk/students/blob/master/src/UniqueEmailValidator.php реализация немного сложная (мы вылезаем наверх чтобы получить объект студента). Не лучше ли вешать валидатор емайла не на поле, а на сам объект студента?
Вот ответы из гугла:
- http://stackoverflow.com/questions/8170301/symfony2-form-validation-based-on-two-fields/8170731#8170731 (применение валидатора к объекту, а не полю)
- http://stackoverflow.com/questions/15621225/pass-custom-parameters-to-custom-validationconstraint-in-symfony2/15641972#15641972 (передача аргументов в валидатор)
Еще советы:
> https://github.com/kubk/students/blob/master/public/index.php#L12
> if ($code === 404) {
Я бы проверял еще что $e instanceof класса, отвечающего за HTTP ошибки, так как код 404 теоретически может быть и в не HTTP исключении. (например, код ошибки в PDOException).
Ну и ты там при 404 ошибке отдаешь страницу с кодом 503. Надо проверять, что исключение содержит HTTP код ошибки и возвращать его наверно.
И интересный способ с генерацией женских фамилий:
> $surname = $faker->lastName() . 'a';
Я полез в исходники фейкера и обнаружил там такой же подход: https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/ru_RU/Person.php
тут https://github.com/kubk/students/blob/master/src/LinkGenerator.php#L54 HTML в коде, и ссылка не экранируется как положено.
Лучше было бы использовать для HTML кода макрос твига, а генерировать в PHP только сам URL.
Здесь https://github.com/kubk/students/blob/master/src/UniqueEmailValidator.php реализация немного сложная (мы вылезаем наверх чтобы получить объект студента). Не лучше ли вешать валидатор емайла не на поле, а на сам объект студента?
Вот ответы из гугла:
- http://stackoverflow.com/questions/8170301/symfony2-form-validation-based-on-two-fields/8170731#8170731 (применение валидатора к объекту, а не полю)
- http://stackoverflow.com/questions/15621225/pass-custom-parameters-to-custom-validationconstraint-in-symfony2/15641972#15641972 (передача аргументов в валидатор)
https://jsfiddle.net/aq5r451d/
Пока что без усложнений, сейчас ими займусь.
xampp ставь, там все настроено уже. Если отдельный надо, то посмотри просто в xampp конфиг файл, скачай отдельно апач и настрой похожим образом. Отдельные настройки легко гуглятся.
По поводу теста формы тоже прихожу к выводу, что раз мы пытаемся "точнее имитировать поведение пользователей, которые регистрируются", то тест формы логичнее сделать более высокоуровневым (функциональным), в противном случае выходит, что тестируются валидатор и форма Cимфони.
Но тогда вопрос по функциональным тестам: в случае с валидацией формы нужно ли проверять, какие именно поля формы не проходят валидацию? Или достаточно проверить, что не происходит редирект на главную? В первом случае нужно использовать парсер html и подумать о том, как написать селектор для вычленения количества ошибок формы без привязки к вёрстке.
> Также, нам понадобится возможность записи в БД, так как сервис при регистрации пишет в нее.
Совсем забыл об этом в тестах класса AuthService, а ведь он использует TDG. Странно, что PHPUnit мне не выдал ошибку из-за того, что использую у мока метод save, который никак не описал во время создания этого мока. Видимо, это из-за того, возвращаемое этим методом значение никак не проверяется (вдобавок метод save ничего и не возвращает: https://github.com/kubk/students/blob/master/src/StudentGateway.php#L60 ). Наверное лучше, чтобы методы, вставляющие данные в БД, что-то возвращали? Например lastInsertId. А в классе, который использует TDG, проверять, что это значение = число, отличное от нуля (или бросать исключение в самом методе save и ничего не возвращать?). Не могу понять, что лучше здесь.
И по поводу БД, выходит, что AuthService должен использовать не мок TDG, а сам класс TDG, которому будет передаваться PDO-соединение c новой базой (которую будем постоянно чистить и перезаполнять)? А в тестах регистрации у AuthService нужно проверять, что в БД появилось новое значение?
В этом >>909619 посте ты писал, что надо стараться изолировать тесты от любых внешних зависимостей вроде базы, если это возможно.
> А что, если завтра мы начнем использовать 2 куки? Что, если вместо токена мы начнем использовать email + цифровая подпись? Хеш от пароля? Тест придет в негодность.
Угу. Только вот если мы будем использовать хеш от пароля, к примеру, то это уже будет совсем другой способ регистрации/логина. Например для регистрации будем пользоваться функцией password_hash, для логина будем брать из request'а email, находить пользователя с таким email в БД, проверять через password_verify его пароль. Вырисовывается отдельная абстракция. Думаю, отдельная абстракция должна быть и для цифровой подписи, с которой я не знаком. Ещё необходимо добавить абстракцию над теми данными пользователя, по которым мы его идентифицируем (в примерах использования symfony/security видел слово credentials), раз токенов может быть больше одного. Поначалу писал что-то подобное, но когда получил фабрику абстрактных интерфейсов, то вспомнил, что требуется по заданию и завернул в один класс, забив на гибкость. Выходит, что в моём случае AuthService - слишком громкое название и его как минимум нужно переименовать в CookieAuthService.
Вообще да, в тесте AuthService у меня 2 или 3 приватных метода, что уже говорит о том, что я тестирую не интерфейс, а пытаюсь лезть в кишки. Подумаю над улучшением, пока в голову ничего не приходит.
> Немного можно было бы упростить себе жизнь, если бы сервис авторизации работал не с Request/Response, а с коллекциями кук.
Но тогда контроллеру нужно быть умнее и передавать в методы сервиса не просто реквест, а куки, ну и потом эти куки класть в response. Хотя, работать с HTTP это прямая обязанность контроллера.
>> Свой CookieTokenStorage вместо дефолтного SessionTokenStorage
> А может такой класс уже даже есть?
Готовой реализации я не нашел, вот тут https://github.com/symfony/symfony/issues/13464 юзеры с меткой contributor вроде пришли к выводу, что куки подходят для хранения токена, но issue по-прежнему висит. Ревьюверов то код стайл не устраивает, то избыточные аннотации @see
>> https://github.com/kubk/students/blob/master/src/StudentTwigExtension.php#L25
>> https://github.com/kubk/students/blob/master/tests/StudentTwigExtensionTest.php#L21
> Насчет pathToRoute - мне кажется, там незачем писать мок, можно просто ограничиться вызовом функции и при желании, проверить, что результат похож на URL. Там же логики в ней - одна строчка, стоит ли ради нее мок писать?
Он там передаётся в конструктор, а вот его метод действительно можно не мокать. Кажется, начинаю немного лучше понимать, как писать тесты.
> Я полез в исходники фейкера и обнаружил там такой же подход:
К сожалению, об этом почему-то не написано в доках, только в коде. Оттуда об этом и узнал.
Суть лупбек теста понял, target у UniqueEmailValidator'а изменил. За БД возьмусь, когда придумаю, что делать с AuthService. Может есть на примете какие готовые библиотеки для авторизации, которые можно глянуть и поучиться?
Спасибо за ответ.
По поводу теста формы тоже прихожу к выводу, что раз мы пытаемся "точнее имитировать поведение пользователей, которые регистрируются", то тест формы логичнее сделать более высокоуровневым (функциональным), в противном случае выходит, что тестируются валидатор и форма Cимфони.
Но тогда вопрос по функциональным тестам: в случае с валидацией формы нужно ли проверять, какие именно поля формы не проходят валидацию? Или достаточно проверить, что не происходит редирект на главную? В первом случае нужно использовать парсер html и подумать о том, как написать селектор для вычленения количества ошибок формы без привязки к вёрстке.
> Также, нам понадобится возможность записи в БД, так как сервис при регистрации пишет в нее.
Совсем забыл об этом в тестах класса AuthService, а ведь он использует TDG. Странно, что PHPUnit мне не выдал ошибку из-за того, что использую у мока метод save, который никак не описал во время создания этого мока. Видимо, это из-за того, возвращаемое этим методом значение никак не проверяется (вдобавок метод save ничего и не возвращает: https://github.com/kubk/students/blob/master/src/StudentGateway.php#L60 ). Наверное лучше, чтобы методы, вставляющие данные в БД, что-то возвращали? Например lastInsertId. А в классе, который использует TDG, проверять, что это значение = число, отличное от нуля (или бросать исключение в самом методе save и ничего не возвращать?). Не могу понять, что лучше здесь.
И по поводу БД, выходит, что AuthService должен использовать не мок TDG, а сам класс TDG, которому будет передаваться PDO-соединение c новой базой (которую будем постоянно чистить и перезаполнять)? А в тестах регистрации у AuthService нужно проверять, что в БД появилось новое значение?
В этом >>909619 посте ты писал, что надо стараться изолировать тесты от любых внешних зависимостей вроде базы, если это возможно.
> А что, если завтра мы начнем использовать 2 куки? Что, если вместо токена мы начнем использовать email + цифровая подпись? Хеш от пароля? Тест придет в негодность.
Угу. Только вот если мы будем использовать хеш от пароля, к примеру, то это уже будет совсем другой способ регистрации/логина. Например для регистрации будем пользоваться функцией password_hash, для логина будем брать из request'а email, находить пользователя с таким email в БД, проверять через password_verify его пароль. Вырисовывается отдельная абстракция. Думаю, отдельная абстракция должна быть и для цифровой подписи, с которой я не знаком. Ещё необходимо добавить абстракцию над теми данными пользователя, по которым мы его идентифицируем (в примерах использования symfony/security видел слово credentials), раз токенов может быть больше одного. Поначалу писал что-то подобное, но когда получил фабрику абстрактных интерфейсов, то вспомнил, что требуется по заданию и завернул в один класс, забив на гибкость. Выходит, что в моём случае AuthService - слишком громкое название и его как минимум нужно переименовать в CookieAuthService.
Вообще да, в тесте AuthService у меня 2 или 3 приватных метода, что уже говорит о том, что я тестирую не интерфейс, а пытаюсь лезть в кишки. Подумаю над улучшением, пока в голову ничего не приходит.
> Немного можно было бы упростить себе жизнь, если бы сервис авторизации работал не с Request/Response, а с коллекциями кук.
Но тогда контроллеру нужно быть умнее и передавать в методы сервиса не просто реквест, а куки, ну и потом эти куки класть в response. Хотя, работать с HTTP это прямая обязанность контроллера.
>> Свой CookieTokenStorage вместо дефолтного SessionTokenStorage
> А может такой класс уже даже есть?
Готовой реализации я не нашел, вот тут https://github.com/symfony/symfony/issues/13464 юзеры с меткой contributor вроде пришли к выводу, что куки подходят для хранения токена, но issue по-прежнему висит. Ревьюверов то код стайл не устраивает, то избыточные аннотации @see
>> https://github.com/kubk/students/blob/master/src/StudentTwigExtension.php#L25
>> https://github.com/kubk/students/blob/master/tests/StudentTwigExtensionTest.php#L21
> Насчет pathToRoute - мне кажется, там незачем писать мок, можно просто ограничиться вызовом функции и при желании, проверить, что результат похож на URL. Там же логики в ней - одна строчка, стоит ли ради нее мок писать?
Он там передаётся в конструктор, а вот его метод действительно можно не мокать. Кажется, начинаю немного лучше понимать, как писать тесты.
> Я полез в исходники фейкера и обнаружил там такой же подход:
К сожалению, об этом почему-то не написано в доках, только в коде. Оттуда об этом и узнал.
Суть лупбек теста понял, target у UniqueEmailValidator'а изменил. За БД возьмусь, когда придумаю, что делать с AuthService. Может есть на примете какие готовые библиотеки для авторизации, которые можно глянуть и поучиться?
Спасибо за ответ.
Какая ось? Если шиндошс, то иди нахуй сразу, а так на любой норм дистр мануалов немеряно.
Ну это вообще пушка.
http://codepad.org/HkDAOOY6
http://codepad.org/IiJ7Joy5
http://codepad.org/arDs0UqS
http://codepad.org/lu46RLAn
http://codepad.org/hm58Nzoc
Вроде норм все, есть только стилистические огрехи.
> if($height>$anonHeight){
Делай отступы между символами вроде знаков равенства и т.п. - if($height > $anonHeight){
Непонятно почему в задачке про генератор стихов ты додумался до конструкции типа
$word1 = ($massiveWord1[array_rand($massiveWord1)]);
А в задачке про генератор имен ты делаешь отдельную переменную $random
>echo "------<br>";
Тут наверное удобней будет использовать тег <hr>, он просто выводит горизонтальную строку как блочный элемент.
Спасибо за ответ. Не совсем понял
>А в задачке про генератор имен ты делаешь отдельную переменную $random
если не трудно, можешь написать как это должно выглядеть? Я старался придумать самый короткий вариант ну типа чтоб строк как можно меньше было. Ничего другого не смог придумать.
Думаю, лучше использовать готовый вариант. Ведь пагинатор может не показывать все страницы сразу (вроде "1 2 3 ... последняя"), а только часть, и показывать другие номера при переходе. Реализовать самому это конечно можно, но потребует написания дополнительного кода.
> В итоге блок с ссылками на страницы я каждый раз при смене страницы подгружаю заново с сервера.
Я надеюсь, что тут имеется в виду, что этот блок приходит вместе с самим контентом, а не получается отдельным запросом - делать 2 запроса было бы неэффективно.
Также, советую проверить, что реализация загрузки с аяксом учитывает описанные в этой статье требования: https://github.com/codedokode/pasta/blob/master/js/ajax.md
>>911461
>>911485
Стихи http://codepad.org/HkDAOOY6
Верно
Кубики http://codepad.org/IiJ7Joy5
Логика немного неверная - если выпало 2 дабла, то мы уже не смотрим, кто победил. А в остальном верно.
Шифр http://codepad.org/arDs0UqS
Все правильно.
Рост http://codepad.org/lu46RLAn
Верно.
Имя http://codepad.org/hm58Nzoc
Все правильно.
То, что другой анон сказал насчет оформелния - тоже верно, привыкай оформлять код правильно, смотри второй пост треда.
В генераторе стихов можно упростить код, если сделать массив с шаблоном стиха вида [$word1, [' '], $word2, [' '], $word3...] и обходить его циклом foreach.
>>911201
А это для тех, кто не хочет изучать ООП, MVC, фреймворки, тестирование, решать задачи про вектор, студентов, файлообменник и тестхаб, а хочет учиться по видеокурсам "PHP за 24 часа без занудной теории".
Думаю, лучше использовать готовый вариант. Ведь пагинатор может не показывать все страницы сразу (вроде "1 2 3 ... последняя"), а только часть, и показывать другие номера при переходе. Реализовать самому это конечно можно, но потребует написания дополнительного кода.
> В итоге блок с ссылками на страницы я каждый раз при смене страницы подгружаю заново с сервера.
Я надеюсь, что тут имеется в виду, что этот блок приходит вместе с самим контентом, а не получается отдельным запросом - делать 2 запроса было бы неэффективно.
Также, советую проверить, что реализация загрузки с аяксом учитывает описанные в этой статье требования: https://github.com/codedokode/pasta/blob/master/js/ajax.md
>>911461
>>911485
Стихи http://codepad.org/HkDAOOY6
Верно
Кубики http://codepad.org/IiJ7Joy5
Логика немного неверная - если выпало 2 дабла, то мы уже не смотрим, кто победил. А в остальном верно.
Шифр http://codepad.org/arDs0UqS
Все правильно.
Рост http://codepad.org/lu46RLAn
Верно.
Имя http://codepad.org/hm58Nzoc
Все правильно.
То, что другой анон сказал насчет оформелния - тоже верно, привыкай оформлять код правильно, смотри второй пост треда.
В генераторе стихов можно упростить код, если сделать массив с шаблоном стиха вида [$word1, [' '], $word2, [' '], $word3...] и обходить его циклом foreach.
>>911201
А это для тех, кто не хочет изучать ООП, MVC, фреймворки, тестирование, решать задачи про вектор, студентов, файлообменник и тестхаб, а хочет учиться по видеокурсам "PHP за 24 часа без занудной теории".
В ОП посте есть ссылка, найдешь сам?
Если что-то не получается, подробно напиши, что ты сделал и что ожидал, и что вышло вместо этого. И что в логе ошибок Апача написано.
Морду не делают, так как она не нужна, это же сервер, а не игра по метанию свиней в стены. Если ты освоишь командную строку и конфиги, ты увидишь, что зачастую этот подход удобнее и требует меньше времени. Ну например, конфиг легко скопировать, сгенерировать, искать слова в нем, а попробуй то же самое сделать с графическим интерфейсом.
Ну и никто не запрещает тебе самому написать конфигуратор Апача. Могу предложить сделать приложение на HTML/JS и превратить его в обычное приложение с помощью Electron. Уверен, на любом собеседовании твой конфигуратор произведет хорошее впечатление.
>>910960
Когда я писал "имитировать поведение пользователя", я имел в виду не только поведение человека с браузером, но и код который например вызывает методы какого-то класса. Я просто хотел сказать, что надо не опираться за знание внутренних особенностей кода, а использовать только предоставленные интерфейсы.
Что касается формы, тестирование через браузер позволяет протестировать все слои кода насквозь, так как данные формы могут как-то дополнительно обрабатываться и преобразовываться на пути от браузера к БД.
> в случае с валидацией формы нужно ли проверять, какие именно поля формы не проходят валидацию?
Стоит, если это не требует больших затрат. Так как это делает тест более точным.
> В первом случае нужно использовать парсер html и подумать о том, как написать селектор для вычленения количества ошибок формы без привязки к вёрстке.
Без привяки к верстке, к сожалению, обойтись не выйдет. Лучше всего привязываться к каким-то атрибутам, которые не поменяются. Но это редко возможно. По факту при смене верстки, скорее всего, придется переделывать тесты (это недостаток браузерных тестов).
Обычно эмуляторы браузера позволяют искать элементы по разным критериям: id, текст, CSS/XPATH селекторы.
Чуть-чуть можно облегчить жизнь, вынеся селекторы элементов из кода тестов отдельно (в классы PageObjects), чтобы их было проще менять.
У меня была еще такая идея: сделать специальные классы (например, test-form-error) или атрибуты, которые относятся только к тестам и которые мы не трогаем при смене верстки. Можно пробовать привязываться к текстам на элементах, но они тоже могут меняться.
Тут (англ) советуют привязываться к id: https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/
Тут (англ) используют атрибуты для пометки нужных элементов: http://techblog.constantcontact.com/software-development/a-better-way-to-id-elements-in-selenium-tests/
> Странно, что PHPUnit мне не выдал ошибку из-за того, что использую у мока метод save, который никак не описал во время создания этого мока
Видимо неперехваченный метод работает, как обычно.
> Наверное лучше, чтобы методы, вставляющие данные в БД, что-то возвращали?
Это не имеет значения.
> И по поводу БД, выходит, что AuthService должен использовать не мок TDG, а сам класс TDG, которому будет передаваться PDO-соединение c новой базой (которую будем постоянно чистить и перезаполнять)?
Получается что да, если мы не хотим полагаться на знание, какие именно методы вызываются.
> А в тестах регистрации у AuthService нужно проверять, что в БД появилось новое значение?
Можно, но лучше бы попробовать через тот же сервис найти ранее зарегистрированного пользователя, а не лезть в базу в обход него.
> надо стараться изолировать тесты от любых внешних зависимостей вроде базы, если это возможно
В данном случае с базой получается проще.
> Только вот если мы будем использовать хеш от пароля, к примеру, то это уже будет совсем другой способ регистрации/логина. Например для регистрации будем пользоваться функцией password_hash, для логина будем брать из request'а email
Не, ты путаешь. Я имел в виду, мы можем поменять формат кук. Вместо одной куки с токеном использовать, например, email + хеш пароля, которые мы берем из объекта Student. Или id + специальная подпись. Тогда внешний интерфейс AuthService не меняется, но внутренняя логика становится другой.
Тут лучше всего генерировать авторизационные куки с помощью сервиса, и потом ему же их и скармливать. Так мы избегаем внедрения в тест знаний о его внутреннем устройстве.
> Вырисовывается отдельная абстракция. Думаю, отдельная абстракция должна быть и для цифровой подписи, с которой я не знаком.
Такую абстракцию уже предоставляет AuthService.
> Ещё необходимо добавить абстракцию над теми данными пользователя, по которым мы его идентифицируем (в примерах использования symfony/security видел слово credentials)
В Симфони абстракция нужна, так как они не знают, как будет выглядеть объект пользователя и как будет реализована авторизация.
> Выходит, что в моём случае AuthService - слишком громкое название и его как минимум нужно переименовать в CookieAuthService.
Можно. Тогда логично будет и передавать ему сразу куки вместо всего Request.
> Но тогда контроллеру нужно быть умнее и передавать в методы сервиса не просто реквест, а куки, ну и потом эти куки класть в response.
Добавь тайп-хинт, чтобы нельзя было перепутать, и пусть так и делает. В случае с куками ответа, можно просто получить коллекцию кук из Response и передать этот объект.
> Может есть на примете какие готовые библиотеки для авторизации, которые можно глянуть и поучиться?
Не знаю. Есть библиотека Симфони, но она очень-очень переусложненная, я к ней даже подходить лишний раз не хочу. Фаерволлы какие-то, куча абстракций.
Можно поискать в packagist.org по словам auth, authentication и тд.
Есть еще такое, для авторизации через соцсети:
- http://hybridauth.sourceforge.net/
- http://opauth.org/
- https://github.com/Vinelab/social-auth (кстати, с тестами)
Так что можешь попробовать спроектировать свою. Хочешь усложнить жизнь - добавь расширяемость и подключение авторизации через соцсети.
А, еще насчет тестов. Настройки тестов (вроде бутстрап-файла) стоит поместить в phpunit.xml.
В ОП посте есть ссылка, найдешь сам?
Если что-то не получается, подробно напиши, что ты сделал и что ожидал, и что вышло вместо этого. И что в логе ошибок Апача написано.
Морду не делают, так как она не нужна, это же сервер, а не игра по метанию свиней в стены. Если ты освоишь командную строку и конфиги, ты увидишь, что зачастую этот подход удобнее и требует меньше времени. Ну например, конфиг легко скопировать, сгенерировать, искать слова в нем, а попробуй то же самое сделать с графическим интерфейсом.
Ну и никто не запрещает тебе самому написать конфигуратор Апача. Могу предложить сделать приложение на HTML/JS и превратить его в обычное приложение с помощью Electron. Уверен, на любом собеседовании твой конфигуратор произведет хорошее впечатление.
>>910960
Когда я писал "имитировать поведение пользователя", я имел в виду не только поведение человека с браузером, но и код который например вызывает методы какого-то класса. Я просто хотел сказать, что надо не опираться за знание внутренних особенностей кода, а использовать только предоставленные интерфейсы.
Что касается формы, тестирование через браузер позволяет протестировать все слои кода насквозь, так как данные формы могут как-то дополнительно обрабатываться и преобразовываться на пути от браузера к БД.
> в случае с валидацией формы нужно ли проверять, какие именно поля формы не проходят валидацию?
Стоит, если это не требует больших затрат. Так как это делает тест более точным.
> В первом случае нужно использовать парсер html и подумать о том, как написать селектор для вычленения количества ошибок формы без привязки к вёрстке.
Без привяки к верстке, к сожалению, обойтись не выйдет. Лучше всего привязываться к каким-то атрибутам, которые не поменяются. Но это редко возможно. По факту при смене верстки, скорее всего, придется переделывать тесты (это недостаток браузерных тестов).
Обычно эмуляторы браузера позволяют искать элементы по разным критериям: id, текст, CSS/XPATH селекторы.
Чуть-чуть можно облегчить жизнь, вынеся селекторы элементов из кода тестов отдельно (в классы PageObjects), чтобы их было проще менять.
У меня была еще такая идея: сделать специальные классы (например, test-form-error) или атрибуты, которые относятся только к тестам и которые мы не трогаем при смене верстки. Можно пробовать привязываться к текстам на элементах, но они тоже могут меняться.
Тут (англ) советуют привязываться к id: https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/
Тут (англ) используют атрибуты для пометки нужных элементов: http://techblog.constantcontact.com/software-development/a-better-way-to-id-elements-in-selenium-tests/
> Странно, что PHPUnit мне не выдал ошибку из-за того, что использую у мока метод save, который никак не описал во время создания этого мока
Видимо неперехваченный метод работает, как обычно.
> Наверное лучше, чтобы методы, вставляющие данные в БД, что-то возвращали?
Это не имеет значения.
> И по поводу БД, выходит, что AuthService должен использовать не мок TDG, а сам класс TDG, которому будет передаваться PDO-соединение c новой базой (которую будем постоянно чистить и перезаполнять)?
Получается что да, если мы не хотим полагаться на знание, какие именно методы вызываются.
> А в тестах регистрации у AuthService нужно проверять, что в БД появилось новое значение?
Можно, но лучше бы попробовать через тот же сервис найти ранее зарегистрированного пользователя, а не лезть в базу в обход него.
> надо стараться изолировать тесты от любых внешних зависимостей вроде базы, если это возможно
В данном случае с базой получается проще.
> Только вот если мы будем использовать хеш от пароля, к примеру, то это уже будет совсем другой способ регистрации/логина. Например для регистрации будем пользоваться функцией password_hash, для логина будем брать из request'а email
Не, ты путаешь. Я имел в виду, мы можем поменять формат кук. Вместо одной куки с токеном использовать, например, email + хеш пароля, которые мы берем из объекта Student. Или id + специальная подпись. Тогда внешний интерфейс AuthService не меняется, но внутренняя логика становится другой.
Тут лучше всего генерировать авторизационные куки с помощью сервиса, и потом ему же их и скармливать. Так мы избегаем внедрения в тест знаний о его внутреннем устройстве.
> Вырисовывается отдельная абстракция. Думаю, отдельная абстракция должна быть и для цифровой подписи, с которой я не знаком.
Такую абстракцию уже предоставляет AuthService.
> Ещё необходимо добавить абстракцию над теми данными пользователя, по которым мы его идентифицируем (в примерах использования symfony/security видел слово credentials)
В Симфони абстракция нужна, так как они не знают, как будет выглядеть объект пользователя и как будет реализована авторизация.
> Выходит, что в моём случае AuthService - слишком громкое название и его как минимум нужно переименовать в CookieAuthService.
Можно. Тогда логично будет и передавать ему сразу куки вместо всего Request.
> Но тогда контроллеру нужно быть умнее и передавать в методы сервиса не просто реквест, а куки, ну и потом эти куки класть в response.
Добавь тайп-хинт, чтобы нельзя было перепутать, и пусть так и делает. В случае с куками ответа, можно просто получить коллекцию кук из Response и передать этот объект.
> Может есть на примете какие готовые библиотеки для авторизации, которые можно глянуть и поучиться?
Не знаю. Есть библиотека Симфони, но она очень-очень переусложненная, я к ней даже подходить лишний раз не хочу. Фаерволлы какие-то, куча абстракций.
Можно поискать в packagist.org по словам auth, authentication и тд.
Есть еще такое, для авторизации через соцсети:
- http://hybridauth.sourceforge.net/
- http://opauth.org/
- https://github.com/Vinelab/social-auth (кстати, с тестами)
Так что можешь попробовать спроектировать свою. Хочешь усложнить жизнь - добавь расширяемость и подключение авторизации через соцсети.
А, еще насчет тестов. Настройки тестов (вроде бутстрап-файла) стоит поместить в phpunit.xml.
> HTMLElement.prototype.find
Не советую совать свой код в стандартные прототипы - так как это может конфликтовать c другими библиотеками (или существующими функциями, или добавленными в будущем), да и неаккуратно как-то.
Да и нелогично передавать context, если ты вызываешь find на конкретном элементе.
Поиск по id не учитывает context. Не позволяет искать в отсоединенных (не вставленных в DOM) элементах.
> if (!(result instanceof HTMLElement)) {
Это логичнее делать при задании функций, так как там мы знаем, результат какого типа вернет функция. То есть сделать как-то так:
func: pipe(context.getElementsByClassName, collectionToArray)
pipe() - надо написать самому (получается функциональное программирование).
> var elems = context.getElementsByTagName("*");
Это может быть не очень эффективно, если элементов много. Выгоднее может быть обходить ДОМ рекурсивно.
> if (elems.className == selector) {
Тоже неправильно. Надо проверять наличие класса, а не полное совпадение.
>>910330
Для вписывания кода в Reporter стоит применить Heredoc или Nowdoc, чтобы код был читабельным и можно было ставить кавычки. А еще лучше, конечно, сделать отдельный PHP-шаблон: http://www.phpinfo.su/articles/practice/shablony_v_php.html (а если еще заморочиться, можно сделать перекодировщик HTML в plain text для ideone, вырезающий теги).
У Organisation, возможно, не нужно поле name (хотя и вреда от него нет).
> public function getOrgTotals() {
> $info = new stdClass();
Вот тут мне не нравится использование объекта как массива. Может, лучше было сделать массив? А еще можно просто сделать в организации методы для суммирования по департаментам.
Чуть-чуть попробовать упростить код в однотипных методах можно так:
return $this->sumDepartmentsByMethod('getSomething');
или применив array_reduce к массиву департаментов (заодно голову поломаешь). Увы, в PHP-синтаксисе код получается не очень компактный, но в языках со стрелочными функциями получается как-то так:
return this.departments.reduce((prev, dep) => prev + dep.getSomething(), 0);
В PHP была пара предложений сделать такой короткий синтаксис:
- https://wiki.php.net/rfc/short_closures
- https://wiki.php.net/rfc/arrow_functions
> if (is_object($employee) and get_parent_class($employee) == 'Employee') {
А если нет, то прячем ошибку и ничего не говорим? Это плохо, лучше выбросить исключение: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
> class Names {
Название неправильное, правильно NameGenerator. Ведь твой класс - это не хранилище имен.
В классе Employee есть небольшая проблема, что тот, кто его наследует, должен не забыть в конструкторе задать 3 значения зарплаты, и тд. Но где гарантия, что он не забудет? Решить проблему можно, создав в базовом классе абстрактные функции вроде getBaseSalary() и тд., которые наследники будут обязаны определить.
> как более элегантно забить работягами я просто не знаю:
Сделать функцию-парсер выражения вроде ['1ме2', '5ме3', ...].
>>910119
Чтобы понять паттерны, надо изучать код компонентов Симфони или подобный сложный код, и там уже находить паттерны, и тогда читать про них. Тогда, я думаю, будет понятнее. Ну и надо не слепо везде использовать паттерны, а оценивать, выгодно это или нет.
Ну возьмем паттерн Фабрика. Вот просто так он выглядит как бессмысленное усложнение, зачем создавать объект Фабрикой, когда есть $x = new Class? Молодец, если ты так думаешь вместо зубрежки, то ты уже не безнадежен. Но не все так просто. Представь,что объект создается не в твоем коде, а где-то в сторонней библиотеке, которую ты править не можешь. Как тогда заставить ее создавать объекты нужного нам класса? Вот тут бы и пригодилась бы Фабрика, если бы автор того кода сделал возможность передать в его класс нашу Фабрику.
>>910023
наставь echo и var-dump, чтобы видеть что в переменных, что возвращают функции. И найти, где происходит ошибка.
В случае с PDO, надо включить ERRMODE_EXCEPTION, чтобы видеть ошибки работы с базой.
Ну и вообще тебе бы стоило подучить PDO прежде чем его использовтаь.
> HTMLElement.prototype.find
Не советую совать свой код в стандартные прототипы - так как это может конфликтовать c другими библиотеками (или существующими функциями, или добавленными в будущем), да и неаккуратно как-то.
Да и нелогично передавать context, если ты вызываешь find на конкретном элементе.
Поиск по id не учитывает context. Не позволяет искать в отсоединенных (не вставленных в DOM) элементах.
> if (!(result instanceof HTMLElement)) {
Это логичнее делать при задании функций, так как там мы знаем, результат какого типа вернет функция. То есть сделать как-то так:
func: pipe(context.getElementsByClassName, collectionToArray)
pipe() - надо написать самому (получается функциональное программирование).
> var elems = context.getElementsByTagName("*");
Это может быть не очень эффективно, если элементов много. Выгоднее может быть обходить ДОМ рекурсивно.
> if (elems.className == selector) {
Тоже неправильно. Надо проверять наличие класса, а не полное совпадение.
>>910330
Для вписывания кода в Reporter стоит применить Heredoc или Nowdoc, чтобы код был читабельным и можно было ставить кавычки. А еще лучше, конечно, сделать отдельный PHP-шаблон: http://www.phpinfo.su/articles/practice/shablony_v_php.html (а если еще заморочиться, можно сделать перекодировщик HTML в plain text для ideone, вырезающий теги).
У Organisation, возможно, не нужно поле name (хотя и вреда от него нет).
> public function getOrgTotals() {
> $info = new stdClass();
Вот тут мне не нравится использование объекта как массива. Может, лучше было сделать массив? А еще можно просто сделать в организации методы для суммирования по департаментам.
Чуть-чуть попробовать упростить код в однотипных методах можно так:
return $this->sumDepartmentsByMethod('getSomething');
или применив array_reduce к массиву департаментов (заодно голову поломаешь). Увы, в PHP-синтаксисе код получается не очень компактный, но в языках со стрелочными функциями получается как-то так:
return this.departments.reduce((prev, dep) => prev + dep.getSomething(), 0);
В PHP была пара предложений сделать такой короткий синтаксис:
- https://wiki.php.net/rfc/short_closures
- https://wiki.php.net/rfc/arrow_functions
> if (is_object($employee) and get_parent_class($employee) == 'Employee') {
А если нет, то прячем ошибку и ничего не говорим? Это плохо, лучше выбросить исключение: https://github.com/codedokode/pasta/blob/master/php/exceptions.md
> class Names {
Название неправильное, правильно NameGenerator. Ведь твой класс - это не хранилище имен.
В классе Employee есть небольшая проблема, что тот, кто его наследует, должен не забыть в конструкторе задать 3 значения зарплаты, и тд. Но где гарантия, что он не забудет? Решить проблему можно, создав в базовом классе абстрактные функции вроде getBaseSalary() и тд., которые наследники будут обязаны определить.
> как более элегантно забить работягами я просто не знаю:
Сделать функцию-парсер выражения вроде ['1ме2', '5ме3', ...].
>>910119
Чтобы понять паттерны, надо изучать код компонентов Симфони или подобный сложный код, и там уже находить паттерны, и тогда читать про них. Тогда, я думаю, будет понятнее. Ну и надо не слепо везде использовать паттерны, а оценивать, выгодно это или нет.
Ну возьмем паттерн Фабрика. Вот просто так он выглядит как бессмысленное усложнение, зачем создавать объект Фабрикой, когда есть $x = new Class? Молодец, если ты так думаешь вместо зубрежки, то ты уже не безнадежен. Но не все так просто. Представь,что объект создается не в твоем коде, а где-то в сторонней библиотеке, которую ты править не можешь. Как тогда заставить ее создавать объекты нужного нам класса? Вот тут бы и пригодилась бы Фабрика, если бы автор того кода сделал возможность передать в его класс нашу Фабрику.
>>910023
наставь echo и var-dump, чтобы видеть что в переменных, что возвращают функции. И найти, где происходит ошибка.
В случае с PDO, надо включить ERRMODE_EXCEPTION, чтобы видеть ошибки работы с базой.
Ну и вообще тебе бы стоило подучить PDO прежде чем его использовтаь.
<?php
error_reporting(-1);
$anonHeight = 169; / Рост анона /
/ Рост одноклассников анона /
$classmates = array(
'Антон'=>172,
'Семен'=>165,
'Лена'=>189,
'Иван'=>171,
'Петр'=>182,
'Сидор'=>176,
'Аня' =>180,
'Таня'=>179,
'Маня'=>171
);
$number = 0; / Сколько человек в классе выше анона /
/ Перебираем всех одноклассников /
foreach ($classmates as $name => $height) {
echo "Имя: {$name}, рост: {$height} см.\n";
if ($height < $anonheight){
$number++;
echo "В классе {$number}";
}
/ Тут надо добавить проверку, выше или ниже этот человек, чем анон,
и подсчитать число тех, кто выше /
}
echo "В классе {$number} человек выше анона\n";
Сравнение с ростом анона почему-то не работает, тех, кто ниже не видно, выше оказываются все.
<?php
error_reporting(-1);
$anonHeight = 169; / Рост анона /
/ Рост одноклассников анона /
$classmates = array(
'Антон'=>172,
'Семен'=>165,
'Лена'=>189,
'Иван'=>171,
'Петр'=>182,
'Сидор'=>176,
'Аня' =>180,
'Таня'=>179,
'Маня'=>171
);
$number = 0; / Сколько человек в классе выше анона /
/ Перебираем всех одноклассников /
foreach ($classmates as $name => $height) {
echo "Имя: {$name}, рост: {$height} см.\n";
if ($height < $anonheight){
$number++;
echo "В классе {$number}";
}
/ Тут надо добавить проверку, выше или ниже этот человек, чем анон,
и подсчитать число тех, кто выше /
}
echo "В классе {$number} человек выше анона\n";
Сравнение с ростом анона почему-то не работает, тех, кто ниже не видно, выше оказываются все.
Вообще, когда делал пагинатор на джс в одном из проектов, писал с тем условием, что в джс с бэкенда приходит всего один параметр: номер последней страницы. А там уж скрипт сам проставляет многоточия в нужных местах, disabled на стрелочки и тд.
НО, т.к. это был пагинатор для модулей в админке, требование "работать без джс" не было. Сейчас же для себя я решил обязательно это требование включить.
>Я надеюсь, что тут имеется в виду, что этот блок приходит вместе с самим контентом
Конечно.
>Также, советую проверить, что реализация загрузки с аяксом учитывает описанные в этой статье требования:
Спасибо. Будет интересно для практики сделать статус-бар загрузки контента не НА сервер, а С сервера.
А ещё интересно было бы узнать, как по уму реализовывать хранение конфига системы в базе данных (как у всех популярных cms). Интуитивно я бы сделал примерно так: отдельная модель UserConfig с отдельной таблицей в БД, сохраняющая при каждом изменении данные в кэш. При просрочке кэша, соответственно, туда их снова заносит из БД. Всё это дело выполняет отдельный миддлвэр для всех запросов к серверу. В том направлении мыслю?
Спасибо за проверку и советы. Решил последнюю задачу из раздела "строки". Программа работает верно, на разные палиндромы, но реализация так себе. Можно как то улучшить?
http://ideone.com/rbYtIA
> HTMLElement.prototype.find
У тебя условие задачи начинается со слов "Напиши функцию dom.find(selector, context)", я решил что это будет метод dom элемента.
Код лучше выкладывать на ideone, так ты облегчишь жизнь проверяющим.
Вот твой код: http://ideone.com/728DSl
Читай ошибку: "Undefined variable: anonheight"
Переменные $anonHeight и $anonheight - разные, регистр имеет значение. Ну и ты скорее всего всего знак в if'е перепутал, там сравнение if (ростТекущего < ростАнона), то есть ты ищешь тех, кто ниже анона.
Не, dom там это вроде неймспейса, чтоюы не конфликтовать с другими функциями и библиотеками:
var dom = {};
dom.find = ....
> А что теперь делать, если нужно получить запросом данные из нескольких таблиц?
Смотрим, какая из них основная, и помещаем в нужный класс.
>>908446
>>912082
> Ну вдобавок почему-то не все элементы отсюда https://unicode-table.com/ru/#miscellaneous-symbols хотели отображаться, взял которые работали, думаю не смертельно.
Это зависит от установленных в твоей системе шрифтов.
> @import "css/style.css" screen;
А почему не просто <link href> ?
> src="../src/field.js?f
Не надо дописывать странные символы, лучше научиться очищать кеш и пользоваться соответствующей опцией в инструментах разработчика.
Код конечно получился доволно запутанным, из-за того, что вместе объединены игровая логика, вывод информации, и обработка событий. Такой большой код надо как-то разделять на части. У тебя много методов, и не очень понятно, как они и в каком порядке вызываются.
Ну например, если посмотреть на класс Field, то трудно понять, как им вообще пользоваться, как правильно инициализировать. Если бы вся инициализация была в конструкторе, было бы проще (хотя конечно DOM элементы может быть удобнее создавать отдельным методом, чтобы мы могли создать объект, и только потом создать таблицу). Еще есть такой вариант: сделать класс FieldBuilder, который при вызове метода createField() вернет объект класса Field, который управляет полем. Таким образом, управлять полем нельзя, пока оно не создано (а в твоем коде мы можем вызывать публичные методы вроде getCells() когда поле еще не создано). В общем, тут есть разные варианты.
Или, например, можно было бы попробовать разбить код хотя бы на 2 класса: один отвечает за работу с DOM, другой - за игровую логику.
Для хранения информации об открытых клеточках, есть 2 подхода, можно хранить данные об открытых клетках. рсположении мин, и тд, в JS-переменных (массивах и словарях), а можно хранить их в DOM, в виде классов и атрибутов. У тебя вроде бы используется второй.
> Field.prototype.getField = function() {
> return this._gameContainer.querySelector("#field");
Вот тут как минимум 2 проблемы:
- поле _gameContainer не инициализируется в конструкторе, значит мы должны как-то догадаться, когда уже можно вызывать этот метод, а когда еще нельзя
- вместо того, чтобы искать элемент каждый раз, может быть выгоднее просто хранить ссылку на него
> Field.prototype.getFace = function() {
> return this.getHeader().querySelector("[id$='face']");
Это какой-то переусложненный подход. Для смены свойств элемента надо использовать классы. А вот писать в коде только часть идентификатора - плохо, так как поиск по danger-face не найдт эту строку.
Если посмотреть на классы Field и Popup, то можно увидеть взаимные зависимости: Field вызывает Popup, а тот вызывает Field. В данном случае это говорит о неудачном проектировании. У тебя получился не универсальный попап, а попап, работющий только с объектом Field. Сделать зависимость односторонней можно, если например в Popup передавать коллбек, который будет вызван при нажатии на кнопку. Тогда Popup не будет ничего знать о Field и не будет к нему как-то привязан.
> Field.prototype.getTd = function(x, y) {
Этот метод сделан неэффективно. Ты ищешь все td, потом перебираешь их. Лучше сделать один из вариантов:
- искать td по table.rows.cells[j]
- сделать словарь, поместить в него ссылки на ячейки и искать как cells[y][x]
> Field.prototype.getTimerElem
Не очень понятно, зачем этот метод сделан публичным, а не приватным.
> this.getField().oncontextmenu = this._playerRightClick.bind(this);
Неправильно. Событие oncontextmenu соответствует открытию меню, а не нажатию правой кнопки. Например, меню можно открыть клавишей на клавиатуре. Для отслеживания клика правой кнопкой надо использовать onmousedown/up (оно срабатывает для всех кнопок мыши, а не только для левой).
> if (!this._isCorrectTd(td, event, 3, ["opened", "hint"])) {
Не очень понятно, почему мы передаем список классов. Разве функция "можно ли поставить флаг" сама не знает, по каким клеткам можно кликать? Какой смысл выносить это знание из функции?
Я бы сделал примерно так:
если (!можноПоставитьФлаг(...)) { ... }
поставитьФлаг();
То есть выделил бы действия вроде "поставить флаг", "проверить, можно ли стаивть флаг" в отдельные функции (или даже в отдельный класс, работающий с DOM). А у тебя код содержит не высокоуровневые действия, а детали реализации:
если (клетка содержит классы X и Y) { ... };
добавить класс Z;
Получается, ты в одной функции смешиваешь игровую логику и работу с DOM. Из-за этого код хуже читается. Также из-за этого получается небольшое дублирование кода.
То же самое и в других местах:
> if ((!cells.classList.contains("flag") &&
> cells.classList.contains("mine")) ||
А ведь можно написать if (!this._hasFlag(cell) && this._hasMine(cell)) ...
> face.removeAttribute("id");
> face.setAttribute("id", id);
Лучше просто face.id = .... А еще лучше классы менять.
> //Отменить contextmenu событие по-умолчанию
> event.preventDefault();
Почему-то это стоит в конце функции и не всегда срабатывает.
Классы для цифр лучше было бы пометить префиксом, для понятности и защиты от конфликтов (что кто-то еще сделает такой класс), например: mines-1, mines-2, как-то так.
> cells.sort(Util.shuffle)
Лучше было сделать Util.shuffle(cells), так как отдельно от sort твоя функция Shuffle смысла не имеет.
> cells.innerHTML = "& # 9773;";
Кстати, юникодные символы можно вставить в текстовую строку с помощью \uxxxx: https://learn.javascript.ru/string#специальные-символы
> Field.prototype._createHeader = function(container) {
Тут наверно тоже стоило бы использовать HTML-шаблон или вставить элементы в HTML код самой страницы. Не очень выгодно создавать дивы вручную. А вот ячейки удобнее генерировать программно.
Насчет цифр - можно было просто поискать нужный шрифт. Хотя вариант с картинками тоже годится, лучше наверно было бы использовать css-спрайт (картинку со всеми цифрами сразу). Вместо подгрузки картинок через создание новых img каждый раз выгоднее наверно просто менять css-классы или src у существующих объектов.
> _playerWinCheck
Имя функции начинается с глагола. "Что сделать?"
> new Popup(templatePopup.innerHTML, this);
> var outerTemplatePopup = popup.createPopup(message, container);
Показ попапа требует вызова конструктора и createPopup(), но непонятно, почему например, шаблон передается в конструктор, а container - в createPopup(). Надо подумать, куда лучше какой аргумент передавать.
Для победы лучше бы не требовать ставить флажки, а просто проверять что все, кроме мин, открыто. А флаги просто оставить для игрока, чтобы он мог отмечать клетки.
Насчет криво расположенного попапа - а ты пробовал пользоваться инструментами разработчика (Ctrl + shift + I) и посмотреть, какие css-свойства применены к попапу и к элементу, относительно которого он позиционируется?
> Field.MAX_NUMBER = 999;
> function Field(settings) {
Немного непривычно смотрится, когда ты сначала присваиваешь свойство функции, а только потом объявляешь. Там есть какие-то правила, что объявления функций могут выполняться до остального кода, но лучше бы просто переставить строчки местами.
> function Field(settings) {
Если настроек немного, то наверно проще было передавать их отдельными аргументаими. Хотя твой вариант тоже годится.
> var timer = setInterval(function() {
> clearTimeout(timer);
Не соответствуют друг другу функции. Также, clearTimeout разбросан несколько раз по коду, а правильнее было бы сделать функцию _stopTimer().
> safetyCells = this._openTheCell(safetyCells);
Непонятно, зачем результат возвращается и сохраняется назад в массив.
startNewGame не очищает старый таймер. Что, если он останется и продолжит работать?
В общем, тебе надо подумать над разделением кода работы с DOM и игровой логики. Хотя бы на уровне функций, а лучше - на уровне 2 классов.
> А что теперь делать, если нужно получить запросом данные из нескольких таблиц?
Смотрим, какая из них основная, и помещаем в нужный класс.
>>908446
>>912082
> Ну вдобавок почему-то не все элементы отсюда https://unicode-table.com/ru/#miscellaneous-symbols хотели отображаться, взял которые работали, думаю не смертельно.
Это зависит от установленных в твоей системе шрифтов.
> @import "css/style.css" screen;
А почему не просто <link href> ?
> src="../src/field.js?f
Не надо дописывать странные символы, лучше научиться очищать кеш и пользоваться соответствующей опцией в инструментах разработчика.
Код конечно получился доволно запутанным, из-за того, что вместе объединены игровая логика, вывод информации, и обработка событий. Такой большой код надо как-то разделять на части. У тебя много методов, и не очень понятно, как они и в каком порядке вызываются.
Ну например, если посмотреть на класс Field, то трудно понять, как им вообще пользоваться, как правильно инициализировать. Если бы вся инициализация была в конструкторе, было бы проще (хотя конечно DOM элементы может быть удобнее создавать отдельным методом, чтобы мы могли создать объект, и только потом создать таблицу). Еще есть такой вариант: сделать класс FieldBuilder, который при вызове метода createField() вернет объект класса Field, который управляет полем. Таким образом, управлять полем нельзя, пока оно не создано (а в твоем коде мы можем вызывать публичные методы вроде getCells() когда поле еще не создано). В общем, тут есть разные варианты.
Или, например, можно было бы попробовать разбить код хотя бы на 2 класса: один отвечает за работу с DOM, другой - за игровую логику.
Для хранения информации об открытых клеточках, есть 2 подхода, можно хранить данные об открытых клетках. рсположении мин, и тд, в JS-переменных (массивах и словарях), а можно хранить их в DOM, в виде классов и атрибутов. У тебя вроде бы используется второй.
> Field.prototype.getField = function() {
> return this._gameContainer.querySelector("#field");
Вот тут как минимум 2 проблемы:
- поле _gameContainer не инициализируется в конструкторе, значит мы должны как-то догадаться, когда уже можно вызывать этот метод, а когда еще нельзя
- вместо того, чтобы искать элемент каждый раз, может быть выгоднее просто хранить ссылку на него
> Field.prototype.getFace = function() {
> return this.getHeader().querySelector("[id$='face']");
Это какой-то переусложненный подход. Для смены свойств элемента надо использовать классы. А вот писать в коде только часть идентификатора - плохо, так как поиск по danger-face не найдт эту строку.
Если посмотреть на классы Field и Popup, то можно увидеть взаимные зависимости: Field вызывает Popup, а тот вызывает Field. В данном случае это говорит о неудачном проектировании. У тебя получился не универсальный попап, а попап, работющий только с объектом Field. Сделать зависимость односторонней можно, если например в Popup передавать коллбек, который будет вызван при нажатии на кнопку. Тогда Popup не будет ничего знать о Field и не будет к нему как-то привязан.
> Field.prototype.getTd = function(x, y) {
Этот метод сделан неэффективно. Ты ищешь все td, потом перебираешь их. Лучше сделать один из вариантов:
- искать td по table.rows.cells[j]
- сделать словарь, поместить в него ссылки на ячейки и искать как cells[y][x]
> Field.prototype.getTimerElem
Не очень понятно, зачем этот метод сделан публичным, а не приватным.
> this.getField().oncontextmenu = this._playerRightClick.bind(this);
Неправильно. Событие oncontextmenu соответствует открытию меню, а не нажатию правой кнопки. Например, меню можно открыть клавишей на клавиатуре. Для отслеживания клика правой кнопкой надо использовать onmousedown/up (оно срабатывает для всех кнопок мыши, а не только для левой).
> if (!this._isCorrectTd(td, event, 3, ["opened", "hint"])) {
Не очень понятно, почему мы передаем список классов. Разве функция "можно ли поставить флаг" сама не знает, по каким клеткам можно кликать? Какой смысл выносить это знание из функции?
Я бы сделал примерно так:
если (!можноПоставитьФлаг(...)) { ... }
поставитьФлаг();
То есть выделил бы действия вроде "поставить флаг", "проверить, можно ли стаивть флаг" в отдельные функции (или даже в отдельный класс, работающий с DOM). А у тебя код содержит не высокоуровневые действия, а детали реализации:
если (клетка содержит классы X и Y) { ... };
добавить класс Z;
Получается, ты в одной функции смешиваешь игровую логику и работу с DOM. Из-за этого код хуже читается. Также из-за этого получается небольшое дублирование кода.
То же самое и в других местах:
> if ((!cells.classList.contains("flag") &&
> cells.classList.contains("mine")) ||
А ведь можно написать if (!this._hasFlag(cell) && this._hasMine(cell)) ...
> face.removeAttribute("id");
> face.setAttribute("id", id);
Лучше просто face.id = .... А еще лучше классы менять.
> //Отменить contextmenu событие по-умолчанию
> event.preventDefault();
Почему-то это стоит в конце функции и не всегда срабатывает.
Классы для цифр лучше было бы пометить префиксом, для понятности и защиты от конфликтов (что кто-то еще сделает такой класс), например: mines-1, mines-2, как-то так.
> cells.sort(Util.shuffle)
Лучше было сделать Util.shuffle(cells), так как отдельно от sort твоя функция Shuffle смысла не имеет.
> cells.innerHTML = "& # 9773;";
Кстати, юникодные символы можно вставить в текстовую строку с помощью \uxxxx: https://learn.javascript.ru/string#специальные-символы
> Field.prototype._createHeader = function(container) {
Тут наверно тоже стоило бы использовать HTML-шаблон или вставить элементы в HTML код самой страницы. Не очень выгодно создавать дивы вручную. А вот ячейки удобнее генерировать программно.
Насчет цифр - можно было просто поискать нужный шрифт. Хотя вариант с картинками тоже годится, лучше наверно было бы использовать css-спрайт (картинку со всеми цифрами сразу). Вместо подгрузки картинок через создание новых img каждый раз выгоднее наверно просто менять css-классы или src у существующих объектов.
> _playerWinCheck
Имя функции начинается с глагола. "Что сделать?"
> new Popup(templatePopup.innerHTML, this);
> var outerTemplatePopup = popup.createPopup(message, container);
Показ попапа требует вызова конструктора и createPopup(), но непонятно, почему например, шаблон передается в конструктор, а container - в createPopup(). Надо подумать, куда лучше какой аргумент передавать.
Для победы лучше бы не требовать ставить флажки, а просто проверять что все, кроме мин, открыто. А флаги просто оставить для игрока, чтобы он мог отмечать клетки.
Насчет криво расположенного попапа - а ты пробовал пользоваться инструментами разработчика (Ctrl + shift + I) и посмотреть, какие css-свойства применены к попапу и к элементу, относительно которого он позиционируется?
> Field.MAX_NUMBER = 999;
> function Field(settings) {
Немного непривычно смотрится, когда ты сначала присваиваешь свойство функции, а только потом объявляешь. Там есть какие-то правила, что объявления функций могут выполняться до остального кода, но лучше бы просто переставить строчки местами.
> function Field(settings) {
Если настроек немного, то наверно проще было передавать их отдельными аргументаими. Хотя твой вариант тоже годится.
> var timer = setInterval(function() {
> clearTimeout(timer);
Не соответствуют друг другу функции. Также, clearTimeout разбросан несколько раз по коду, а правильнее было бы сделать функцию _stopTimer().
> safetyCells = this._openTheCell(safetyCells);
Непонятно, зачем результат возвращается и сохраняется назад в массив.
startNewGame не очищает старый таймер. Что, если он останется и продолжит работать?
В общем, тебе надо подумать над разделением кода работы с DOM и игровой логики. Хотя бы на уровне функций, а лучше - на уровне 2 классов.
> var keys = Object.keys(dataAttr);
> for (i = 0; i < keys.length; i++) {
Почему не for (key in dataAttr) ?
> for (var i = 0; i < classList.length; i++) {
> div.classList.add(classList);
Эффективнее наверно их склеить в строку и присвоить в className
> div.setAttribute("id", id);
div.id = ...
Атрибут id описан например тут: https://developer.mozilla.org/ru/docs/Web/API/Element
Посмотри заодно, какие там еще свойства и методы есть.
Аноны, кто-нибудь пилил шаблонизатор, который работает с отдельными tpl-файлами?
Суть в том, что сейчас использую такой. Самопильный, естественно.
Шаблонизатор не просто вставляет переменные в текст, а в самих tpl-файлах есть условия и вызовы других функций (например, `date`).
Единственная проблема -- файлы подключаются (и, соответственно, выводятся) через `include`. Есть необходимость переделать шаблоны и связанные с ними классы так, чтобы можно было получать результат работы шаблонизатора, но не выводить его сразу.
Но суть вопроса в том, что я понятия не имею как это сделать.
`file_get_contents`+`eval` ломается при первом PHP-теге. На этом мои идеи иссякли… Я даже не знаю как это гуглить -- все результаты, кроме работающих на `include`, это плагины для Вордпресс, лол.
Анон, у кого есть код?.. Алгоритм?.. Идеи?..
ЯННП. Как ты запускаешь свой самописный шаблонизатор, если ты сразу инклудишь шаблоны? Кажется, ты просто вынес html-код в отдельные файлы, а не используешь шаблонизатор.
>чтобы можно было получать результат работы шаблонизатора, но не выводить его сразу
ob_start();
// Отправляешь на вывод что угодно
$your_output = ob_get_clean();
В your_output лежит все, что должно было улететь на вывод (но не улетело).
>`file_get_contents`+`eval` ломается при первом PHP-теге.
И правильно делает. Если хочешь писать свой шаблонизатор со своими тегами, никаких php-тегов в шаблонах быть не должно.
Я хуй его знает что с ним делать нахуй, где эти ебучие логи смотреть, и что значить освоить командную строку? Там же миллиард програм которые работают через емулятор терминала , смотри анон, https://www.digitalocean.com/community/tutorials/apache-ubuntu-14-04-lts-ru делаю полностью по инструкции , просто напросто копирую пися в писю , когда перехожу оно мне выводит просто пустую страницу и пиздец, залупа гнида пидарска, ебать его в рот, говно ебаное. Во вторых нахуй, логи блять пидарские нахуй, я так понял что их тут смотрить
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Шо блять значить {APACHE_LOG_DIR} , я сука должен жить полноценой жизнью дроча апач и знать что обознчают апачивские макросы?
Ну это все хуйня, анан. Самое больше что меня страшить это mYsql, апач то можно удалить из бубнты, а вот мускул ты хуй удалишь из ОС, это хуже вируса герписа в нервной системе.
Я блять в отчьянии, нельзя блять обойтись без блять хоть без апача, ебать его в рот?
>Как ты запускаешь свой самописный шаблонизатор, если ты сразу инклудишь шаблоны?
Есть класс `Templater`. В конструктор передаются все данные, в виде массива `array('post' => $post)`. В моём случае это необходимо, ибо в разных tpl-файлах могут запрашиваться какие-либо данные -- для JS в шапке, для HTML в теле…
Ну, а Templater#show - просто экспортирует переменные функцией `extract` и инклюдит файл, имя которого передано аргументов в Templater#show.
Как-то так:
> new Templater($data_array)->show('head')->show('top')->show('form')->show('thread');
и т.д.
Таким образом:
-- каждый элемент ассоциативного массива становится переменной, к которой шаблоны имеют доступ. ПРОФИТ!
-- в метод Templater#show вторым аргументом можно передавать данные для именно этого файла. ПРОФИТ!
-- не ебёшься с данным каждый раз. ПРОФИТ!
-- можно няшно чейнить :з
>ob_start();
>$your_output = ob_get_clean();
Знал про эти функции, но как-то боялся контроль вывода испортить.
После `ob_get_clean` вывод вернётся в нормальное состояние? Всякие 'echo' не будут просить слить буфер?
https://github.com/enotocode/minesweeper.mvc
Прочитать мануал xampp?
В той директории, куда ты его установил, должна быть папка, в которую ты можешь положить свой срипт. В браузере он будетдоступен примерно по такому адресу "localhost\project\script.php". Иногда требуется перезагрузка xampa/wampa, чтобы новый скрипт стал доступным.
>Смотрим, какая из них основная, и помещаем в нужный класс.
А можно выделить такие мультизапросов-методы в отдельный класс? Чтобы не думать потом, а куда же я его положил всетаки сюда или сюда?
У меня ебучий линукс и я в душе не ебу куда он установиливает программы( на самом деле размазывает по всей файловой системе), да.Читаю мануал, вообще нет как запускать скрипты, может в глаза ласкаюсь, но не могу найти. И такой вопрос что такое phpmyadmin? Создал пользовтеля в нем, попытался под этим пользовтелем зайти из консоли, выдает Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' . Загуглил эту ошибку, я так понял что не установлены инструменты для работы с mysql и вот теперь сижу думаю как их установить все не сломать к хуям.
Выполняю задание по ним, а именно:
>Автозамена. Напиши скрипт, заменяющий определенное слово на другое (например, слово «дурак» на «хороший человек» в фразе «ты дурак»). Скрипт должен не пропускать слово, если оно написано буквами в разном регистре (ДуРАк), с заменой русских букв на похожие английские (а -> a), или через пробелы («ты — д у р а к»)
Понятно, что надо написать "между каждым из символов может встречаться символ пробела", но можно ли это сделать, не вставляя поиск пробела после каждого символа в искомой строчке?
Например: сделать не '/д//sу//sр//sа//sк//s/', а что-то типа '/(дурак)//s(предыдущий символ может содержаться в любом количестве в группе символов перед ним)/', где курсивом отмечена некая магическая команда регулярных выражений, которую я пропустил в мануалах.
Перечитал весь мануал, но так и не нашёл ответ на вопрос. Существует ли такая команда? Было бы жутко удобно.
Все очень просто
Поставь виртуальную машину, накати винду (если не получается в линукс), утстанови опоч и пыхыч.
Не баян а классика!
Твой шаблон - это просто PHP файл. Нужно использовать ob_clean/ob_get_clean(). Для чистоты хорошо бы еще поддержать обработку исключений и в catch/finally поставить ob_end_clean.
>>912543
mysql точно так же удаляется, я думаю.
Если ты не знаешь командную строку, то начни с гайда по ней в ОП посте.
Значение переменной легко увидеть через echo $VAR
Статьи на DO не для глупых копипастеров команд, а для тех, кто понимает, что делает. Если ты не понимаешь, либо учись, либо найми того, кто понимает.
>>912649
Нет, это нелогично. И в запросе обычно всегда есть какая-то основная сущность, ну например, выбираем коммент и приджойниваем его автора - можно положить в класс для комментов.
>>912776
> а что-то типа '/(дурак)//s(предыдущий символ может содержаться в любом количестве в группе символов перед ним)/',
Нельзя так.
>>912775
Нужны вебсокеты. Страница устаналивает постоянное соединение с демоном на сервере, и тот присылает ей информацию о новых сообщениях, например, по протоколу WAMP (Web Application Message Protocol).
Чтобы пользоваться вебсокетами, надо сначала освоить сети, обычные сокеты и сетевое программирование. Вот список заданий и теории для изучения: https://2ch.hk/pr/arch/2016-12-01/res/864640.html#880424 (М)
>>912653
phpmyadmin - это клиент для сервера баз данных MySQL, сделанный как PHP приложение. Начинающему я бы советовал сначала освоить клиент для командной строки mysql, а то приходят потом люди на работу и не понимают, как миграцию написать, привыкли таблицы через кнопочки править.
Для начала можешь изучить вообще, что такое "клиент" и "сервер".
> Загуглил эту ошибку, я так понял что не установлены инструменты для работы с mysql
Плохо гуглил, это сокет для соединения с сервером mysql, значит он не установлен или не запущен.
>>912610
Это функция, которую передают в другую функцию и та вызывает переданнную. Потому и "функция обратного вызова" - не мы ее вызываем, а функция, в которую мы коллбек передали.
Зачем они нужны? Был такой вопрос и ответ на него выше в посте >>908094
Попробуй решить написанные там задачи (про отбор по критерию).
Твой шаблон - это просто PHP файл. Нужно использовать ob_clean/ob_get_clean(). Для чистоты хорошо бы еще поддержать обработку исключений и в catch/finally поставить ob_end_clean.
>>912543
mysql точно так же удаляется, я думаю.
Если ты не знаешь командную строку, то начни с гайда по ней в ОП посте.
Значение переменной легко увидеть через echo $VAR
Статьи на DO не для глупых копипастеров команд, а для тех, кто понимает, что делает. Если ты не понимаешь, либо учись, либо найми того, кто понимает.
>>912649
Нет, это нелогично. И в запросе обычно всегда есть какая-то основная сущность, ну например, выбираем коммент и приджойниваем его автора - можно положить в класс для комментов.
>>912776
> а что-то типа '/(дурак)//s(предыдущий символ может содержаться в любом количестве в группе символов перед ним)/',
Нельзя так.
>>912775
Нужны вебсокеты. Страница устаналивает постоянное соединение с демоном на сервере, и тот присылает ей информацию о новых сообщениях, например, по протоколу WAMP (Web Application Message Protocol).
Чтобы пользоваться вебсокетами, надо сначала освоить сети, обычные сокеты и сетевое программирование. Вот список заданий и теории для изучения: https://2ch.hk/pr/arch/2016-12-01/res/864640.html#880424 (М)
>>912653
phpmyadmin - это клиент для сервера баз данных MySQL, сделанный как PHP приложение. Начинающему я бы советовал сначала освоить клиент для командной строки mysql, а то приходят потом люди на работу и не понимают, как миграцию написать, привыкли таблицы через кнопочки править.
Для начала можешь изучить вообще, что такое "клиент" и "сервер".
> Загуглил эту ошибку, я так понял что не установлены инструменты для работы с mysql
Плохо гуглил, это сокет для соединения с сервером mysql, значит он не установлен или не запущен.
>>912610
Это функция, которую передают в другую функцию и та вызывает переданнную. Потому и "функция обратного вызова" - не мы ее вызываем, а функция, в которую мы коллбек передали.
Зачем они нужны? Был такой вопрос и ответ на него выше в посте >>908094
Попробуй решить написанные там задачи (про отбор по критерию).
Да, всё дело в регистре, надо внимательнее относиться, спасибо. Знак "меньше" я просто для проверки ставил, хотел увидеть, выведет ли того, кто ниже.
> Начинающему я бы советовал сначала освоить клиент для командной строки mysql, а то приходят потом люди на работу и не понимают, как миграцию написать, привыкли таблицы через кнопочки править.
Ну охуеть просто, ну у тебя и советы. Кому всралась эта командная стока, если всем надо "ЧТО БЫ ПРОСТО РАБОТАЛО!!" .Командную строку я немного знаю но не вижу причин изучать ее больше, один хер открываешь вакансии, а там фреймворк ебет фреймворк фреймвоком, да да я знаю основы вся хуйня. И тем более что я спрашиваю если я установлю mysql в ос, они будут пользоваться одной базой mysql консоли и phpmyadmin?
И почему постояные посылы в шапку? Говно шапка, главная проблем что вся эта поебота - php, mysql, апач, composera и куча компонентов которые надо лепить в кучу и за этого у ньюфагов просто мозги взрываються.
>>Кому всралась эта командная стока
Тебе. Она помогает разобраться и понять как устроено то, с чем ты работаешь.
>я установлю mysql в ос, они будут пользоваться одной базой mysql консоли и phpmyadmin?
Вообще да, но с таким настроем, у тебя вообще ничего работать не будет.
>И почему постояные посылы в шапку?
Потому что сядь и изучи уроки, вместо того чтобы постоянно плакать.
>главная проблем что вся эта поебота - php, mysql, апач, composera и куча компонентов которые надо лепить в кучу и за этого у ньюфагов просто мозги взрываються
Сначала разберись с чем-то одним, вместо того, чтобы хвататься за все сразу. По всем перечисленным пунктам у опа есть уроки. Все есть в шапке.
>в кучу
Всё последовательно изложено, что тебе не нравится? Ни кто не мешает тебе остановиться и поискать информацию самостоятельно.
Ах, да,
>У меня ебучий линукс
>Я хуй его знает что с ним делать нахуй
Ну и пошёл нахуй тогда.
>>913151
Зачем вы вообще ему отвечаете? Он ничего не хочет изучать, наивно полагая, что все инструменты должны "просто работать", после того, как нажмёшь одну большую кнопку. Только вот за что платить такому спецу? Его ведь может заменить любой.
>>913174
Рейтингов есть много разных: https://habrahabr.ru/company/kingservers/blog/307012/
А вот как дела обстоят у нас: https://habrahabr.ru/company/hh/blog/318450/
Хм, почему то js-никам не нужно идеально знать устройство операционой системы, наврно по этому js и так прет вверх. Я же не виноват что в linux по ебанутому организовано удаление программ. После целого дня ебли и удалениячисти, чисти! зависимостей мускул стал.
Если запущен phpMyAdmin, то доступ к баз ене возможен?
Да, это конечно ближе к делу, но все-таки по миру видно что php слабеет, а вот js наоборот поднимается, такой конечно пиздец это "единые решения" вокруг одного языка (я про реакты и ноды)
>>913240
>Ну и пошёл нахуй тогда.
Целую тебя в твои румяные ягодички.
> Ага, вот что получается если запущен phpMyAdmin, до доступа к mysql через консоль не возможен, по этому оно и выкидывало
Это ты уже от себя выдумываешь, потому что видимо не понимаешь даже как клиент связывается с сервером. Проблемы могут быть только если в MySQL стоит ограничение на 1 соединение (что вряд ли), и даже в этом случае ошибка будет в том клиенте, которые пытается подключиться вторым.
> Так, буду и дальше держать вас в курсе.
Больше пользы тебе будет, если ты разберешься, почему именно что-то не работало и как это исправить. Ну например при ошибке cannot connect стоит как минимум проверить:
- установлен ли сервер
- запущен ли он
- указано ли в конфиге сервера, что надо слушать данный сокет
А не переустанавливать программы наугад.
>>913333
Ты не путай верстальщиков и разработчиков на Javascript.
В linux во многих дистрибутивах (Дебиан, Убунту) установка и удаление программ организовано лучше, чем в винде. Расскажи, как в винде одной командой вроде apt-get install php5 установить PHP? Надо идти искать сайт, скачивать архивы, распаковывать, проверять антивирусом, устанавливать, жать далее, ой, надо было запустить от администратора.
Ты бы мог привести как хороший пример Андроид или iOS. Там тоже используется пакетный менеджер (как в линуксах), просто для пользователя там сверху к нему прикручен магазин приложений с графическим интерфейсом, а админы люди суровые и больше любят командную строку, а не прокручивать сотни картинок и смотреть на тормозящие анимации.
> Если запущен phpMyAdmin, то доступ к баз ене возможен?
Это ни на что не влияет. MySQL поддерживает несколько соединений одновременно.
>>913250
Да не выйдет ничего у него с таким подходом, как только надо будет сделать что-то не по туториалу, он сядет в лужу и будет полдня разбираться, в итоге все удалит и установит заново в надежде что оно само заработает.
>>913174
Ты смотрел, как этот рейтинг формируется, от чего зависит?
>>913088
> если всем надо "ЧТО БЫ ПРОСТО РАБОТАЛО!!"
Либо изучай теорию, либо нанимай того, кто ее изучил. Тогда будет "просто работать".
> И тем более что я спрашиваю если я установлю mysql в ос, они будут пользоваться одной базой mysql консоли и phpmyadmin?
Ты сам-то понял, что написал? ЧТо значит "база mysql консоли"? ЧТо значит "база phpmyadmin"? phpmyadmin - это клиент для доступа к серверу БД. В нем нет никаких баз. Я тебе вчера написал, прочитай что такое клиент и сервер, ты даже это поленился сделать.
Также желательно хотя бы в общих чертах понимать, что такое TCP-соединение, IP-адрес, localhost, номер порта, unix-domain сокет.
> Ага, вот что получается если запущен phpMyAdmin, до доступа к mysql через консоль не возможен, по этому оно и выкидывало
Это ты уже от себя выдумываешь, потому что видимо не понимаешь даже как клиент связывается с сервером. Проблемы могут быть только если в MySQL стоит ограничение на 1 соединение (что вряд ли), и даже в этом случае ошибка будет в том клиенте, которые пытается подключиться вторым.
> Так, буду и дальше держать вас в курсе.
Больше пользы тебе будет, если ты разберешься, почему именно что-то не работало и как это исправить. Ну например при ошибке cannot connect стоит как минимум проверить:
- установлен ли сервер
- запущен ли он
- указано ли в конфиге сервера, что надо слушать данный сокет
А не переустанавливать программы наугад.
>>913333
Ты не путай верстальщиков и разработчиков на Javascript.
В linux во многих дистрибутивах (Дебиан, Убунту) установка и удаление программ организовано лучше, чем в винде. Расскажи, как в винде одной командой вроде apt-get install php5 установить PHP? Надо идти искать сайт, скачивать архивы, распаковывать, проверять антивирусом, устанавливать, жать далее, ой, надо было запустить от администратора.
Ты бы мог привести как хороший пример Андроид или iOS. Там тоже используется пакетный менеджер (как в линуксах), просто для пользователя там сверху к нему прикручен магазин приложений с графическим интерфейсом, а админы люди суровые и больше любят командную строку, а не прокручивать сотни картинок и смотреть на тормозящие анимации.
> Если запущен phpMyAdmin, то доступ к баз ене возможен?
Это ни на что не влияет. MySQL поддерживает несколько соединений одновременно.
>>913250
Да не выйдет ничего у него с таким подходом, как только надо будет сделать что-то не по туториалу, он сядет в лужу и будет полдня разбираться, в итоге все удалит и установит заново в надежде что оно само заработает.
>>913174
Ты смотрел, как этот рейтинг формируется, от чего зависит?
>>913088
> если всем надо "ЧТО БЫ ПРОСТО РАБОТАЛО!!"
Либо изучай теорию, либо нанимай того, кто ее изучил. Тогда будет "просто работать".
> И тем более что я спрашиваю если я установлю mysql в ос, они будут пользоваться одной базой mysql консоли и phpmyadmin?
Ты сам-то понял, что написал? ЧТо значит "база mysql консоли"? ЧТо значит "база phpmyadmin"? phpmyadmin - это клиент для доступа к серверу БД. В нем нет никаких баз. Я тебе вчера написал, прочитай что такое клиент и сервер, ты даже это поленился сделать.
Также желательно хотя бы в общих чертах понимать, что такое TCP-соединение, IP-адрес, localhost, номер порта, unix-domain сокет.
Лупбек тест как его понял: https://github.com/kubk/students/blob/master/tests/AuthServiceTest.php
Предположим, что теперь нам нужно использовать пару кук пароль+почта: https://github.com/kubk/students/tree/password-auth
Тест AuthService не изменился, так как по сути сервис сам себя тестирует. Сам бы до такого не додумался.
Ещё меня удивило насколько мало пришлось менять кода ради того, чтобы изменить способ регистриции. В прошлой версии студентов я не делал отдельного объекта для формы, а теперь понимаю, что нужно было.
И касательно этого метода: https://github.com/kubk/students/blob/password-auth/tests/AuthServiceTest.php#L42
Вспомнил про аннотацию @depends, поможет ли она сделать testLoopback более читаемым или только размажет его по методам?
Идея с авторизацией через соцсети заинтересовала.
>Твой шаблон - это просто PHP файл
Поэтому мне и было интересно: можно-ли как-нибудь, кроме буферизации вывода, запустить код, но получить результат в переменную, а не на выход как это обычно бывает между PHP-тегами.
И тебе спасибо ^_^
>Это ты уже от себя выдумываешь, потому что видимо не понимаешь даже как клиент связывается с сервером
По логике вещей ты прав, но я проверял и не один раз.
>установлен ли сервер, запущен ли он , указно ли в конфиге
Вот это я не знаю как гуглить, а во всех книгах просто пишут как работать с оболочками, я даже не знаю чем отличается клиент от сервера мускула, мда.
http://ideone.com/1iIqrG
Она рассчитана на использование циклов или нет? Просто моё решение не выглядит оптимальным из-за размера.
Протестировал на PHP 5.6, но хочу быть уверен. Не нагуглил.
Да не должны течь по логике, но нахуя они? Приведи пример уместного использования
В PHP все функции глобальные, это не яваскрипт. Попытка создать ту же функцию второй раз приведет к фатальной ошибке.
Потому функции внутри функций объявлять не только бесполезно , но и вредно.
Используй для этого анонимные функции $fn = function () { ... };
>В PHP все функции глобальные
Тогда почему этот код с 1й пикчи вывел 'passed'?
`function_exists` накрыла-бы его, если-бы область видимости потекла.
>Попытка создать ту же функцию второй раз приведет к фатальной ошибке
>пик2
Или я неправильно тестирую, или у меня неправильный PHP.
Опять таки, уже ненужно, ибо придумал нормальный способ написать.. но всё-равно интересно.
error_reporting(-1);
for ($x = 1; $x < 10; $x++) {
$result = $x $x;
echo "$x $x = $result\n";
}
?>
это единственный способ впихнуть результат в эхо, чтобы он был вида
5 * 5 = 25
? если вместо резалта впихнуть выражение, как я бы в питоне сделал, то он пишет хуету какуюто
проебались знаки * кое где, ну там и так понятно
>Или я неправильно тестирую, или у меня неправильный PHP.
Потому, что код со 2го пика тоже выводит 'passed'.
Потому что функция внутри не создастся, пока ты не вызовешь test(). Лучше напиши так:
test();
test();
>>914119
PHP не Питон. В строку нельзя вставлять произвольные выражения, но их можно приклеить точкой:
echo "x = " . ($x + 1);
Или вставить через sprintf("x = %d", $x + 1);
Для запуска программ нужен интерпретатор PHP. В шапке есть уроки:
- про командную строку
- про установку PHP
Прочитай их и сможешь запускать программы. Когда тебе надост возиться с консолью, настрой запуск интерпретатора в своем редакторе, если он это поддерживает.
Там есть ошибки внизу, обращение к несуществующей еще переменной, их надо исправить:
> PHP Notice: Undefined variable: string1 in /home/iQgWcT/prog.php on line 20
Также, первые 2 строки генерируются по одинаковому алгоритму, там можно было бы сделать цикл из 2 повторений вместо копипасты.
> $random1=array_rand($word1,1);
> $choosenWord1=$word1[$random1];
Тут можно убрать промежуточную переменную $random1.
> $string2=$string2 . $choosenWord1. ' ';
Эту переменную можно убрать и сразу выводить $chosenWord1 .. 3 через echo
>>913716
Ну так погугли "архитектура клиент-сервер"
http://www.4stud.info/networking/lecture5.html
https://ru.wikipedia.org/wiki/Клиент_—_сервер
http://www.mstu.edu.ru/study/materials/zelenkov/ch_7_1.html
http://citforum.ru/programming/application/builder_cs3.shtml
Кроме серверных СУБД, бывают еще встраиваемые БД. Это когда серверов/клиентов нет и код, управляющий БД, встроен в приложение (например как библиотека), и только это одно приложение может работать с этой базой данных. Данные, как правило, в таких случаях хранятся в файле. Например, так работает встраиваемая БД sqlite, которая используется в андроиде, скайпе, хроме для хранения истории, настроек и прочего. В PHP, кстати, она тоже доступна.
> Вот это я не знаю как гуглить
Управление сервисами (фоновыми процессами) сделано по-разному в разных дистрибутивах. В современных Дебианах и Убунту используется systemd, так что стоит гуглить его:
"systemd проверить статус"
"systemd запустить службу"
И так далее.
Также можно вывести список запущенных процессов командой ps lax и отфильтровать его командой grep mysql. Почитай и про эти команды тоже.
Проверить наличие файла сокета можно командой stat /var/lib/mysqld....
Там есть ошибки внизу, обращение к несуществующей еще переменной, их надо исправить:
> PHP Notice: Undefined variable: string1 in /home/iQgWcT/prog.php on line 20
Также, первые 2 строки генерируются по одинаковому алгоритму, там можно было бы сделать цикл из 2 повторений вместо копипасты.
> $random1=array_rand($word1,1);
> $choosenWord1=$word1[$random1];
Тут можно убрать промежуточную переменную $random1.
> $string2=$string2 . $choosenWord1. ' ';
Эту переменную можно убрать и сразу выводить $chosenWord1 .. 3 через echo
>>913716
Ну так погугли "архитектура клиент-сервер"
http://www.4stud.info/networking/lecture5.html
https://ru.wikipedia.org/wiki/Клиент_—_сервер
http://www.mstu.edu.ru/study/materials/zelenkov/ch_7_1.html
http://citforum.ru/programming/application/builder_cs3.shtml
Кроме серверных СУБД, бывают еще встраиваемые БД. Это когда серверов/клиентов нет и код, управляющий БД, встроен в приложение (например как библиотека), и только это одно приложение может работать с этой базой данных. Данные, как правило, в таких случаях хранятся в файле. Например, так работает встраиваемая БД sqlite, которая используется в андроиде, скайпе, хроме для хранения истории, настроек и прочего. В PHP, кстати, она тоже доступна.
> Вот это я не знаю как гуглить
Управление сервисами (фоновыми процессами) сделано по-разному в разных дистрибутивах. В современных Дебианах и Убунту используется systemd, так что стоит гуглить его:
"systemd проверить статус"
"systemd запустить службу"
И так далее.
Также можно вывести список запущенных процессов командой ps lax и отфильтровать его командой grep mysql. Почитай и про эти команды тоже.
Проверить наличие файла сокета можно командой stat /var/lib/mysqld....
Вы должны решить как минимум Вектор, чтобы взяться за нее. Если вы сделали студентов или файлообменник - эту задачу должны решить легко.
По поводу проверки email на уникальность: я бы назвал класс не UniqueEmail, а StudentEmailUnique, так как это ограничение работает только для одного класса - студента. Если ты хочешь сделать универсальное ограничение, работающее с любыми сущностями, то тебе придется усложнить класс, добавив в него такие свойства:
- имя поля для email. По имени поля нам надо как-то получить сам email (ведь модель данных в форме может быть массивом, объектом со свойствами, объектом с методами, объектом с ArrayAccess). Для этого можно использовать класс PropertyAccessor, метод getValue().
- callable, которая принимает на вход id объекта и email и ищет, есть ли такой в базе.
Также, вместо callable можно сделать интерфейс вроде EmailCheckInterface с методом doesEmailExists($email, $id) и передавать в Constraint реализацию этого интерфейса.
В общем, получается немного костыльно.
В Симфони эта проблема частично решается тем, что для доступа к БД используется Доктрина, и там указывается только имя поля: https://symfony.com/doc/current/reference/constraints/UniqueEntity.html
Можешь посмотреть код этого ограничения и его валидатора, если интересно.
Насчет обработки HTTP-исключений - я тут глянул документацию http://silex.sensiolabs.org/doc/2.0/usage.html#error-handlers и по моему, мы немного изобрели велосипед, так как какая-то обработка там уже есть по умолчанию. Ну например, HTTP-код на Response выставляется автоматически, а переданный тобой игнорируется.
Тут https://mighty-ridge-55042.herokuapp.com/ поиск по фамилии зависит от регистра букв. Возможно, неправильно выбран COLLATION для поля таблицы или не установлена какая-то настройка MySQL.
При попытке при регистрации отправить email tesITPtANUSприм=w"ерPUNCTUMр.Ukф код падает с ошибкой 503.
Для пометки найденного слова есть тег <mark>
В тесте HTML-экранирования тоже немного заложены детали реализации: ты проверяешь конкретную мнемонику, вроде & lt ; . Но угловую скобку можно записать и по-другому, кодом вроде & # 1234 ; или & # 001234 ; . Конечно маловероятно, что алгоритм htmlspecialchars поменяется, но можно было бы сделать тест так:
- передаем строку с тегом и проверяем, что на выходе тега нет
- вызываем html_entity_decode на результате и проверяем, что получилась исходная строка
- аналогично можно проверить кавычки, а также отдельные угловые скобки, что их нет в результирующей строке
> public function getRegisteredStudent(ParameterBag $parameterBag)
Тут аргумент лучше назвать $cookiesBag, а то нельзя понять, что туда передавать. А еще лучше конечно бы не писать конкретный класс в тайп-хинте, а указать, что нам подойдет любой класс, из которого можно получать значения (включая массив), но к сожалению, в PHP так сделать (пока) нельзя.
> public function rememberStudent(Student $student, ResponseHeaderBag $headers): ResponseHeaderBag
Здесь незачем возвращать Bag, так как этот тот же самый объект, что подается на вход. То, что ты его возвращаешь, значит, что пользователь функции должен теперь этот Bag как-то засунуть в HTTP ответ (ведь тут не гарантии, что это тот же объект, что на входе).
Более того, это может привести к ошибкам, посмотри на код:
> $unregisteredStudentHeaders = $authService->unregister($rememberedStudentHeaders);
Можно подумать, что тут $rememberedStudentHeaders не изменяется.
> public function studentsAreTheSame(Student $studentA, Student $studentB): bool
Я думаю, это все же не должно быть в классе авторизации. У него нет задачи сравнивать студентов. Полтьзователь должен сам догадаться, как их сравнивать.
В Доктрине, где есть Entity Map, достаточно сделать $a === $b. В других случаях надо сравнивать id, token или другой уникальный идентификатор.
> public function unregister
Правильнее logOut или forget (антоним remember).
> $result = $this->authService->getRegisteredStudent($parameterBag);
> $this->assertFalse($result instanceof Student);
Лучше assertEmpty или assertNull.
Так, в общем, тесты неплохо сделаны.
> Вспомнил про аннотацию @depends, поможет ли она сделать testLoopback более читаемым или только размажет его по методам?
Мне не очень нравится, так как код становится менее очевидным (и кстати, я про нее не помнил, пришлось лезть в гугл). Да и экономит ведь это всего одну строчку.
> Ещё меня удивило насколько мало пришлось менять кода ради того, чтобы изменить способ регистриции.
В этом и смысл инкапсуляции и разделения отвественности.
Насчет авторизации по паролю - лучше в куке (и в БД) хранить не пароль, а его соленый хеш. Чуть безопасней, а так у тебя пароль в открытом виде на каждый запрос шлется. И в базе лежит. Украли базу/куки - и можно идти проверять, подойдет ли этот пароль к почте, к соцсетям итд.
По хорошему конечно надо еще формы регистрации и авторизации обрабатывать через HTTPS. А то любой провайдер или владелец вайфая может все в открытом виде смотреть.
По поводу проверки email на уникальность: я бы назвал класс не UniqueEmail, а StudentEmailUnique, так как это ограничение работает только для одного класса - студента. Если ты хочешь сделать универсальное ограничение, работающее с любыми сущностями, то тебе придется усложнить класс, добавив в него такие свойства:
- имя поля для email. По имени поля нам надо как-то получить сам email (ведь модель данных в форме может быть массивом, объектом со свойствами, объектом с методами, объектом с ArrayAccess). Для этого можно использовать класс PropertyAccessor, метод getValue().
- callable, которая принимает на вход id объекта и email и ищет, есть ли такой в базе.
Также, вместо callable можно сделать интерфейс вроде EmailCheckInterface с методом doesEmailExists($email, $id) и передавать в Constraint реализацию этого интерфейса.
В общем, получается немного костыльно.
В Симфони эта проблема частично решается тем, что для доступа к БД используется Доктрина, и там указывается только имя поля: https://symfony.com/doc/current/reference/constraints/UniqueEntity.html
Можешь посмотреть код этого ограничения и его валидатора, если интересно.
Насчет обработки HTTP-исключений - я тут глянул документацию http://silex.sensiolabs.org/doc/2.0/usage.html#error-handlers и по моему, мы немного изобрели велосипед, так как какая-то обработка там уже есть по умолчанию. Ну например, HTTP-код на Response выставляется автоматически, а переданный тобой игнорируется.
Тут https://mighty-ridge-55042.herokuapp.com/ поиск по фамилии зависит от регистра букв. Возможно, неправильно выбран COLLATION для поля таблицы или не установлена какая-то настройка MySQL.
При попытке при регистрации отправить email tesITPtANUSприм=w"ерPUNCTUMр.Ukф код падает с ошибкой 503.
Для пометки найденного слова есть тег <mark>
В тесте HTML-экранирования тоже немного заложены детали реализации: ты проверяешь конкретную мнемонику, вроде & lt ; . Но угловую скобку можно записать и по-другому, кодом вроде & # 1234 ; или & # 001234 ; . Конечно маловероятно, что алгоритм htmlspecialchars поменяется, но можно было бы сделать тест так:
- передаем строку с тегом и проверяем, что на выходе тега нет
- вызываем html_entity_decode на результате и проверяем, что получилась исходная строка
- аналогично можно проверить кавычки, а также отдельные угловые скобки, что их нет в результирующей строке
> public function getRegisteredStudent(ParameterBag $parameterBag)
Тут аргумент лучше назвать $cookiesBag, а то нельзя понять, что туда передавать. А еще лучше конечно бы не писать конкретный класс в тайп-хинте, а указать, что нам подойдет любой класс, из которого можно получать значения (включая массив), но к сожалению, в PHP так сделать (пока) нельзя.
> public function rememberStudent(Student $student, ResponseHeaderBag $headers): ResponseHeaderBag
Здесь незачем возвращать Bag, так как этот тот же самый объект, что подается на вход. То, что ты его возвращаешь, значит, что пользователь функции должен теперь этот Bag как-то засунуть в HTTP ответ (ведь тут не гарантии, что это тот же объект, что на входе).
Более того, это может привести к ошибкам, посмотри на код:
> $unregisteredStudentHeaders = $authService->unregister($rememberedStudentHeaders);
Можно подумать, что тут $rememberedStudentHeaders не изменяется.
> public function studentsAreTheSame(Student $studentA, Student $studentB): bool
Я думаю, это все же не должно быть в классе авторизации. У него нет задачи сравнивать студентов. Полтьзователь должен сам догадаться, как их сравнивать.
В Доктрине, где есть Entity Map, достаточно сделать $a === $b. В других случаях надо сравнивать id, token или другой уникальный идентификатор.
> public function unregister
Правильнее logOut или forget (антоним remember).
> $result = $this->authService->getRegisteredStudent($parameterBag);
> $this->assertFalse($result instanceof Student);
Лучше assertEmpty или assertNull.
Так, в общем, тесты неплохо сделаны.
> Вспомнил про аннотацию @depends, поможет ли она сделать testLoopback более читаемым или только размажет его по методам?
Мне не очень нравится, так как код становится менее очевидным (и кстати, я про нее не помнил, пришлось лезть в гугл). Да и экономит ведь это всего одну строчку.
> Ещё меня удивило насколько мало пришлось менять кода ради того, чтобы изменить способ регистриции.
В этом и смысл инкапсуляции и разделения отвественности.
Насчет авторизации по паролю - лучше в куке (и в БД) хранить не пароль, а его соленый хеш. Чуть безопасней, а так у тебя пароль в открытом виде на каждый запрос шлется. И в базе лежит. Украли базу/куки - и можно идти проверять, подойдет ли этот пароль к почте, к соцсетям итд.
По хорошему конечно надо еще формы регистрации и авторизации обрабатывать через HTTPS. А то любой провайдер или владелец вайфая может все в открытом виде смотреть.
Все должно работать нормально.
>>912553
https://github.com/enotocode/minesweeper.mvc
Я вижу, у тебя много js-файлов. Вручную вписывать их в код в нужном порядке становится утомительно, да и это неэффективно, грузить много мелких файлов через Интернет.
Можешь прочитать про системы модулей в JS: CommonJS и AMD. Затем - про сборщики и загрузкичи модулей вроде WebPack.
Советую настроить сборщик так, чтобы в режиме разработки не требовалась бы сборка и файлы бы грузились напрямую (это упрощает и ускоряет отладку), а в режиме релиза собирался бы единый JS файл. Некоторые делают так, что собрка используется всегда, но это по моему только все усложняет: нужны карта файлов и отладчик, который ее поддерживает, надо ждать, пока все соберется, и тд.
Кстати, ты используешь jsDoc, если ты используешь (или использовал бы) IDE, которая его поддерживает, это может улучшить качество автодополнения кода, и иногда IDE даже может проверять код на правильность за счет аннотаций. PhpStorm/WebStorm вроде это умеет.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/Cell.js
Тут surroundinMines наверно выгоднее не хранить, а сделать у Field метод для его расчета. Вообще, странно, у тебя у Клеточки есть своство "isMine", но оно в MinesweeperGame не проставляется. Очень странно.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js
тут наверно лучше не объявлять свойства вроде openCells публичными, а сделать методы для получения нужной информации из них.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js#L37
Здесь может получиться потенциально бесконечный цикл, такое следует избегать.
openCells, возможно, стоило сделать словарем для более эффективного поиска, вроде this.openCells[y][x] = true;
Наличие словаря избавит нас от необходимости писать сложные алгоритмы пересечения списков клеток.
В классе MinesweeperGame, возможно, лучше было бы сделать 2 словаря:
- cells[y][x] с объектами Cell с информацией о клеточке
- openedCells[y][x] с true/false, показывает, открыта ли клетка
- markedCells[y][x] true/false
Можно сделать даже один словарь, если сделать у ячейки свойства isOpened/isMarked, но тогда их можно случайно поменять, и MinesweeperGame об этом не узнает (и не сгенерирует событие). Но можно конечно договориться так не делать. Или можно сделать Cell тоже источником событий, и подписываться на событие открытия, но это конечно немного усложнит жизнь.
А у тебя этого нет, у тебя одна и та же клеточка может быть представлена 2 разными объектами в minedCells и openCells, это, мне кажется, нехорошо. Ведь тогда, если мы хотим что-то у клеточки поменять, мы должны найти все экземпляры и поменять нужное свойство. Лучше делать, что 1 сущности соответствует ровно 1 экземпляр объекта.
Вот если бы ты убрал из Cell свойство isMined, и сделал класс CellCoords с свойствами x/y, ты бы мог создавать их сколько угодно, потому что они представляют координаты ячейки, а не саму ячейку. А ячейка должна быть в одном экземпляре.
Может тебе задачку на ООП дать про отель, если ты эти вещи не очень понимаешь?
Вообще, когда нас модель сложная и содержит много объектов (например, для программы управления отелем будут классы Отель - Номер - Постоялец - Бронь - Счет за услуги), нам так или иначе надо отслеживать изменения в них, по всему дереву иерархии, и иметь поток информации об изменениях (вроде "Номер 302 поменял статус на Забронирован") И вариантов-то тут не так и много:
- либо в каждый класс добавлять события, генерировать их при изменениях, и класс-владелец подписывается на события в объектах, которые он хранит внутри
-либо мы должны использовать angular-подобную схему, когда мы сохраняем копию объектов, и после изменений сравниваем граф (набор) объектов с сохраненной ранее копией, находим разницу и генерируем события изменения. Это требует меньше кода, но работает медленнее и ест больше памяти.
- либо react-подобную, когда изменения нас не интересуют, так как мы каждый раз пересоздаем дерево DOM полностью. Но тут надо понимать, что события нам все равно могут понадобиться, например, для реализации функции undo
- либо использовать иммутабельные объекты (объекты, которые нельзя изменять). То есть при любом изменении на поле мы создаем новый экземпляр поля и новый экземпляр клеточки. Это требует очень много памяти, зато мы в любой момент можем сохранить текущий объект Поле в переменную, и знать что он и клеточки в нем никогда не изменятся. Удобно, если нам нужно иметь полную историю всех изменений в модели. Перемещение на 5 ходов назад получается очень простым, надо просто взять старый экземпляр Модели из истории и поставить как текущий.
(если у тебя будет время, можешь со всеми этими методами поэкспериментировать, или хотя бы подумать, как это реализовать).
В общем, для начала надо правильно реализовать Модель (классы, которые хранят состояние игры и клеточек). Где что хранится, как что можно узнать.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js#L311
> console.log('Watch out!');
> return false
Не очень понял смысл этих строчек. Может, надо исключение выбросить?
https://github.com/enotocode/minesweeper.mvc/blob/master/app/GameEvent.js
Здесь, ты может сам заметил, проблема в том, что у разных событий может быть разное число аргументов, с разными названиями. Получается, надо либо сделать разные классы событий, либо сделать один класс, а аргументы передавать словарем вроде { cell: someCell, action: SomeClass.ACTION_OPEN }
Использование нескольких классов требует больше кода, но позволяет ставить jsDoc, добавлять кастомные методы и свойства в события.
Ну например, для событий GAME_OVER target не очень-то и нужен, так как подписчик должен знать, на какой объект он подписывался.
Еще, наверно, стоило бы начать раскидывать файлы по папкам.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/BrowserView.js
Тут у тебя жестко прописаны id и селекторы элементов, и мы не можем поместить на 1 странице несколько BrowserView (привязанные к одной или разным моделям), жаль. Ну и ссылка на document жестко прописана.
Еще у тебя нет разделения на публичные/приватные методы и свойства. Из-за этого сложнее читать код, так как непонятно, какие методы внутренние, а какие выставлены наружу.
В JS нет приватных свойств, иногда просто имя начинают с подчеркивания, чтобы показать, что оно приватное.
Все должно работать нормально.
>>912553
https://github.com/enotocode/minesweeper.mvc
Я вижу, у тебя много js-файлов. Вручную вписывать их в код в нужном порядке становится утомительно, да и это неэффективно, грузить много мелких файлов через Интернет.
Можешь прочитать про системы модулей в JS: CommonJS и AMD. Затем - про сборщики и загрузкичи модулей вроде WebPack.
Советую настроить сборщик так, чтобы в режиме разработки не требовалась бы сборка и файлы бы грузились напрямую (это упрощает и ускоряет отладку), а в режиме релиза собирался бы единый JS файл. Некоторые делают так, что собрка используется всегда, но это по моему только все усложняет: нужны карта файлов и отладчик, который ее поддерживает, надо ждать, пока все соберется, и тд.
Кстати, ты используешь jsDoc, если ты используешь (или использовал бы) IDE, которая его поддерживает, это может улучшить качество автодополнения кода, и иногда IDE даже может проверять код на правильность за счет аннотаций. PhpStorm/WebStorm вроде это умеет.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/Cell.js
Тут surroundinMines наверно выгоднее не хранить, а сделать у Field метод для его расчета. Вообще, странно, у тебя у Клеточки есть своство "isMine", но оно в MinesweeperGame не проставляется. Очень странно.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js
тут наверно лучше не объявлять свойства вроде openCells публичными, а сделать методы для получения нужной информации из них.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js#L37
Здесь может получиться потенциально бесконечный цикл, такое следует избегать.
openCells, возможно, стоило сделать словарем для более эффективного поиска, вроде this.openCells[y][x] = true;
Наличие словаря избавит нас от необходимости писать сложные алгоритмы пересечения списков клеток.
В классе MinesweeperGame, возможно, лучше было бы сделать 2 словаря:
- cells[y][x] с объектами Cell с информацией о клеточке
- openedCells[y][x] с true/false, показывает, открыта ли клетка
- markedCells[y][x] true/false
Можно сделать даже один словарь, если сделать у ячейки свойства isOpened/isMarked, но тогда их можно случайно поменять, и MinesweeperGame об этом не узнает (и не сгенерирует событие). Но можно конечно договориться так не делать. Или можно сделать Cell тоже источником событий, и подписываться на событие открытия, но это конечно немного усложнит жизнь.
А у тебя этого нет, у тебя одна и та же клеточка может быть представлена 2 разными объектами в minedCells и openCells, это, мне кажется, нехорошо. Ведь тогда, если мы хотим что-то у клеточки поменять, мы должны найти все экземпляры и поменять нужное свойство. Лучше делать, что 1 сущности соответствует ровно 1 экземпляр объекта.
Вот если бы ты убрал из Cell свойство isMined, и сделал класс CellCoords с свойствами x/y, ты бы мог создавать их сколько угодно, потому что они представляют координаты ячейки, а не саму ячейку. А ячейка должна быть в одном экземпляре.
Может тебе задачку на ООП дать про отель, если ты эти вещи не очень понимаешь?
Вообще, когда нас модель сложная и содержит много объектов (например, для программы управления отелем будут классы Отель - Номер - Постоялец - Бронь - Счет за услуги), нам так или иначе надо отслеживать изменения в них, по всему дереву иерархии, и иметь поток информации об изменениях (вроде "Номер 302 поменял статус на Забронирован") И вариантов-то тут не так и много:
- либо в каждый класс добавлять события, генерировать их при изменениях, и класс-владелец подписывается на события в объектах, которые он хранит внутри
-либо мы должны использовать angular-подобную схему, когда мы сохраняем копию объектов, и после изменений сравниваем граф (набор) объектов с сохраненной ранее копией, находим разницу и генерируем события изменения. Это требует меньше кода, но работает медленнее и ест больше памяти.
- либо react-подобную, когда изменения нас не интересуют, так как мы каждый раз пересоздаем дерево DOM полностью. Но тут надо понимать, что события нам все равно могут понадобиться, например, для реализации функции undo
- либо использовать иммутабельные объекты (объекты, которые нельзя изменять). То есть при любом изменении на поле мы создаем новый экземпляр поля и новый экземпляр клеточки. Это требует очень много памяти, зато мы в любой момент можем сохранить текущий объект Поле в переменную, и знать что он и клеточки в нем никогда не изменятся. Удобно, если нам нужно иметь полную историю всех изменений в модели. Перемещение на 5 ходов назад получается очень простым, надо просто взять старый экземпляр Модели из истории и поставить как текущий.
(если у тебя будет время, можешь со всеми этими методами поэкспериментировать, или хотя бы подумать, как это реализовать).
В общем, для начала надо правильно реализовать Модель (классы, которые хранят состояние игры и клеточек). Где что хранится, как что можно узнать.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/MinesweeperGame.js#L311
> console.log('Watch out!');
> return false
Не очень понял смысл этих строчек. Может, надо исключение выбросить?
https://github.com/enotocode/minesweeper.mvc/blob/master/app/GameEvent.js
Здесь, ты может сам заметил, проблема в том, что у разных событий может быть разное число аргументов, с разными названиями. Получается, надо либо сделать разные классы событий, либо сделать один класс, а аргументы передавать словарем вроде { cell: someCell, action: SomeClass.ACTION_OPEN }
Использование нескольких классов требует больше кода, но позволяет ставить jsDoc, добавлять кастомные методы и свойства в события.
Ну например, для событий GAME_OVER target не очень-то и нужен, так как подписчик должен знать, на какой объект он подписывался.
Еще, наверно, стоило бы начать раскидывать файлы по папкам.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/BrowserView.js
Тут у тебя жестко прописаны id и селекторы элементов, и мы не можем поместить на 1 странице несколько BrowserView (привязанные к одной или разным моделям), жаль. Ну и ссылка на document жестко прописана.
Еще у тебя нет разделения на публичные/приватные методы и свойства. Из-за этого сложнее читать код, так как непонятно, какие методы внутренние, а какие выставлены наружу.
В JS нет приватных свойств, иногда просто имя начинают с подчеркивания, чтобы показать, что оно приватное.
Знание о том, как из координат получить id, размазано по 2 классам: BrowserView и ViewHelper. Плохо.
> tableContent += "<td id=x" + j + "y"+ i + "></td>";
Вот это знание в классе BrowserView. Там желательно его и держать, не вынося в другие классы.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/BrowserView.js#L158
> ViewHelper.addDelegateListener(table, 'TD', 'mousedown', function(target) {
> that.eventDispatcher.dispatchEvent( new GameEvent(GameEvent.UPDATE_CELL_STATUS, cell) );
Событие названо неправильно. Откуда View знает, что при клике по клетке она меняет статус? Это знает только модель. Событие должно быть названо BrowserView.EVENT_CELL_CLICK_LEFT или как-то так. Видишь, как смысл сразу меняется?
Я советую события сделать константами не на GameEvent, а на соотв. классах.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/ServiceApp.js
Это лучше было назвать init.js или initGame.js или bootstrap.js, как-то так. Это ведь не класс, чтобы его с большой буквы писать.
> var browserView = new BrowserView();
> browserView.attach(mswprGame);
А почему мы модель не передаем сразу в конструктор? Есть какая-то причина? Чтобы ее можно было заменить на другую? Это конечно иногда может быть полезно (если у нас 1 вью и несколько игр, между которыми можно переключаться), но я не уверен, что у тебя это работает.
Ну и насчет 2 независимых контроллеров (ModalController/BrowserViewController) - вот я не уверен, а не лучше ли сделать ModalController/ModalView подчиненными BrowserViewController, чтобы он им говорил, когда показать диалог, а не они сами решали? А то просто у тебя получается что диалог сам решает, когда ему открываться, как-то иерархия управления размывается, это как 2 директора на одном предприятии.
Ну и соответственно логику кода понимать сложнее, так как они вдвоем могут подписаться на одно событие и каждый по-своему обрабатывает его.
Вот допустим ConsoleController можно сделать независимым, так как он никак с BrowserViewController не пересекается. Но ModalView ведь перекрывает BrowserView на экране, они не независимы друг от друга.
Вот если бы статус игры выводился в отдельной независимой области экрана - тогда можно было бы сделать контроллер/вид независимыми. Но тут, мне кажется, они должны быть подчиненными.
BrowserViewController очень сильно привязан к BrowserView, оно в общем-то логично, но я поймал себя на мысли, что может быть, проще было его код во View и поместить? Хотя тогда конечно мы не можем прицепить к вью какой-то альтернативный контроллер (например такой. который позволяет только следить за игрой, но запрещает делать ходы или такой, который слушает нажатия клавиш). Хотя опять же, вот допустим, если мы присоединяем контроллер только для просмотра - а зачем тогда во вью мы подписываемся на клики по полю, если они никому не нужны? У меня конечно правильного ответа, как надо сделать, пока нет, только такие мысли.
А, еще неплохо бы твоего сапера выложить в сеть на беспллатные github pages ( https://pages.github.com/ ), надо лишь ветку gh-pages в твоем репозитории создать (хотя судя по https://help.github.com/articles/creating-project-pages-using-the-command-line/ это уже можно не делать).
https://github.com/enotocode/minesweeper.mvc/blob/master/app/ConsoleController.js#L58
Тут не проверяется, что вернет openCell(). При ошибке хорошо бы это и писать.
> ConsoleController.prototype.updateCellStatus = function(status, cell){
Проще наверно просто пересоздавать строку с нуля, а не мучаться с заменой отдельного символа. там все равно отрисовка этого поля потом больше времени займет.
А так тебе приходится держать копию данных модели и синхронизировать ее постоянно. Сложно и есть риск что-то забыть.
> @param {string} forGloryOfArgumentsGod - Number of mouse button (event.witch) on mousedown
Лучше было сделать отдельный метод setMouseButtonListener с этим аргументом.
https://github.com/enotocode/minesweeper.mvc/tree/master/templates/css
Это надо поместить в public.
Знание о том, как из координат получить id, размазано по 2 классам: BrowserView и ViewHelper. Плохо.
> tableContent += "<td id=x" + j + "y"+ i + "></td>";
Вот это знание в классе BrowserView. Там желательно его и держать, не вынося в другие классы.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/BrowserView.js#L158
> ViewHelper.addDelegateListener(table, 'TD', 'mousedown', function(target) {
> that.eventDispatcher.dispatchEvent( new GameEvent(GameEvent.UPDATE_CELL_STATUS, cell) );
Событие названо неправильно. Откуда View знает, что при клике по клетке она меняет статус? Это знает только модель. Событие должно быть названо BrowserView.EVENT_CELL_CLICK_LEFT или как-то так. Видишь, как смысл сразу меняется?
Я советую события сделать константами не на GameEvent, а на соотв. классах.
https://github.com/enotocode/minesweeper.mvc/blob/master/app/ServiceApp.js
Это лучше было назвать init.js или initGame.js или bootstrap.js, как-то так. Это ведь не класс, чтобы его с большой буквы писать.
> var browserView = new BrowserView();
> browserView.attach(mswprGame);
А почему мы модель не передаем сразу в конструктор? Есть какая-то причина? Чтобы ее можно было заменить на другую? Это конечно иногда может быть полезно (если у нас 1 вью и несколько игр, между которыми можно переключаться), но я не уверен, что у тебя это работает.
Ну и насчет 2 независимых контроллеров (ModalController/BrowserViewController) - вот я не уверен, а не лучше ли сделать ModalController/ModalView подчиненными BrowserViewController, чтобы он им говорил, когда показать диалог, а не они сами решали? А то просто у тебя получается что диалог сам решает, когда ему открываться, как-то иерархия управления размывается, это как 2 директора на одном предприятии.
Ну и соответственно логику кода понимать сложнее, так как они вдвоем могут подписаться на одно событие и каждый по-своему обрабатывает его.
Вот допустим ConsoleController можно сделать независимым, так как он никак с BrowserViewController не пересекается. Но ModalView ведь перекрывает BrowserView на экране, они не независимы друг от друга.
Вот если бы статус игры выводился в отдельной независимой области экрана - тогда можно было бы сделать контроллер/вид независимыми. Но тут, мне кажется, они должны быть подчиненными.
BrowserViewController очень сильно привязан к BrowserView, оно в общем-то логично, но я поймал себя на мысли, что может быть, проще было его код во View и поместить? Хотя тогда конечно мы не можем прицепить к вью какой-то альтернативный контроллер (например такой. который позволяет только следить за игрой, но запрещает делать ходы или такой, который слушает нажатия клавиш). Хотя опять же, вот допустим, если мы присоединяем контроллер только для просмотра - а зачем тогда во вью мы подписываемся на клики по полю, если они никому не нужны? У меня конечно правильного ответа, как надо сделать, пока нет, только такие мысли.
А, еще неплохо бы твоего сапера выложить в сеть на беспллатные github pages ( https://pages.github.com/ ), надо лишь ветку gh-pages в твоем репозитории создать (хотя судя по https://help.github.com/articles/creating-project-pages-using-the-command-line/ это уже можно не делать).
https://github.com/enotocode/minesweeper.mvc/blob/master/app/ConsoleController.js#L58
Тут не проверяется, что вернет openCell(). При ошибке хорошо бы это и писать.
> ConsoleController.prototype.updateCellStatus = function(status, cell){
Проще наверно просто пересоздавать строку с нуля, а не мучаться с заменой отдельного символа. там все равно отрисовка этого поля потом больше времени займет.
А так тебе приходится держать копию данных модели и синхронизировать ее постоянно. Сложно и есть риск что-то забыть.
> @param {string} forGloryOfArgumentsGod - Number of mouse button (event.witch) on mousedown
Лучше было сделать отдельный метод setMouseButtonListener с этим аргументом.
https://github.com/enotocode/minesweeper.mvc/tree/master/templates/css
Это надо поместить в public.
Твой код выдает у меня ошибку в Хромиуме:
> var regexp = new RegExp('^\\s|\\s$', 'iu');
> Uncaught SyntaxError: Invalid flags supplied to RegExp constructor 'iu'
Где ты тут видишь флаг u? https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp
В JS строк все внутри в Юникоде и указывать флаги с кодировкой, как в PHP, не требуется.
И консоль не работает, я пишу game.open('a3') , и мне выводится пустая строка. Я что-то включить забыл?
>В классе MinesweeperGame, возможно, лучше было бы сделать 2 словаря:
>- cells[y][x] с объектами Cell с информацией о клеточке
>- openedCells[y][x] с true/false, показывает, открыта ли клетка
>- markedCells[y][x] true/false
Если я воспользуюсь словарями, тогда мне не нужен объект Cell, и не нужно хранить экземпляры отдельно в cells[y][x]? Ведь координаты открытых и отмеченных клетках будут в словарях. Можно добавить третий словарь mines[x][y] и отказаться от класса Cell и от cells[y][x]? В этом случае можно использовать CellCoords, как принятый формат передачи координат?
>>914262
>А, еще неплохо бы твоего сапера выложить в сеть на беспллатные github pages
https://enotocode.github.io/minesweeper.mvc/
>Твой код выдает у меня ошибку в Хромиуме:
> var regexp = new RegExp('^\\s|\\s$', 'iu');
> Uncaught SyntaxError: Invalid flags supplied to RegExp constructor 'iu'
Я поправил. Почему-то мой Хромиум не ругался.
>И консоль не работает, я пишу game.open('a3') , и мне выводится пустая строка. Я что-то включить забыл?
Скажи пожалуйста версию своего хромиума?
Не могу понять, в чем может быть проблема. Скачал дистрибутив с гитхаба, открыл в опере/хроме/фф - game.open('a3') работает. Позже попробую на другой машине потестить.
>В классе MinesweeperGame, возможно, лучше было бы сделать 2 словаря:
>- cells[y][x] с объектами Cell с информацией о клеточке
>- openedCells[y][x] с true/false, показывает, открыта ли клетка
>- markedCells[y][x] true/false
Если я воспользуюсь словарями, тогда мне не нужен объект Cell, и не нужно хранить экземпляры отдельно в cells[y][x]? Ведь координаты открытых и отмеченных клетках будут в словарях. Можно добавить третий словарь mines[x][y] и отказаться от класса Cell и от cells[y][x]? В этом случае можно использовать CellCoords, как принятый формат передачи координат?
>>914262
>А, еще неплохо бы твоего сапера выложить в сеть на беспллатные github pages
https://enotocode.github.io/minesweeper.mvc/
>Твой код выдает у меня ошибку в Хромиуме:
> var regexp = new RegExp('^\\s|\\s$', 'iu');
> Uncaught SyntaxError: Invalid flags supplied to RegExp constructor 'iu'
Я поправил. Почему-то мой Хромиум не ругался.
>И консоль не работает, я пишу game.open('a3') , и мне выводится пустая строка. Я что-то включить забыл?
Скажи пожалуйста версию своего хромиума?
Не могу понять, в чем может быть проблема. Скачал дистрибутив с гитхаба, открыл в опере/хроме/фф - game.open('a3') работает. Позже попробую на другой машине потестить.
http://ideone.com/r4ExXQ
Почему тут ничего не выводится? Что не так?
кто-нибудь пользуется vs code в связке с пхп?
Что-то вроде:
ajax({
url:"update.php",
type:"POST",
async: true,
data:{},
success: function (data) {},
error: function (error1) {},
})
В php просто написан код и его результат выводится через echo.
Есть два вопроса:
1) "подгружают php файлы" - как-то совсем криво звучит. Как это называется правильно?
2) у меня используется несколько отдельных файлов, по одному на каждую кнопку. И в половине из них прописано подключение к MySQL базе. Параметры все одни и те же. Я правильно понимаю, что нужно сделать одну функцию, и каждый раз её писать? Но где именно её нужно писать? В отдельном файле и прописывать import в начале каждого php файла?
Вопросы явно взаимосвязаны. В общем, я не понимаю вот этого вот момента.
Господа, сириус квешн. На чем лучше писать такой сайт: цмс или фреймворк? И что именно выбрать из цмс или фреймворков?
$subject = "8-123-456-87-99";
$pattern = "/[0-9](11)/";
preg_match($pattern, $subject, $matches);
print_r($matches);
Потому что ничего не найдено. Твоя регулярка соответствует подстрокам 111 211 ... 911.
Мучаюсь над проектированием. Решил сделать класс Game с игровой логикой и Field связанный с dom. Пробую хранить объект Field в поле объекта Game. Не понятно у кого из них будут какие свойства (количество мин, длина, высота, значение времени) и что делать когда пользователь решил начать игру заново, создавать ли новый field или чистить его.
Даже не совсем понимаю кто какие методы заберет из старого кода или выбирая вариант с двумя классами - методы сильно поменяются?
> Решил сделать класс Game с игровой логикой и Field связанный с dom.
То,что связано с DOM - это View. Я тебе советую сначала целиком спроектировать модель, и только потом браться за View/Controller.
Тут я вижу 2 таких варианта:
- сделать логику игры в классе Game (хранение статуса, определение победы, раскрытие клеточек), и сделать класс-модель Field, хранящий только состояние поля (например, информацию о клеточках, минах, флажках)
- сделать и логику игры, и состояние поля в одном классе Game
Также, у нас есть разные варианты хранения информации о клеточках (есть ли там мина, стоит ли флажок и тд):
- хранить информацию в классе Game или Field
- хранить ее в объектах Cell, которые хранятся внутри Game/Field
То есть получается выбор из нескольких вариантов. И при выборе нам надо продумать, как мы будем собирать информацию об изменениях и как генерировать события.
>>914855
Начни только с модели, не думай пока, как это отображать на экране.
В Constraint доступа к Доктрине быть не должно, так как этот объект представляет собой Ограничение, и хранит относящуюся к нему информацию. Доступ к Доктрине может потребоваться в ConstraintValidator, который проверяет значение на соответствие Ограничению.
Вбиваем в Гугл: "symfony constraint validator dependency"
Находим: http://symfony.com/doc/current/validation/custom_constraint.html#constraint-validators-with-dependencies
> How to Create a custom Validation Constraint
> Constraint Validators with Dependencies
> If your constraint validator has dependencies, such as a database connection, it will need to be configured as a service in the Dependency Injection Container. This service must include the validator.constraint_validator tag so that the validation system knows about it:
> ...
То есть объявляется сервис в контейнере, указывается его имя класса в validatedBy(), по этому имени он потом ищется.
Поскольку наш ConstraintValidator создается где-то в недрах компонента Validator, то мы не можем так просто передать ему аргументы в конструктор. Потому мы должны вместо этого объявить его как сервис в DI container, чтобы Validator взял этот сервис из контейнера. Ну и очевидно, что в контейнере мы можем прописать любые правила инициализации нашего валидатора и аргументы конструктора.
Там также требуется пометить сервис валидатора специальным тегом. Я думаю, этот тег нужен не просто так, какой-то компонент Симфони ищет все сервисы-валидаторы по этому тегу и что-то с ними делает. Не люблю, когда остаются какие-то пробелы в понимании темы, потому сделаем поиск в коде Симфони по "constraint_validator":
Находим:
> class AddConstraintValidatorsPass implements CompilerPassInterface
> foreach ($container->findTaggedServiceIds('validator.constraint_validator') as $id => $attributes) {
> ...
> $container->getDefinition('validator.validator_factory')->replaceArgument(1, $validators);
Обрати внимание, что это FrameworkBundle - часть Симфони, а не часть независимого от Симфони компонента Validator. То есть работать это должно только в Симфони.
На первый взляд, ничего не понять. То есть понятно, что код ищет все сервисы-валидаторы по тегу, получает их имена классов (или алиасы), кладет в массив и сохраняет этот массив в контейнере как аргумент для создания сервиса validator.validator_factory. Выглядит как костыль какой-то. Непонятно:
1) зачем тут нужен какой-то CompilerPass?
2) зачем помечать сервисы-валидаторы тегом validator.constraint_validator? Без него что, нельзя получить сервис из контейнера по имени класса?
Начнем с первого вопроса. В Симфони ради оптимизации сделана так называемая "компиляция DI контейнера" (о контейнере: https://symfony.com/doc/current/components/dependency_injection.html , о компиляции: http://symfony.com/doc/current/components/dependency_injection/compilation.html ). Там сервисы в контейнер добавляются не в коде, а через конфиги (services.yml), и в каждом бандле (части фреймворка) может быть свой конфиг с объявлением сервисов. Чтобы при каждом запуске приложения (читай, при каждом HTTP-запросе от браузера) не читать и не анализировать кучу конфигов с определениями сервисов из всех бандлов (долго), Симфони "компилирует" собранную информацию в один php-файл и кладет его куда-то в папку cache (если у тебя есть DI container от Симфони, то поищи этот файл, он назвается как-то вроде devContainer.php и посмотри его содержимое). При втором запуске Симфони просто загружает файл с скомпилированным контейнером, не тратя время. Если используется opcache, это работает очень быстро, мы получаем все удобства DI контейнера с сложными конфигами и кучей сервисов без ущерба производительности.
Вот я нагуглил пример скомпилированного контейнера: https://searchcode.com/codesearch/view/71056161/
Это сгенерированный на основе конфигов код, и видно что получение/создание сервисов (методы вроде get_xxx) сделано очень минималистично и эффективно.
Бандлы могут вмешиваться в компиляцию и добавлять "CompilerPass" - класс, который что-то делает с контейнером перед компиляцией в файл. То есть тут используется CompilerPass, чтобы один раз составить список сервисов-валидаторов при компиляции, сохранить его в контейнере как аргумент для validator_factory и при последующих запусках не тратить на это время.
Возвращаясь к компоненту Validator и тегу. Напомню, что Симфони состоит из Компонентов, которые можно использовать независимо от самого фреймворка, и бандлов, которые являются частью фреймворка. Эта validator_factory, которая умеет искать сервис-валидатор в контейнере по id, который возвращает validatedBy() - это часть Симфони (FrameworkBundle). А что, если мы используем компонент Validator без Симфони? Как можно тогда передать зависимости в валидатор?
We need to go deeper.
Если мы откроем компонент Валидатор, то увидим там класс https://github.com/symfony/validator/blob/master/ConstraintValidatorFactory.php. Он отвечает за создание объекта Валидатора из Constraint по имени класса, которое вернет validatedBy(). И мы видим там, что код создания нового Валидатора там очень простой:
> new $className
Если мы используем Симфони, в ней в FrameworkBundle есть своя фабрика валидаторов: https://github.com/symfony/framework-bundle/blob/master/Validator/ConstraintValidatorFactory.php
Очевидно, Симфони передает эту фабрику в компонент Валидатора через DI, чтобы компонент Валидатора использовал ее вместо встроенной. Если изучить код, видно, что в эту фабрику через конструктор передается список имен классов и id сервисов валидаторов из контейнера (этот список конструирует AddConstraintValidatorsPass при компиляции контейнера). И соответственно эта симфониевская фабрика умеет искать сервисы в контейнере.
Вот мы и нашли ответ, зачем нужно ставить тег в контейнере: тег в контейнере нужен, чтобы фабрика знала, какие именно классы надо искать в контейнере, и под какими именами там зарегистрирован сервис.
Также, мы нашли ответ на вопрос, как сделать то же самое без Симфони: написать свою фабрику валидаторов, умеющую искать сервис в DI контейнере (или получающую валидаторы откуда-нибудь еще, например, через конструктор).
И наконец, мы нашли пример использования паттерна Фабрика. Фабрика нужна, чтобы переопределить способ создания валидатора в чужом коде.
Вообще, такие трюки в Симфони часто используются: в компоненте (который должен работать без Симфони) делается какая-то минимальная реализация, а уже в Симфони делается более мощная реализация, интегрированная с другими частями Симфони.
На мой взгляд конечно получилось немного сложновато. Это то, за что любят критиковать ООП-подход и паттерны. Но зато получилось очень гибко.
Если у тебя есть время, я бы советовал поковырять компоненты DependencyInjection, Validator, Form, посмотреть, как они устроены. Вместе с книгой Фаулера PoEAA это даст тебе понимание, как и зачем используются паттерны на практике, в отличие от дурацких статей вроде "3 самых простых паттерна для собеседования".
Ну и ты будешь лучше знать возможности и особенности этих компонент.
Ну и помни, что паттерны надо использовать только там, где это оправданно, а не вставлять везде "чтобы было".
> Только вот я уже пару дней читаю официальную документацию симфони и все мои потуги ни к чему не приводят.
При чтении обязательно различай Компоненты Симфони (независимые от фреймворка) и их использование вместе с фреймворком - фреймворк может расширять их возможности, как мы видели на примере выше.
В Constraint доступа к Доктрине быть не должно, так как этот объект представляет собой Ограничение, и хранит относящуюся к нему информацию. Доступ к Доктрине может потребоваться в ConstraintValidator, который проверяет значение на соответствие Ограничению.
Вбиваем в Гугл: "symfony constraint validator dependency"
Находим: http://symfony.com/doc/current/validation/custom_constraint.html#constraint-validators-with-dependencies
> How to Create a custom Validation Constraint
> Constraint Validators with Dependencies
> If your constraint validator has dependencies, such as a database connection, it will need to be configured as a service in the Dependency Injection Container. This service must include the validator.constraint_validator tag so that the validation system knows about it:
> ...
То есть объявляется сервис в контейнере, указывается его имя класса в validatedBy(), по этому имени он потом ищется.
Поскольку наш ConstraintValidator создается где-то в недрах компонента Validator, то мы не можем так просто передать ему аргументы в конструктор. Потому мы должны вместо этого объявить его как сервис в DI container, чтобы Validator взял этот сервис из контейнера. Ну и очевидно, что в контейнере мы можем прописать любые правила инициализации нашего валидатора и аргументы конструктора.
Там также требуется пометить сервис валидатора специальным тегом. Я думаю, этот тег нужен не просто так, какой-то компонент Симфони ищет все сервисы-валидаторы по этому тегу и что-то с ними делает. Не люблю, когда остаются какие-то пробелы в понимании темы, потому сделаем поиск в коде Симфони по "constraint_validator":
Находим:
> class AddConstraintValidatorsPass implements CompilerPassInterface
> foreach ($container->findTaggedServiceIds('validator.constraint_validator') as $id => $attributes) {
> ...
> $container->getDefinition('validator.validator_factory')->replaceArgument(1, $validators);
Обрати внимание, что это FrameworkBundle - часть Симфони, а не часть независимого от Симфони компонента Validator. То есть работать это должно только в Симфони.
На первый взляд, ничего не понять. То есть понятно, что код ищет все сервисы-валидаторы по тегу, получает их имена классов (или алиасы), кладет в массив и сохраняет этот массив в контейнере как аргумент для создания сервиса validator.validator_factory. Выглядит как костыль какой-то. Непонятно:
1) зачем тут нужен какой-то CompilerPass?
2) зачем помечать сервисы-валидаторы тегом validator.constraint_validator? Без него что, нельзя получить сервис из контейнера по имени класса?
Начнем с первого вопроса. В Симфони ради оптимизации сделана так называемая "компиляция DI контейнера" (о контейнере: https://symfony.com/doc/current/components/dependency_injection.html , о компиляции: http://symfony.com/doc/current/components/dependency_injection/compilation.html ). Там сервисы в контейнер добавляются не в коде, а через конфиги (services.yml), и в каждом бандле (части фреймворка) может быть свой конфиг с объявлением сервисов. Чтобы при каждом запуске приложения (читай, при каждом HTTP-запросе от браузера) не читать и не анализировать кучу конфигов с определениями сервисов из всех бандлов (долго), Симфони "компилирует" собранную информацию в один php-файл и кладет его куда-то в папку cache (если у тебя есть DI container от Симфони, то поищи этот файл, он назвается как-то вроде devContainer.php и посмотри его содержимое). При втором запуске Симфони просто загружает файл с скомпилированным контейнером, не тратя время. Если используется opcache, это работает очень быстро, мы получаем все удобства DI контейнера с сложными конфигами и кучей сервисов без ущерба производительности.
Вот я нагуглил пример скомпилированного контейнера: https://searchcode.com/codesearch/view/71056161/
Это сгенерированный на основе конфигов код, и видно что получение/создание сервисов (методы вроде get_xxx) сделано очень минималистично и эффективно.
Бандлы могут вмешиваться в компиляцию и добавлять "CompilerPass" - класс, который что-то делает с контейнером перед компиляцией в файл. То есть тут используется CompilerPass, чтобы один раз составить список сервисов-валидаторов при компиляции, сохранить его в контейнере как аргумент для validator_factory и при последующих запусках не тратить на это время.
Возвращаясь к компоненту Validator и тегу. Напомню, что Симфони состоит из Компонентов, которые можно использовать независимо от самого фреймворка, и бандлов, которые являются частью фреймворка. Эта validator_factory, которая умеет искать сервис-валидатор в контейнере по id, который возвращает validatedBy() - это часть Симфони (FrameworkBundle). А что, если мы используем компонент Validator без Симфони? Как можно тогда передать зависимости в валидатор?
We need to go deeper.
Если мы откроем компонент Валидатор, то увидим там класс https://github.com/symfony/validator/blob/master/ConstraintValidatorFactory.php. Он отвечает за создание объекта Валидатора из Constraint по имени класса, которое вернет validatedBy(). И мы видим там, что код создания нового Валидатора там очень простой:
> new $className
Если мы используем Симфони, в ней в FrameworkBundle есть своя фабрика валидаторов: https://github.com/symfony/framework-bundle/blob/master/Validator/ConstraintValidatorFactory.php
Очевидно, Симфони передает эту фабрику в компонент Валидатора через DI, чтобы компонент Валидатора использовал ее вместо встроенной. Если изучить код, видно, что в эту фабрику через конструктор передается список имен классов и id сервисов валидаторов из контейнера (этот список конструирует AddConstraintValidatorsPass при компиляции контейнера). И соответственно эта симфониевская фабрика умеет искать сервисы в контейнере.
Вот мы и нашли ответ, зачем нужно ставить тег в контейнере: тег в контейнере нужен, чтобы фабрика знала, какие именно классы надо искать в контейнере, и под какими именами там зарегистрирован сервис.
Также, мы нашли ответ на вопрос, как сделать то же самое без Симфони: написать свою фабрику валидаторов, умеющую искать сервис в DI контейнере (или получающую валидаторы откуда-нибудь еще, например, через конструктор).
И наконец, мы нашли пример использования паттерна Фабрика. Фабрика нужна, чтобы переопределить способ создания валидатора в чужом коде.
Вообще, такие трюки в Симфони часто используются: в компоненте (который должен работать без Симфони) делается какая-то минимальная реализация, а уже в Симфони делается более мощная реализация, интегрированная с другими частями Симфони.
На мой взгляд конечно получилось немного сложновато. Это то, за что любят критиковать ООП-подход и паттерны. Но зато получилось очень гибко.
Если у тебя есть время, я бы советовал поковырять компоненты DependencyInjection, Validator, Form, посмотреть, как они устроены. Вместе с книгой Фаулера PoEAA это даст тебе понимание, как и зачем используются паттерны на практике, в отличие от дурацких статей вроде "3 самых простых паттерна для собеседования".
Ну и ты будешь лучше знать возможности и особенности этих компонент.
Ну и помни, что паттерны надо использовать только там, где это оправданно, а не вставлять везде "чтобы было".
> Только вот я уже пару дней читаю официальную документацию симфони и все мои потуги ни к чему не приводят.
При чтении обязательно различай Компоненты Симфони (независимые от фреймворка) и их использование вместе с фреймворком - фреймворк может расширять их возможности, как мы видели на примере выше.
Твоя регулярка ищет строки, где идет 11 цифр подряд без символов между ними. Тебе надо написать другое:
- сначала напиши выражение "ровно 1 цифра и любое число скобок, минусов, пробелов за ней"
- затем возьми это выражение в скобки и допиши к ним "повторяется ровно 10 раз"
>>914464
> 1) "подгружают php файлы" - как-то совсем криво звучит. Как это называется правильно?
Это звучит неправильно, так как никакие php файлы с сервера в браузер не передаются. Передается результат их работы. Правильно сказать "JS-код отправляет AJAX запрос на сервер для получения HTML кода и вставки его в страницу (в дерево DOM страницы)". Заметь, что я не упомянул PHP, так как клиентский код не знает ничего о том, какой язык программирования используется в серверном коде - это нельзя узнать, не имея доступа к серверу.
> 2) у меня используется несколько отдельных файлов, по одному на каждую кнопку. И в половине из них прописано подключение к MySQL базе. Параметры все одни и те же. Я правильно понимаю, что нужно сделать одну функцию, и каждый раз её писать? Но где именно её нужно писать? В отдельном файле и прописывать import в начале каждого php файла?
Удобно сделать файл bootstrap.php, который инициализирует какие-то вещи, которые нужны всем скриптам. И подключить его в начале каждого из файлов. В нем может быть как просто код, так и объявлены полезные функции.
Ну и замечу, что если ты хочешь профессионально программировать на PHP, тебе надо изучать больше (ООП, MVC, фреймворки), например, используя учебник и уроки из ОП поста. Судя по твоему описанию, у тебя пока уровень изучения основ PHP.
> прописывать import
В PHP нет такого ключевого слова. require наверно имелся в виду.
Ну и дам еще статью про аякс, может пригодится: https://github.com/codedokode/pasta/blob/master/js/ajax.md
>>914317
А ты, я смотрю, оптимист ($year < 500). решено верно, хотя в echo для вывода переменной вместо запятой лучше просто подставлять переменную в строку.
>>914307
> Если я воспользуюсь словарями, тогда мне не нужен объект Cell,
Может быть и нужен. Удобно сделать объект, представляющий одну клеточку, и хранить в нем, открыта ли она, есть ли мина, есть ли флажок.
> Можно добавить третий словарь mines[x][y] и отказаться от класса Cell и от cells[y][x]? В этом случае можно использовать CellCoords, как принятый формат передачи координат?
Можно попробовать и так. Трудно пока сказать, в чем недостатки.
> Я поправил. Почему-то мой Хромиум не ругался.
У меня версия 46, ей может год или около того. Может в твоем браузере флаг u что-то значит, может в нем сделано игнорирование неизвестных флагов. В любом случае он там быть не должен.
Теперь все вроде работает, в том числе и из консоли. Впрочем, вот чего не хватает:
- координаты почему-то отсчитываются с 0, а не с 1
- в консоли не пишутся события в игре вроде "клеточка x открыта" или "игра проиграна", трудно понять, что именно изменилось
- если за раз открывается несколько клеточек, в консоль вываливается куча изображений поля
- при STATUS_LOSE можно ходить, при этом под полем появляется несколько диалогов
Твоя регулярка ищет строки, где идет 11 цифр подряд без символов между ними. Тебе надо написать другое:
- сначала напиши выражение "ровно 1 цифра и любое число скобок, минусов, пробелов за ней"
- затем возьми это выражение в скобки и допиши к ним "повторяется ровно 10 раз"
>>914464
> 1) "подгружают php файлы" - как-то совсем криво звучит. Как это называется правильно?
Это звучит неправильно, так как никакие php файлы с сервера в браузер не передаются. Передается результат их работы. Правильно сказать "JS-код отправляет AJAX запрос на сервер для получения HTML кода и вставки его в страницу (в дерево DOM страницы)". Заметь, что я не упомянул PHP, так как клиентский код не знает ничего о том, какой язык программирования используется в серверном коде - это нельзя узнать, не имея доступа к серверу.
> 2) у меня используется несколько отдельных файлов, по одному на каждую кнопку. И в половине из них прописано подключение к MySQL базе. Параметры все одни и те же. Я правильно понимаю, что нужно сделать одну функцию, и каждый раз её писать? Но где именно её нужно писать? В отдельном файле и прописывать import в начале каждого php файла?
Удобно сделать файл bootstrap.php, который инициализирует какие-то вещи, которые нужны всем скриптам. И подключить его в начале каждого из файлов. В нем может быть как просто код, так и объявлены полезные функции.
Ну и замечу, что если ты хочешь профессионально программировать на PHP, тебе надо изучать больше (ООП, MVC, фреймворки), например, используя учебник и уроки из ОП поста. Судя по твоему описанию, у тебя пока уровень изучения основ PHP.
> прописывать import
В PHP нет такого ключевого слова. require наверно имелся в виду.
Ну и дам еще статью про аякс, может пригодится: https://github.com/codedokode/pasta/blob/master/js/ajax.md
>>914317
А ты, я смотрю, оптимист ($year < 500). решено верно, хотя в echo для вывода переменной вместо запятой лучше просто подставлять переменную в строку.
>>914307
> Если я воспользуюсь словарями, тогда мне не нужен объект Cell,
Может быть и нужен. Удобно сделать объект, представляющий одну клеточку, и хранить в нем, открыта ли она, есть ли мина, есть ли флажок.
> Можно добавить третий словарь mines[x][y] и отказаться от класса Cell и от cells[y][x]? В этом случае можно использовать CellCoords, как принятый формат передачи координат?
Можно попробовать и так. Трудно пока сказать, в чем недостатки.
> Я поправил. Почему-то мой Хромиум не ругался.
У меня версия 46, ей может год или около того. Может в твоем браузере флаг u что-то значит, может в нем сделано игнорирование неизвестных флагов. В любом случае он там быть не должен.
Теперь все вроде работает, в том числе и из консоли. Впрочем, вот чего не хватает:
- координаты почему-то отсчитываются с 0, а не с 1
- в консоли не пишутся события в игре вроде "клеточка x открыта" или "игра проиграна", трудно понять, что именно изменилось
- если за раз открывается несколько клеточек, в консоль вываливается куча изображений поля
- при STATUS_LOSE можно ходить, при этом под полем появляется несколько диалогов
Спасибо, ОПушка. Теперь все понятно.
Первое очевидно нужно для того, чтобы хранить значение, а второе, чтобы обрабатывать.
В чем смысл первого выражения? Зачем объявлять функцию какими-то окольными путями, когда в языке есть стандартный способ?
>>915097
А если я описываю классы и это конструкторы?
>>915120
В примерах документации мозиллы объявлено так:
https://developer.mozilla.org/ru/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Прототипное_программирование
Задание дают прежде всего тебе, чтобы ты понял, готов ли ты по знаниям и опыту или нет. Если сам не осиливаешь - значит рано. Задачу можешь вбросить конечно.
Если бы тебе было что-то не очевидно, ты бы уже спросил об этом
Так, если тебе не понятно даже задание, то мы тебе ни чем не сможем помочь
задание понятно. а как решить непонятно
>>915231
Я уже кое-что накидал через array_walk_recursive, но там то ключи не меняются, то на выходе получается одномерный массив.
Задание:
Написать функцию, которая будет принимать 3 параметра
1) Любой вложенности массив (array) = $datas
2) Что ищем (string) = $key
3) На что заменяем (string) = $value
На выходе мы должны получить массив $datas в котором заменены все элементы (ключи, значения) $key на $value.
Будет + если решите задачу без использования циклов.
Например:
$datas = [
'aasd' => 'bbsr',
'aas' => [
'zzc' => 'ffts'
]
];
$key = 's';
$value = '<SUPER>';
$datas = str_replace_array($datas, $key, $value);
echo '<pre>';
print_r($datas);
<<< Результат:
(array) [
'aa<SUPER>d' => 'bb<SUPER>r',
'aa<SUPER>' => [
'zzc' => 'fft<SUPER>'
]
];
задание понятно. а как решить непонятно
>>915231
Я уже кое-что накидал через array_walk_recursive, но там то ключи не меняются, то на выходе получается одномерный массив.
Задание:
Написать функцию, которая будет принимать 3 параметра
1) Любой вложенности массив (array) = $datas
2) Что ищем (string) = $key
3) На что заменяем (string) = $value
На выходе мы должны получить массив $datas в котором заменены все элементы (ключи, значения) $key на $value.
Будет + если решите задачу без использования циклов.
Например:
$datas = [
'aasd' => 'bbsr',
'aas' => [
'zzc' => 'ffts'
]
];
$key = 's';
$value = '<SUPER>';
$datas = str_replace_array($datas, $key, $value);
echo '<pre>';
print_r($datas);
<<< Результат:
(array) [
'aa<SUPER>d' => 'bb<SUPER>r',
'aa<SUPER>' => [
'zzc' => 'fft<SUPER>'
]
];
щас буду решать с использованием циклов. без циклов не получилось. :c
Все, уже решил через цикл foreach ^^
Алсо а что не перекатываетесь?
Подсказка: Положи символы, которые ты берешь в 17 строке для сравнения в переменные и выведи их по парно. Станет видно почему не работает.
правильно ли я понял смысл той задачи: вычислить, сколько в каком банке ежемесячный платеж? И правильно ли я решил всё?
>Нихуя не понимаю, как включить --enable-mbstring в PHP
Его нельзя "включить". То что ты написал - дополнение к конфигуратору при самостоятельной компиляции интерпретатора. Узнать как именно конфигурировалась среда перед компиляцией твоего интерпретатора можно из окна информации (php -i в консоли или .php файл с вызовом функции phpinfo()). В большинстве источников интерпретатор скомпилирован с поддержкой mbstring, все что тебе нужно это установить пакет phpVER-mbstring (где VER - версия интерпретатора).
В версиях для виндоус большинство дополнений идут вместе с бинарниками (смотри папку ext в корневой директории интерпретатора), тогда нужно указать в php.ini путь до дополнения, которое хочешь подключить. Поищи в своем php.ini по слову extension и убери комментарий в строке с нужным дополнением.
>а что не перекатываетесь?
ОП делает перекаты когда отвечает на большинство постов в треде.
Я тут заданьица поделал. С назывателем чисел прописью очень долго мучался. Даже забивал на несколько недель, но дожал всё таки.
Клавиша Шифт - http://ideone.com/emLdBx
Йода - http://ideone.com/98uaEk
Сумма прописью - http://ideone.com/MnfgLD
Народ, в задаче про банки и айпэд нет ответов правильных, сравнить не с чем. Глньте, пожалуйста, тут все верно?
Последние версии битры почти как фреймворки из уровня 2005 года. Вполне сносно
><% ... %>, <%= ... %>, <script language="php"> ... </script>
><%= ... %>
То есть в седьмом ПХП нужно писать вручную в шаблонах <? echo $foo ?> ?
вместо <?= $foo ?>
Разобрался, это я дебил.
>>890782 | https://2ch.hk/pr/res/880700.html#890782 (М)
https://github.com/someApprentice/Students/
> https://github.com/someApprentice/Students/blob/master/students.sql#L31
Вот тут у колонки стоит COLLATE utf8_bin. COLLATE задает правила сортировки и сравнения строк и utf8_bin значит, что строки сравниваются с учетом регистра. То есть поск например потребует ввода имен в правильном регистре. Я думаю, по умолчанию лучше бы использовать utf8_unicode_ci, которая нечувтствительна к регистру.
Тут (англ) есть сложные пояснения: http://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html
Тут немного на русском: http://gahcep.github.io/blog/2013/01/05/mysql-utf8/
https://github.com/someApprentice/Students/blob/master/app/init.php#L10
> use App\Model\Helper\LoginHelper;
> use App\Model\Helper\LoginHelper as Authorizer;
Тут зачем-то класс 2 раза импортирован
https://github.com/someApprentice/Students/blob/master/app/Controller/Controller.php
Вот тут конечно методы вроде getPageQuery или getSortQuery явно не очень правильно смотрятся. Ведь это базовый контроллер, и в него надо класть только то, что может пригодиться в любом контроллере, а это явно функции для вывода таблицы постранично. Возможно, стоило их поместить в унаследованный класс BaseTableController, возможно - вынести в Helper. В принципе, это исправлять не надо, но в дальнейшем надо задумываться, куда лучше поместить метод.
https://github.com/someApprentice/Students/blob/master/app/Controller/IndexAction.php
Вот тут что-то странное, 2 вызова render. Мы сначала выводим страницу 'templates/index.phtml', а затем под ней зачем-то выводим 'templates/list.phtml'. Это как минимум странно выглдяит. Ведь мы уже вывели подвал страницы, и после него пытаемся вывести таблицу, которая явно не под подвалом должна быть.
Действия Search и просто вывод таблицы стоило бы объединить. Это ведь почти одно и то же, и там и там есть и сортировка, и пагинация, проще использовать один код и в нужных местах просто поставить пару if.
https://github.com/someApprentice/Students/blob/master/app/Controller/SearchAction.php#L19
> if ($_GET) {
Я бы не советовал так проверять, так как в теории в GET могут быть какие-то неотносящиеся к поиску параметры (их могут добавить позже). Лучше проверять, например, что $_GET['query'] не пуст.
> $this->render('templates/search.phtml', compact('query', 'pager'));
> } else {
> $this->render('templates/search.phtml');
Вот это тоже странно. Один и тот же шаблон может вызываться как с переменными, так и без. Как писать надежный код, если ты даже не знаешь, передана такая переменная или нет? Да и неудобно, перед любым использованием переменной надо писать if (isset()). Не стоит так делать, тут стоит оставить единственный вызов render, в который передаются все нужные переменные.
Теперь посмотрим на использование класса Pager. Это класс, в котором много неудачных решений:
> $pager = new Pager(compact('query', 'correntPage', 'sort', 'by'));
> $limit = $pager->getLimit();
> $offset = $pager->getOffset();
> $pager->setRecords($records);
> $pager->setRecordsCount($recordsCount);
Во-первых, бросается в глаза, что почему-то все аргументы не передаются в конструктор сразу. 2 аргумента, ($records, $recordsCount) задаются уже после создания. Получается, чтобы получить работоспособный объект, нам надо не только создать его, но и вызвать еще 2 метода, и как об этом догадаться, если мы видим класс впервые? Надо внимательно изучать код, тратить время.
В принципе, аргументы можно задавать через методы вида setSomething(), но обычно так делают только с необязательными аргументами, класс будет работать корректно и без их указания.
Но для пагинации число записей - это ключевой параметр, так как именно на его основе считается число страниц. Странно, что он не задается в конструкторе.
Во-вторых, почему-то для передачи аргументов в конструктор используется массив. Причем вместе идут как параметры, которые не зависят от номера страницы (query, sort), так и номер текущей страницы. Тоже нелогично, мне кажется, что номер страницы должен идти отдельно.
Опять же, если мы хотим использовать класс, то неочевидно, что за массив надо ему передать, и что в нем должно быть.
Как мы помним, лучше всего, когда у каждого класса своя зона ответственности, одна какая-то задача. Какие задачи решает класс Pager?
- предоставляет информацию о текущем и общем числе страниц
- генерирует ссылку на страницу с указанным номером
- генерирует ссылку и стрелочку для сортировки
- хранит список выбранных записей ($records)
Что касается "хранит список выбранных записей", это явно лишнее. Это выглядит как не его задача. Непонятно, зачем было это в него засовывать. Так в него можно засунуть например и getCurrentUser().
Теоретически конечно можно переименовать его в класс TableHelper, экземпляр которого соответствует выводимой на странице таблице, и потому он знает и параметры сортровки, и хрант список записей, и номер страницы. Но тут-то он называется Pager.
Также, есть такая задача как "генерирует ссылку на нужную страницу". В принципе, это имеет отношение к пагинации, но ведь это тоже отдельная задача. Вот представим, нам где-то в другом месте кода надо получить ссылку на таблицу с определенной сортировкой (например, ссылку для заголовка таблицы). Как ее получить? Через класс Pager, но для его использования нам надо указать, сколько всего в базе записей. Хотя мы всего лишь хотели сгенерировать ссылку, и для этого знать число записей не нужно. Это показывает, что возможно, генерация ссылок - отдельная задача, которую стоит вынести из класса Pager.
Как можно вынести генерацию ссылок, чтобы Pager ей не занимался? Есть много способов:
- сгенерировать ссылку снаружи и передавать в класс шаблон ссылки вида /index.php?sort=name&page={page}, в который подставляется номер страницы
- передавать в класс анонимную функцию-генератор ссылок вида function ($page) { ... }, которая должна по номеру страницы вернуть ссылку
- просто сделать отдельный метод в другом классе, и вызывать его из Pager
- передать в Pager объект-генератор ссылок
Все эти вещи, конечно, увеличат объем кода. Но зато будет универсальный класс, который можно подключить на любую страницу. В простом приложении, возможно, лучше не заморачиваться и просто сделать все в одном классе.
Ну и наконец, название выбрано неудачно. Можно подумать по названию, что это универсальный класс для любых страниц, но на самом деле он работает только с таблицей студентов. Раз так, надо назвать его StudentPager или как-то так.
В общем, я бы советовал подумать над исправлением этого класса. Сильно усложнять все наверно не стоит, но чуть-чуть исправить недостатки желательно. Возможно, стоит просто переделать его из класса пагинации в класс, помогающий выводить таблицу. Тогда стоит подумать, а можно ли сделать этот класс универсальнее, чтобы он помогал выводить любые таблицы с сортировкой и пагинацией.
>>890782 | https://2ch.hk/pr/res/880700.html#890782 (М)
https://github.com/someApprentice/Students/
> https://github.com/someApprentice/Students/blob/master/students.sql#L31
Вот тут у колонки стоит COLLATE utf8_bin. COLLATE задает правила сортировки и сравнения строк и utf8_bin значит, что строки сравниваются с учетом регистра. То есть поск например потребует ввода имен в правильном регистре. Я думаю, по умолчанию лучше бы использовать utf8_unicode_ci, которая нечувтствительна к регистру.
Тут (англ) есть сложные пояснения: http://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html
Тут немного на русском: http://gahcep.github.io/blog/2013/01/05/mysql-utf8/
https://github.com/someApprentice/Students/blob/master/app/init.php#L10
> use App\Model\Helper\LoginHelper;
> use App\Model\Helper\LoginHelper as Authorizer;
Тут зачем-то класс 2 раза импортирован
https://github.com/someApprentice/Students/blob/master/app/Controller/Controller.php
Вот тут конечно методы вроде getPageQuery или getSortQuery явно не очень правильно смотрятся. Ведь это базовый контроллер, и в него надо класть только то, что может пригодиться в любом контроллере, а это явно функции для вывода таблицы постранично. Возможно, стоило их поместить в унаследованный класс BaseTableController, возможно - вынести в Helper. В принципе, это исправлять не надо, но в дальнейшем надо задумываться, куда лучше поместить метод.
https://github.com/someApprentice/Students/blob/master/app/Controller/IndexAction.php
Вот тут что-то странное, 2 вызова render. Мы сначала выводим страницу 'templates/index.phtml', а затем под ней зачем-то выводим 'templates/list.phtml'. Это как минимум странно выглдяит. Ведь мы уже вывели подвал страницы, и после него пытаемся вывести таблицу, которая явно не под подвалом должна быть.
Действия Search и просто вывод таблицы стоило бы объединить. Это ведь почти одно и то же, и там и там есть и сортировка, и пагинация, проще использовать один код и в нужных местах просто поставить пару if.
https://github.com/someApprentice/Students/blob/master/app/Controller/SearchAction.php#L19
> if ($_GET) {
Я бы не советовал так проверять, так как в теории в GET могут быть какие-то неотносящиеся к поиску параметры (их могут добавить позже). Лучше проверять, например, что $_GET['query'] не пуст.
> $this->render('templates/search.phtml', compact('query', 'pager'));
> } else {
> $this->render('templates/search.phtml');
Вот это тоже странно. Один и тот же шаблон может вызываться как с переменными, так и без. Как писать надежный код, если ты даже не знаешь, передана такая переменная или нет? Да и неудобно, перед любым использованием переменной надо писать if (isset()). Не стоит так делать, тут стоит оставить единственный вызов render, в который передаются все нужные переменные.
Теперь посмотрим на использование класса Pager. Это класс, в котором много неудачных решений:
> $pager = new Pager(compact('query', 'correntPage', 'sort', 'by'));
> $limit = $pager->getLimit();
> $offset = $pager->getOffset();
> $pager->setRecords($records);
> $pager->setRecordsCount($recordsCount);
Во-первых, бросается в глаза, что почему-то все аргументы не передаются в конструктор сразу. 2 аргумента, ($records, $recordsCount) задаются уже после создания. Получается, чтобы получить работоспособный объект, нам надо не только создать его, но и вызвать еще 2 метода, и как об этом догадаться, если мы видим класс впервые? Надо внимательно изучать код, тратить время.
В принципе, аргументы можно задавать через методы вида setSomething(), но обычно так делают только с необязательными аргументами, класс будет работать корректно и без их указания.
Но для пагинации число записей - это ключевой параметр, так как именно на его основе считается число страниц. Странно, что он не задается в конструкторе.
Во-вторых, почему-то для передачи аргументов в конструктор используется массив. Причем вместе идут как параметры, которые не зависят от номера страницы (query, sort), так и номер текущей страницы. Тоже нелогично, мне кажется, что номер страницы должен идти отдельно.
Опять же, если мы хотим использовать класс, то неочевидно, что за массив надо ему передать, и что в нем должно быть.
Как мы помним, лучше всего, когда у каждого класса своя зона ответственности, одна какая-то задача. Какие задачи решает класс Pager?
- предоставляет информацию о текущем и общем числе страниц
- генерирует ссылку на страницу с указанным номером
- генерирует ссылку и стрелочку для сортировки
- хранит список выбранных записей ($records)
Что касается "хранит список выбранных записей", это явно лишнее. Это выглядит как не его задача. Непонятно, зачем было это в него засовывать. Так в него можно засунуть например и getCurrentUser().
Теоретически конечно можно переименовать его в класс TableHelper, экземпляр которого соответствует выводимой на странице таблице, и потому он знает и параметры сортровки, и хрант список записей, и номер страницы. Но тут-то он называется Pager.
Также, есть такая задача как "генерирует ссылку на нужную страницу". В принципе, это имеет отношение к пагинации, но ведь это тоже отдельная задача. Вот представим, нам где-то в другом месте кода надо получить ссылку на таблицу с определенной сортировкой (например, ссылку для заголовка таблицы). Как ее получить? Через класс Pager, но для его использования нам надо указать, сколько всего в базе записей. Хотя мы всего лишь хотели сгенерировать ссылку, и для этого знать число записей не нужно. Это показывает, что возможно, генерация ссылок - отдельная задача, которую стоит вынести из класса Pager.
Как можно вынести генерацию ссылок, чтобы Pager ей не занимался? Есть много способов:
- сгенерировать ссылку снаружи и передавать в класс шаблон ссылки вида /index.php?sort=name&page={page}, в который подставляется номер страницы
- передавать в класс анонимную функцию-генератор ссылок вида function ($page) { ... }, которая должна по номеру страницы вернуть ссылку
- просто сделать отдельный метод в другом классе, и вызывать его из Pager
- передать в Pager объект-генератор ссылок
Все эти вещи, конечно, увеличат объем кода. Но зато будет универсальный класс, который можно подключить на любую страницу. В простом приложении, возможно, лучше не заморачиваться и просто сделать все в одном классе.
Ну и наконец, название выбрано неудачно. Можно подумать по названию, что это универсальный класс для любых страниц, но на самом деле он работает только с таблицей студентов. Раз так, надо назвать его StudentPager или как-то так.
В общем, я бы советовал подумать над исправлением этого класса. Сильно усложнять все наверно не стоит, но чуть-чуть исправить недостатки желательно. Возможно, стоит просто переделать его из класса пагинации в класс, помогающий выводить таблицу. Тогда стоит подумать, а можно ли сделать этот класс универсальнее, чтобы он помогал выводить любые таблицы с сортировкой и пагинацией.
>>890782 | https://2ch.hk/pr/res/880700.html#890782 (М)
https://github.com/someApprentice/Students/blob/master/templates/index.phtml#L10
> <a href="logout.php?token=<?= $token ?>&go=/public/index.php">logout?</a>
LogOut лучше бы делать отправкой POST-запроса (например, пустой формы с одной кнопкой). Это ведь изменяет состояние залогиненности, а GET запросы обычно не меняют состояние сервера.
Далее, я бы хотел обратить внимание на LoginHelper и посмотреть, хорошо ли в нем соблюдаются принципы ООП. Видимо это класс, отвечающий за регистрацию и сопутствующие действия (логин, регистрация, проверка залогиненности, разлогинивание). В соответствие с принципом инкапсуляции было бы хорошо убрать все знание о том, как технически реализуется залогинивание/разлогиивание, в этот класс. Я тут вижу даже 2 варианта:
- класс может отвечать только за залогинивание/разлогинивание, то есть работу с куками, и не обращается к БД со списком студентов
- класс обращается к БД, и кроме залогинивания/разлогинивания, умеет еще искать текущего пользователя и регистрировать пользователей, может еще менять им пароль
В принципе, второй вариант позволяет спрятать в этот класс подробности того, как генерируется и сохраняется пароль. Тогда, если мы захотим поменять алгоритм хеширования, нам достаточно будет подправить только этот класс, не трогая остальной код. Или если мы захотим разобраться в алгоритме, нам достаточно будет посмотреть на этот класс.
Тут я вижу небольшие нарушения принципа инкапсуляции:
- наружу выставлены методы generateSalt/hashPassword, хотя лучше бы все, что относится к работе с паролями, спрятать внутри класса
- в классе Student есть метод setPassword(), который знает, что для сохранения пароля надо сгенерировать соль и захешировать пароль. Лучше бы убрать этот метод и использовать для смены пароля LoginHelper::setPassword().
В общем-то инкапсуляция есть, но немного знания о том, как ставить пароль, все же вылезло из класса.
Могу предложить посмотреть вот такую реализацию класса авторизации: https://github.com/kubk/students/blob/master/src/AuthService.php
К нему даже написан тест, который проверяет например, что если вызват метод залогинивания, получить куки и скормить их этому классу, то мы получим нужного пользователя: https://github.com/kubk/students/blob/master/tests/AuthServiceTest.php
Далее, тут https://github.com/someApprentice/Students/blob/master/app/Controller/RegisterAction.php#L58 мы не завершаем контроллер после редиректа.
https://github.com/someApprentice/Students/blob/master/app/Model/Validators/StudentValidations.php
Константы вроде const GENDER_MALE = "Man"; логичнее поместить в студента. Это ведь его свойства, а не свойства валидатора. В валидатор можно поместить константы вроде VALIDATE_MIN_LENGTH, относящиеся к валидации. Представь, что нам не нужна валидация и мы удалим этот класс. И вместе с ним удалятся константы, обозначающие пол, хотя они-то нам еще нужны.
https://github.com/someApprentice/Students/blob/master/app/Model/Validators/RegisterStudentFormValidations.php
А интересно, почему здесь StudentValidations передается как зависимость, а не наследуется? По идее ведь RegisterFormValiadtions мог бы расширять StudentValidations, или это неудобно?
И тут получается похожий код:
- https://github.com/someApprentice/Students/blob/master/app/Model/Validators/RegisterStudentFormValidations.php#L32
- https://github.com/someApprentice/Students/blob/master/app/Model/Validators/StudentValidations.php#L58
Может стоит сделать метод validateByRules($entity, $rules, $errorList) в базовом классе?
Хотя, конечно, не очень понятно, как тогда там вставить код, меняющий правила проверки пароля:
> if ($field = 'password' and $editMode) {
Возможно, надо сделать правила более гибкими, чтобы мы могли бы как-то в правилах добавить флаг "это поле может быть пустым" или через правила передать дополнительный параметр в функцю-валидатор.
https://github.com/someApprentice/Students/tree/master/app/Controller
Тут стоит удалить пустые ненужные файлы.
> <a href="?<?= $pager->getLinkForPage(1) ?>"
Тут хорошо бы экранировать спецсимволы, так как в ссылке может встретиться &, а это спецсимвол в HTML.
В общем, решение меня устраивает, если не считать класса Pager. Надо его хотя бы немного переделать.
Ну и дальше стоит начинать изучать фреймворки, хотя бы микрофреймворки для начала, так как тут мы писали все с нуля, и на практике конечно эффективнее учиться использовать готовый код. Также потом стоит глянуть шаблонизатор twig и, может быть, библиотеку-data mapper для работы с БД под названием Doctrine.
>>890782 | https://2ch.hk/pr/res/880700.html#890782 (М)
https://github.com/someApprentice/Students/blob/master/templates/index.phtml#L10
> <a href="logout.php?token=<?= $token ?>&go=/public/index.php">logout?</a>
LogOut лучше бы делать отправкой POST-запроса (например, пустой формы с одной кнопкой). Это ведь изменяет состояние залогиненности, а GET запросы обычно не меняют состояние сервера.
Далее, я бы хотел обратить внимание на LoginHelper и посмотреть, хорошо ли в нем соблюдаются принципы ООП. Видимо это класс, отвечающий за регистрацию и сопутствующие действия (логин, регистрация, проверка залогиненности, разлогинивание). В соответствие с принципом инкапсуляции было бы хорошо убрать все знание о том, как технически реализуется залогинивание/разлогиивание, в этот класс. Я тут вижу даже 2 варианта:
- класс может отвечать только за залогинивание/разлогинивание, то есть работу с куками, и не обращается к БД со списком студентов
- класс обращается к БД, и кроме залогинивания/разлогинивания, умеет еще искать текущего пользователя и регистрировать пользователей, может еще менять им пароль
В принципе, второй вариант позволяет спрятать в этот класс подробности того, как генерируется и сохраняется пароль. Тогда, если мы захотим поменять алгоритм хеширования, нам достаточно будет подправить только этот класс, не трогая остальной код. Или если мы захотим разобраться в алгоритме, нам достаточно будет посмотреть на этот класс.
Тут я вижу небольшие нарушения принципа инкапсуляции:
- наружу выставлены методы generateSalt/hashPassword, хотя лучше бы все, что относится к работе с паролями, спрятать внутри класса
- в классе Student есть метод setPassword(), который знает, что для сохранения пароля надо сгенерировать соль и захешировать пароль. Лучше бы убрать этот метод и использовать для смены пароля LoginHelper::setPassword().
В общем-то инкапсуляция есть, но немного знания о том, как ставить пароль, все же вылезло из класса.
Могу предложить посмотреть вот такую реализацию класса авторизации: https://github.com/kubk/students/blob/master/src/AuthService.php
К нему даже написан тест, который проверяет например, что если вызват метод залогинивания, получить куки и скормить их этому классу, то мы получим нужного пользователя: https://github.com/kubk/students/blob/master/tests/AuthServiceTest.php
Далее, тут https://github.com/someApprentice/Students/blob/master/app/Controller/RegisterAction.php#L58 мы не завершаем контроллер после редиректа.
https://github.com/someApprentice/Students/blob/master/app/Model/Validators/StudentValidations.php
Константы вроде const GENDER_MALE = "Man"; логичнее поместить в студента. Это ведь его свойства, а не свойства валидатора. В валидатор можно поместить константы вроде VALIDATE_MIN_LENGTH, относящиеся к валидации. Представь, что нам не нужна валидация и мы удалим этот класс. И вместе с ним удалятся константы, обозначающие пол, хотя они-то нам еще нужны.
https://github.com/someApprentice/Students/blob/master/app/Model/Validators/RegisterStudentFormValidations.php
А интересно, почему здесь StudentValidations передается как зависимость, а не наследуется? По идее ведь RegisterFormValiadtions мог бы расширять StudentValidations, или это неудобно?
И тут получается похожий код:
- https://github.com/someApprentice/Students/blob/master/app/Model/Validators/RegisterStudentFormValidations.php#L32
- https://github.com/someApprentice/Students/blob/master/app/Model/Validators/StudentValidations.php#L58
Может стоит сделать метод validateByRules($entity, $rules, $errorList) в базовом классе?
Хотя, конечно, не очень понятно, как тогда там вставить код, меняющий правила проверки пароля:
> if ($field = 'password' and $editMode) {
Возможно, надо сделать правила более гибкими, чтобы мы могли бы как-то в правилах добавить флаг "это поле может быть пустым" или через правила передать дополнительный параметр в функцю-валидатор.
https://github.com/someApprentice/Students/tree/master/app/Controller
Тут стоит удалить пустые ненужные файлы.
> <a href="?<?= $pager->getLinkForPage(1) ?>"
Тут хорошо бы экранировать спецсимволы, так как в ссылке может встретиться &, а это спецсимвол в HTML.
В общем, решение меня устраивает, если не считать класса Pager. Надо его хотя бы немного переделать.
Ну и дальше стоит начинать изучать фреймворки, хотя бы микрофреймворки для начала, так как тут мы писали все с нуля, и на практике конечно эффективнее учиться использовать готовый код. Также потом стоит глянуть шаблонизатор twig и, может быть, библиотеку-data mapper для работы с БД под названием Doctrine.
>>915935
Не надо, <?= всегда работает начиная с PHP5.4 по моему.
>>915820
Как это не с чем? Там в каком-то банке получается по моему 61270 или около того. Судя по ответу 61529 в твоей задаче, у тебя примерно на 300 рублей ошибка.
Попробуй поставить сумму кредита 1000 или 4000 и посмотри, что получится. Должно быть 2030 и примерно 6123 рубля соответственно.
Я думаю, проблема в том, что когда там в последний месяц остается мелочь вроде 500р, ты платишь $rest + $finalMonthly, и 500 рублей входят в каждую из переменных. То есть, платишь лишнее.
Также, oneTimePayment надо прибавлять до вычисления процентов, а не после.
Как вам книга Никсона "Создаем динамические сайты"? А то я очень долго дрочил питон и на выхлопе сделал пару недосайтов на джанго, очень уродливые и хелоувордные, очень хочется работать на результат.
На нагруженный проект обычно берут сеньора с опытом подобных проектов и не на удаленку, а в офис. На удаленке как раз проекты не такие сложные обычно, уровня написать плагин к вордпресу или новую фичу в админку cms.
Анончики, не могу понять, почему если я вывожу в цикле каждое $value, то оно показывается именно правильным, со всеми правками. А когда вывожу весь массив $textarray после цикла, то выводится старый, как если бы я ничего не делал с $value.
>>916540
Ты - это я. Я даже удивился такому сходству. Просто точь-в-точь и про питон и про гит, и вопросами задаюсь такими же.
Книга Никсона дает самые азы, пригодится только если например не знаешь как вообще подойти к задачке про студентов от ОПа. Результат она тебе не даст.
Допилил сам, поставив вначале цикла не $value, а &$value. И вот только после этого заработало как надо.
было:
// girls code
if($name_trening != null && $name_country != null && $name_city !=null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND city=? AND date=? ", $name_trening, $name_country, $name_city, $name_date);
}
elseif($name_trening == null && $name_city !=null && $name_date !=null && $name_country != null){
$query = $this->db->placehold("SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND city=? AND date=?", $name_country, $name_city, $name_date);
}
elseif($name_country == null && $name_trening != null && $name_city != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND city=? AND date=? ", $name_trening, $name_city, $name_date);
}
elseif($name_city == null && $name_country != null && $name_date != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND date=? ", $name_trening, $name_country, $name_date);
}
elseif($name_date == null && $name_city != null && $name_country!= null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND city=?", $name_trening, $name_country, $name_city);
}
elseif($name_trening == null && $name_country == null && $name_date == null && $name_city != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=?", $name_city);
}
elseif($name_trening == null && $name_country == null && $name_city == null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE date=?", $name_date);
}
elseif($name_date == null && $name_country == null && $name_city == null && $name_trening !=null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=?", $name_trening);
}
elseif($name_date == null && $name_trening == null && $name_city == null && $name_country != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=?", $name_country);
}
elseif($name_date == null && $name_trening == null && $name_country!= null && $name_city !=null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND city = ?", $name_country, $name_city);
}
elseif($name_date == null && $name_city == null && $name_country != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND name = ?", $name_country, $name_trening);
}
elseif($name_date == null && $name_country == null && $name_city != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=? AND name = ?", $name_city, $name_trening);
}
elseif($name_city == null && $name_trening == null && $name_country != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND date = ?", $name_country, $name_date);
}
elseif($name_country == null && $name_trening == null && $name_city != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=? AND date = ?", $name_city, $name_date);
}
elseif($name_country == null && $name_city == null && $name_trening != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND date = ?", $name_trening, $name_date);
}
else {
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
");
}
$this->db->query($query);
if($this->db->query($query))
return $this->db->results();
else
return false;
стало:
$data = '';
if($name_trening != null){
$data .= $this->db->placehold(' AND name=? ', $name_trening);
}
if($name_country != null){
$data .= $this->db->placehold(' AND instr_name=? ', $name_country);
}
if($name_city != null){
$data .= $this->db->placehold(' AND city=? ', $name_city);
}
if($name_date != null){
$data .= $this->db->placehold(' AND date=? ', $name_date);
}
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE 1 $data");
было:
// girls code
if($name_trening != null && $name_country != null && $name_city !=null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND city=? AND date=? ", $name_trening, $name_country, $name_city, $name_date);
}
elseif($name_trening == null && $name_city !=null && $name_date !=null && $name_country != null){
$query = $this->db->placehold("SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND city=? AND date=?", $name_country, $name_city, $name_date);
}
elseif($name_country == null && $name_trening != null && $name_city != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND city=? AND date=? ", $name_trening, $name_city, $name_date);
}
elseif($name_city == null && $name_country != null && $name_date != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND date=? ", $name_trening, $name_country, $name_date);
}
elseif($name_date == null && $name_city != null && $name_country!= null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND instr_name=? AND city=?", $name_trening, $name_country, $name_city);
}
elseif($name_trening == null && $name_country == null && $name_date == null && $name_city != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=?", $name_city);
}
elseif($name_trening == null && $name_country == null && $name_city == null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE date=?", $name_date);
}
elseif($name_date == null && $name_country == null && $name_city == null && $name_trening !=null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=?", $name_trening);
}
elseif($name_date == null && $name_trening == null && $name_city == null && $name_country != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=?", $name_country);
}
elseif($name_date == null && $name_trening == null && $name_country!= null && $name_city !=null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND city = ?", $name_country, $name_city);
}
elseif($name_date == null && $name_city == null && $name_country != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND name = ?", $name_country, $name_trening);
}
elseif($name_date == null && $name_country == null && $name_city != null && $name_trening != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=? AND name = ?", $name_city, $name_trening);
}
elseif($name_city == null && $name_trening == null && $name_country != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE instr_name=? AND date = ?", $name_country, $name_date);
}
elseif($name_country == null && $name_trening == null && $name_city != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE city=? AND date = ?", $name_city, $name_date);
}
elseif($name_country == null && $name_city == null && $name_trening != null && $name_date != null){
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE name=? AND date = ?", $name_trening, $name_date);
}
else {
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
");
}
$this->db->query($query);
if($this->db->query($query))
return $this->db->results();
else
return false;
стало:
$data = '';
if($name_trening != null){
$data .= $this->db->placehold(' AND name=? ', $name_trening);
}
if($name_country != null){
$data .= $this->db->placehold(' AND instr_name=? ', $name_country);
}
if($name_city != null){
$data .= $this->db->placehold(' AND city=? ', $name_city);
}
if($name_date != null){
$data .= $this->db->placehold(' AND date=? ', $name_date);
}
$query = $this->db->placehold("
SELECT id_trening, new, online, time, discount, instr_name, price, name, url, date, country, city, mesto FROM __timetable
WHERE 1 $data");
http://ideone.com/s1v9fr
http://ideone.com/Iz40zU
Ты как-то странно идешь. Попробуй разбивать строку на массив с разделителем в виде символов, а потом уже делай какие-то действия.
Ну, скажем так. Есть такое негласное правило - если что-то можно сделать без регулярок, то лучше делать без регулярок. Но на самом деле очень удобный инструмент если хорошо освоить.
А в примере пишут preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, PREG_OFFSET_CAPTURE);? Что квадратные скобки не нужны, эта функция возвращает массив? Что обозначает preg?
Зачем такие дурацкие сокращения, дико раздражает после JS?
Спасибо, но я решил уже по своему.
Прочти этот раздел в мануале:
> Как читать определения функции (прототип)
Там объясняется, что значат квадратные скобки и string.
> Что квадратные скобки не нужны
Они обозначают необязательные аргументы, которые можно не указывать.
> эта функция возвращает массив?
Перед определением функции написано int, то есть возвращает целое число. Подробнее ты можешь прочесть в мануале по функции:
> Возвращаемые значения
> preg_match() возвращает 1, если параметр pattern соответствует переданному параметру subject, 0 если нет, или FALSE в случае ошибки.
Число обозначает число найденных сопадений текста с регуляркой, и так как она ищет только первое совпадение, то число может быть либо 0, либо 1. False возвращается при ошибке, например, если указан флаг u, но переданная строка не является корректной строкой в кодировке utf-8 (подробнее про кодировки в моем уроке https://github.com/codedokode/pasta/blob/master/cs/strings.md )
> Что обозначает preg?
Это префикс от расширения (PCRE), в которое входит функция. Думаю, что оно значит Pcre REGexp, где PCRE значит "perl-compatible regelar expressions", то есть "регулярные выражения, совместимые с теми, что используются в языке Perl". Раньше в PHP были еще ereg-функции, Extended posix REGexp, "расширенные регулярные выражения из стандарта POSIX".
Да, у регулярных выражений есть несколько диалектов, с небольшими отличиями.
Если ты посмотришь, то там есть еще другие функции с префиксом preg, например preg_split.
Об этом написано в мануале на заглавной странице расширения PCRE: http://php.net/manual/ru/intro.pcre.php
То есть это не разработчики PHP сами написали свой движок регулярных выражений, они взяли готовую библиотеку PCRE на языке Си и сделали возможность вызывать ее функции из PHP.
> Зачем такие дурацкие сокращения, дико раздражает после JS?
Подозреваю, это пришло из языка Си, где обычно функцию назывют как модуль_функция().
Прочти этот раздел в мануале:
> Как читать определения функции (прототип)
Там объясняется, что значат квадратные скобки и string.
> Что квадратные скобки не нужны
Они обозначают необязательные аргументы, которые можно не указывать.
> эта функция возвращает массив?
Перед определением функции написано int, то есть возвращает целое число. Подробнее ты можешь прочесть в мануале по функции:
> Возвращаемые значения
> preg_match() возвращает 1, если параметр pattern соответствует переданному параметру subject, 0 если нет, или FALSE в случае ошибки.
Число обозначает число найденных сопадений текста с регуляркой, и так как она ищет только первое совпадение, то число может быть либо 0, либо 1. False возвращается при ошибке, например, если указан флаг u, но переданная строка не является корректной строкой в кодировке utf-8 (подробнее про кодировки в моем уроке https://github.com/codedokode/pasta/blob/master/cs/strings.md )
> Что обозначает preg?
Это префикс от расширения (PCRE), в которое входит функция. Думаю, что оно значит Pcre REGexp, где PCRE значит "perl-compatible regelar expressions", то есть "регулярные выражения, совместимые с теми, что используются в языке Perl". Раньше в PHP были еще ereg-функции, Extended posix REGexp, "расширенные регулярные выражения из стандарта POSIX".
Да, у регулярных выражений есть несколько диалектов, с небольшими отличиями.
Если ты посмотришь, то там есть еще другие функции с префиксом preg, например preg_split.
Об этом написано в мануале на заглавной странице расширения PCRE: http://php.net/manual/ru/intro.pcre.php
То есть это не разработчики PHP сами написали свой движок регулярных выражений, они взяли готовую библиотеку PCRE на языке Си и сделали возможность вызывать ее функции из PHP.
> Зачем такие дурацкие сокращения, дико раздражает после JS?
Подозреваю, это пришло из языка Си, где обычно функцию назывют как модуль_функция().
Она и есть простая функция, дополнительные опции просто немного влияют на ее работу. Например, позволяют искать совпадение не с начала строки, или позволяют в результаты добавлять отступ в байтах от начала строки, где было найдено совпадение.
>>918903
Первый раз слышу про такое правило. Регулярки как раз позволяют сделать поиск или замену текста одной строчкой, а без них пришлось бы писать сложный цикл.
>>918883
Да, много где используются, их в идеале надо понимать как русский язык, не задумываясь. Если что-то непонятно, задавай вопросы.
>>918766
Ширина и высота поля относится к модели. Ну представь, что ты допустим уберешь View вообще, просто программно вызываешь методы модели из своего кода. И что, игра сможет работать, не зная ширину и высоту поля? Вряд ли.
Вся игровая логика находится в модели. Контроллеры отвечают за выполнение пользовательских команд (за вызов нужных функций модели по команде пользователя), а вью - за отображение данных из модели.
>>918697
> if (is_numeric($char) && $op == '') {
> $result = (($result 10) + intval($char));
> } elseif (is_numeric($char)) {
> $number = (($number 10) + intval($char));
Вот это нехорошо, что ты для первого числа пишешь отдельную ветку кода. Лучше бы обрабатывать все аргументы одинаково. Например, всегда класть текущее число в $number, и при обнаружении математического знака, и $op == '' переносить из $number в $result.
Также, вот тут вот можно было чуть упростить код:
> elseif ($char == '*' || $char == '+' || $char == '-') {
> elseif ($char == '=') {
Я бы сделал примерно так:
Если (текущий символ - математический знак), то {
- если $op пустой, то копируем $number в $result
- если $op не пустой, выполняем записанную в нем операцию
- затем копируем текущий знак в $op
- затем, если текущий знак - это равно, то выводим результат
}
Для отладки программы я бы советовал натыкать там echo, чтобы видеть, какие значения куда записываются.
Она и есть простая функция, дополнительные опции просто немного влияют на ее работу. Например, позволяют искать совпадение не с начала строки, или позволяют в результаты добавлять отступ в байтах от начала строки, где было найдено совпадение.
>>918903
Первый раз слышу про такое правило. Регулярки как раз позволяют сделать поиск или замену текста одной строчкой, а без них пришлось бы писать сложный цикл.
>>918883
Да, много где используются, их в идеале надо понимать как русский язык, не задумываясь. Если что-то непонятно, задавай вопросы.
>>918766
Ширина и высота поля относится к модели. Ну представь, что ты допустим уберешь View вообще, просто программно вызываешь методы модели из своего кода. И что, игра сможет работать, не зная ширину и высоту поля? Вряд ли.
Вся игровая логика находится в модели. Контроллеры отвечают за выполнение пользовательских команд (за вызов нужных функций модели по команде пользователя), а вью - за отображение данных из модели.
>>918697
> if (is_numeric($char) && $op == '') {
> $result = (($result 10) + intval($char));
> } elseif (is_numeric($char)) {
> $number = (($number 10) + intval($char));
Вот это нехорошо, что ты для первого числа пишешь отдельную ветку кода. Лучше бы обрабатывать все аргументы одинаково. Например, всегда класть текущее число в $number, и при обнаружении математического знака, и $op == '' переносить из $number в $result.
Также, вот тут вот можно было чуть упростить код:
> elseif ($char == '*' || $char == '+' || $char == '-') {
> elseif ($char == '=') {
Я бы сделал примерно так:
Если (текущий символ - математический знак), то {
- если $op пустой, то копируем $number в $result
- если $op не пустой, выполняем записанную в нем операцию
- затем копируем текущий знак в $op
- затем, если текущий знак - это равно, то выводим результат
}
Для отладки программы я бы советовал натыкать там echo, чтобы видеть, какие значения куда записываются.
> if ($number % 100 >= 11 && $number % 100 <= 14) {
> $wordForm = $word3;
Тут лучше было сразу писать return $word3;
> $lastThree = round(($number / 1000 - floor($number / 1000)) * 1000);
> settype($lastThree, "integer");
Тут надо было написать $number % 1000. Вместо settype лушче использовать intval, хотя я не понимаю, чем float тут плох.
> $text .= smallNumberToText($parts[2], 0) . " " . $million . " ";
Тут наверно части фразы было удобнее класть в массив и позже склеивать.
> if ($parts[1] % 10 == 1 || $parts[1] % 10 == 2) {
> $text .= smallNumberToText($parts[1], 1) . " " . $thousand . " ";
> }
> else {
> $text .= smallNumberToText($parts[1], 0) . " " . $thousand . " ";
Тут не нужен этот if, так как проверка на последнюю цифру уже есть в функции smallNumberToText(). Число 0/1 обозначает род, и слово "тысяча" всегда женского рода.
В общем, сделано хорошо и аккуратно.
>>918003
Цикл не всегда подойдет, если поля разных типов, то там может потребоваться как-то по-разному их обрабатывать.
>>917993
Я тебе рассказывал уже, что в такой ситауции еще можно использовать паттерн Query Builder?
Ну и название функции placehold - неудачное.
>>916752
$value - это не элемент массива, а копия его значения. Ты менял только копию, а не сам элемент в массиве.
&$value приводит к тому, что в $value помещается ссылка на знаечние элемента и изменение $value меняет сам элемент в массиве. Имей в виду, что тут есть подвохи (например, после окончания цикла в $value сохраняется ссылка на последний элемент массива и можно нечаянно что-то туда записать). чтобы избежать ошибки, рекомендуется после окончния цикла уничтожить переменную $value через unset($value).
> $first = mb_substr($text, 0, 1);
> $up = mb_strtoupper($first);
> $other = mb_substr($text, 1);
Тут лучше было использовать поменьше промежуточных переменных. Ну например, вместо $up можно было внось использовать $first.
> foreach ($textarray as &$value) {
лучше было назвать переменные как $sentences и $sentence.
> if ($number % 100 >= 11 && $number % 100 <= 14) {
> $wordForm = $word3;
Тут лучше было сразу писать return $word3;
> $lastThree = round(($number / 1000 - floor($number / 1000)) * 1000);
> settype($lastThree, "integer");
Тут надо было написать $number % 1000. Вместо settype лушче использовать intval, хотя я не понимаю, чем float тут плох.
> $text .= smallNumberToText($parts[2], 0) . " " . $million . " ";
Тут наверно части фразы было удобнее класть в массив и позже склеивать.
> if ($parts[1] % 10 == 1 || $parts[1] % 10 == 2) {
> $text .= smallNumberToText($parts[1], 1) . " " . $thousand . " ";
> }
> else {
> $text .= smallNumberToText($parts[1], 0) . " " . $thousand . " ";
Тут не нужен этот if, так как проверка на последнюю цифру уже есть в функции smallNumberToText(). Число 0/1 обозначает род, и слово "тысяча" всегда женского рода.
В общем, сделано хорошо и аккуратно.
>>918003
Цикл не всегда подойдет, если поля разных типов, то там может потребоваться как-то по-разному их обрабатывать.
>>917993
Я тебе рассказывал уже, что в такой ситауции еще можно использовать паттерн Query Builder?
Ну и название функции placehold - неудачное.
>>916752
$value - это не элемент массива, а копия его значения. Ты менял только копию, а не сам элемент в массиве.
&$value приводит к тому, что в $value помещается ссылка на знаечние элемента и изменение $value меняет сам элемент в массиве. Имей в виду, что тут есть подвохи (например, после окончания цикла в $value сохраняется ссылка на последний элемент массива и можно нечаянно что-то туда записать). чтобы избежать ошибки, рекомендуется после окончния цикла уничтожить переменную $value через unset($value).
> $first = mb_substr($text, 0, 1);
> $up = mb_strtoupper($first);
> $other = mb_substr($text, 1);
Тут лучше было использовать поменьше промежуточных переменных. Ну например, вместо $up можно было внось использовать $first.
> foreach ($textarray as &$value) {
лучше было назвать переменные как $sentences и $sentence.
Ну да, работодатель предпочитает людей, которые умеют разрабатывать веб-сайты, и которые могут с первого дня включиться в работу. Если ты это еще не умеешь, то тебе стоит либо учиться дальше (и наш тред предлагает для этого различные задания), либо искать работу в какой-нибудь веб-студии, где ищут стажера-передвигальщика-меню.
Ну и надо понимать, что удаленная работа - это по многим пунктам выгоднее, чем обычная (свободнее график, не тратишь время на транспорт, меньше контроля, может быть более высокая зарплата), и соответственно отбор туда может быть строже.
> Как вам книга Никсона "Создаем динамические сайты"?
Код там плохой и некачественный, и по моему, с уязвимостями. Почитать ты можешь, но не пиши код как в книге.
> и на выхлопе сделал пару недосайтов на джанго, очень уродливые и хелоувордные, очень хочется работать на результат.
Тебе надо как минимум пройти учебник из ОП-поста (или аналогичный), и сделать задачи про студентов и файлообменник (или аналогичные). Ну и HTML/CSS/JS. Если ты знаешь Джанго и ООП, то это не должно представлять для тебя большой сложности.
>>916491
Видимо, нет.
>>915748
Клавиша Шифт - http://ideone.com/emLdBx
> $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
> $chars[0] = mb_strtoupper($chars[0]);
Тут наверно проще было отрезать первый символ через mb_substr, но твой вариант тоже годится.
> $match = preg_replace('/(^\s+)/u', '', $match);
Для отрезания пробелов есть ltrim, rtrim и trim.
> preg_replace('/\s([,.;:!?])(?![?.])\s/ui',
Чтобы поддерживать многоточия, можно еще использовать такое решение: [,.;:!?]+
Еще у тебя там ошибка (внизу выводится), обращение к пока не существующей переменной, ее надо исправить: PHP Notice: Undefined variable: result in /home/VnUvhY/prog.php on line 27
Йода - http://ideone.com/98uaEk
Ок, тут все верно.
Сумма прописью - http://ideone.com/MnfgLD
> if($number == 0) {
> return $spelling[$number];
> }
> else {
Тут else можно было и не писать, так как там выше стоит return, выходящий из функции.
> $millions = (integer)($number / 1000000);
Округлять лучше бы через floor(), она специально для этого предназначена.
> if($millions == 0) {
> }else{
> $words[] = $millionsWords;
> }
Пустой блок в if выглядит странно, лучше было написать if ($millions > 0)
В выводимых фразах надо было еще указать сумму числом. Так, в общем, хорошо сделано.
Ну да, работодатель предпочитает людей, которые умеют разрабатывать веб-сайты, и которые могут с первого дня включиться в работу. Если ты это еще не умеешь, то тебе стоит либо учиться дальше (и наш тред предлагает для этого различные задания), либо искать работу в какой-нибудь веб-студии, где ищут стажера-передвигальщика-меню.
Ну и надо понимать, что удаленная работа - это по многим пунктам выгоднее, чем обычная (свободнее график, не тратишь время на транспорт, меньше контроля, может быть более высокая зарплата), и соответственно отбор туда может быть строже.
> Как вам книга Никсона "Создаем динамические сайты"?
Код там плохой и некачественный, и по моему, с уязвимостями. Почитать ты можешь, но не пиши код как в книге.
> и на выхлопе сделал пару недосайтов на джанго, очень уродливые и хелоувордные, очень хочется работать на результат.
Тебе надо как минимум пройти учебник из ОП-поста (или аналогичный), и сделать задачи про студентов и файлообменник (или аналогичные). Ну и HTML/CSS/JS. Если ты знаешь Джанго и ООП, то это не должно представлять для тебя большой сложности.
>>916491
Видимо, нет.
>>915748
Клавиша Шифт - http://ideone.com/emLdBx
> $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
> $chars[0] = mb_strtoupper($chars[0]);
Тут наверно проще было отрезать первый символ через mb_substr, но твой вариант тоже годится.
> $match = preg_replace('/(^\s+)/u', '', $match);
Для отрезания пробелов есть ltrim, rtrim и trim.
> preg_replace('/\s([,.;:!?])(?![?.])\s/ui',
Чтобы поддерживать многоточия, можно еще использовать такое решение: [,.;:!?]+
Еще у тебя там ошибка (внизу выводится), обращение к пока не существующей переменной, ее надо исправить: PHP Notice: Undefined variable: result in /home/VnUvhY/prog.php on line 27
Йода - http://ideone.com/98uaEk
Ок, тут все верно.
Сумма прописью - http://ideone.com/MnfgLD
> if($number == 0) {
> return $spelling[$number];
> }
> else {
Тут else можно было и не писать, так как там выше стоит return, выходящий из функции.
> $millions = (integer)($number / 1000000);
Округлять лучше бы через floor(), она специально для этого предназначена.
> if($millions == 0) {
> }else{
> $words[] = $millionsWords;
> }
Пустой блок в if выглядит странно, лучше было написать if ($millions > 0)
В выводимых фразах надо было еще указать сумму числом. Так, в общем, хорошо сделано.
Не, не верно. Надо посчитать общую сумму выплат за все время в каждом банке. То есть во сколько реально обойдется кредит.
Месячный платеж тут одинаков - анон платит 5000 или меньше, если это остаток долга в последний месяц.
>>915489
Это нужно, если ты собираешь и копилируешь PHP из исходников. В этом случае ты вначале запускаешь конфигурационный скрипт, и ему указываешь этот параметр.
Если ты скачал уже собранный PHP под Windows, то включить расширение можно 2 способами:
- если это расширение шло в комплекте с PHP (mbstring идет), надо просто в файле конфигурации php.ini его включить через опцию extension=...
- если это расширение не идет в комплекте, надо найти его собранную версию (обычно это dll-файл) на windows.php.net, выбрать совместимую с твоим PHP версию, скачать, поместить в папку ext внутри папки PHP, и включить в php.ini
Чуть подробнее:
- http://php.net/manual/ru/install.pecl.windows.php
- http://php.net/manual/ru/install.pecl.php
Если ты используешь не Windows, а linux, то там обычно такие варианты:
- установить скомпилированное расширение с помощью пакетного менеджера
- скачать исходники и скомпилировать расширение, как описано в мануале
Увидеть список установленных включенных расширений можно так:
- выполнить php-файл с функцией <?php phpinfo();
- выполнить команду вроде php -m в командной строке
>>915492
Вроде сейчас программа работает правильно.
Не, не верно. Надо посчитать общую сумму выплат за все время в каждом банке. То есть во сколько реально обойдется кредит.
Месячный платеж тут одинаков - анон платит 5000 или меньше, если это остаток долга в последний месяц.
>>915489
Это нужно, если ты собираешь и копилируешь PHP из исходников. В этом случае ты вначале запускаешь конфигурационный скрипт, и ему указываешь этот параметр.
Если ты скачал уже собранный PHP под Windows, то включить расширение можно 2 способами:
- если это расширение шло в комплекте с PHP (mbstring идет), надо просто в файле конфигурации php.ini его включить через опцию extension=...
- если это расширение не идет в комплекте, надо найти его собранную версию (обычно это dll-файл) на windows.php.net, выбрать совместимую с твоим PHP версию, скачать, поместить в папку ext внутри папки PHP, и включить в php.ini
Чуть подробнее:
- http://php.net/manual/ru/install.pecl.windows.php
- http://php.net/manual/ru/install.pecl.php
Если ты используешь не Windows, а linux, то там обычно такие варианты:
- установить скомпилированное расширение с помощью пакетного менеджера
- скачать исходники и скомпилировать расширение, как описано в мануале
Увидеть список установленных включенных расширений можно так:
- выполнить php-файл с функцией <?php phpinfo();
- выполнить команду вроде php -m в командной строке
>>915492
Вроде сейчас программа работает правильно.
> HTMLElement.prototype.getElementsByClassName = getElementsByClassName;
Я тебе советую не трогать прототипы встроенных объектов и не подменять браузерные функции. Вместо этого лучше сделать свою функцию и вызвать ее. А она уже может проверять наличие встроенной getElementsByClassName() вызывать ее либо предоставлять альтернативное решение.
К тому же твоя функция не соответствует встроенной в браузер (у нее другие аругменты: https://developer.mozilla.org/ru/docs/Web/API/Element/getElementsByClassName ), и будет ошибка, если какая-то другая библиотека попытается ее вызвать.
Это же вообще неправильно. Твой код сломает работу стороннего кода.
Я советую никогда не пытаться подменить стандартные/браузерные функции.
Дальше ответ тут из-за спам-листа: http://pastebin.ru/a1sx1IT2
> Будет + если решите задачу без использования циклов.
Тебе надо изучить список стандартных функций для работы с массивами и попробовать найти подходщие.
А так, решается циклом + рекурсией.
А вообще, задание немного наркоманское. Я не могу представить, где было бы оправданно делать такие замены в массиве неизвестной структуры.
>>915163
> А если я описываю классы и это конструкторы?
А конструктор - это разве не функция? Я не вижу смысла писать как-то нестандартно, когда есть стандартный и более короткий способ.
> В примерах документации мозиллы объявлено так:
Скорее всего автору кода просто так больше нравится писать. В JS нет единого стиля оформления кода и потому каждый пишет, как он хочет. Я видел и более странные формы вроде
const func = () => { ... };
Просто кто-то любит новые ключевые слова и стрелочки. Хотя читать тяжеловато.
Вот тут например, в англоязычной статье, используется просто function: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS#A_simple_example
>>915097
Да, но это такой тонкий аспект, что я бы не советовал его использовать. Я например не помню наизусть все правила, относящиеся к области видимости функций и переменных. Как и правила подстановки точки с запятой в конце строки.
> Будет + если решите задачу без использования циклов.
Тебе надо изучить список стандартных функций для работы с массивами и попробовать найти подходщие.
А так, решается циклом + рекурсией.
А вообще, задание немного наркоманское. Я не могу представить, где было бы оправданно делать такие замены в массиве неизвестной структуры.
>>915163
> А если я описываю классы и это конструкторы?
А конструктор - это разве не функция? Я не вижу смысла писать как-то нестандартно, когда есть стандартный и более короткий способ.
> В примерах документации мозиллы объявлено так:
Скорее всего автору кода просто так больше нравится писать. В JS нет единого стиля оформления кода и потому каждый пишет, как он хочет. Я видел и более странные формы вроде
const func = () => { ... };
Просто кто-то любит новые ключевые слова и стрелочки. Хотя читать тяжеловато.
Вот тут например, в англоязычной статье, используется просто function: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS#A_simple_example
>>915097
Да, но это такой тонкий аспект, что я бы не советовал его использовать. Я например не помню наизусть все правила, относящиеся к области видимости функций и переменных. Как и правила подстановки точки с запятой в конце строки.
>>915019
>> Если одновременно выбраны А и один из [K,L,M], то стоимость выбранного продукта уменьшается на 5%
> "Выбранный продукт" это же один из K, L, M? Сделал именно так.
Думаю, да, хотя там правила местами туманно сформулированы.
Классы, представляющие собой виды скидок, стоило бы объединить - либо через наследование от абстрактного класса, либо через реализацию интерфейса.
А так тебе пришлось сделать 2 метода для добавления скидок: addCountDiscount(), addCombinationDiscount(). Это не позволяет нам добавлять новые виды скидок, не меняя класс Calculator. И получается калькулятор у тебя знает слишком много о скидках (какие виды скидок бывают), лучше бы он просто принимал объекты-скидки, не вникая в их устройство. Тут нарушается принцип единой ответственности - у каждого объекта своя зона ответственности и это не дело Калькулятора, по какому именно принципу определяется скидка. Это ответственность объекта-скидки.
Давай попробуем придумать, какой может быть интерфейс у объекта-скидки? Спрячу под спойлер, если ты сначала хочешь подумать сам:
- скидка должна получать список товаров, чтобы искать в нем те, к которым можно примениться
- скидка должна также знать об использованных в предыдущих скидках товарах (чтобы соблюдать правило несуммирования скидок, при этом решение о несуммировании остается за объектом-скидкой. Мне кажется, это гибче, чем удалять товары из коллекции, хотя с другой стороны, может привести к копипасте кода по всем классам скидок)
Это понятно, и это простые вещи. А вот что должна вернуть функция применения скидки? Процент? Сумму скидки в рублях? Не, лучше возвращать подробную информацию, а именно:
- к каким конкретно экземплярам (не названиям!) товаров она была применена (заметим, там есть скидки, применяющиеся к нескольким товарам, и к общей сумме заказа)
- чему равна скидка, вроде тут везде использованы проценты
Тогда например Калькулятор мог бы логгировать подробно процесс применения скидок. И в чеке мы бы могли перечислить примененные скидки.
Эту информацию можно возвращать как-то массивом, объектом, а можно даже хранить в самой скидке, но тогда у нас получится одноразовый калькулятор, который надо пересоздавать перед использованием.
Плохо, что у тебя методы вроде getSumAfterApplyingDiscount() возвращают сразу цену. Мне кажется, они берут на себя задачу Калькулятора, и зоны ответственности Калькулятора и Скидки у тебя не разграничены, они лезут в задачи друг друга.
В общем, я бы советовал начинать проектирование именно с Калькулятора и цикла применения скидок, расчета цены, кто за что отвечает, а потом уже под это затачивать остальные классы.
Что касается CountCriteria - я не уверен, что его надо было делать, для меня выглядит как ненужное усложнение. Лучше было бы просто переупорядочить правила. И вообще, если внимательно почитать задание, то там условие сформулировано очень неудачно. Это можно понимать так:
1) "если пользователь выбрал ровно 3 продукта, не использованных в других скидках, то ..." - тут как-то нелогично получается, что в задании нет слов "не использованных в других скидках".
2) "если пользователь выбрал ровно 3 продукта, ..." - тогда, если пользователь выбрал более 5 продуктов, скидка не применяется, что нелогично.
3) "если ни одна из предыдущих скидок не применена, и пользователь выбрал 3 или более продуктов, то применить одну из этих 3 скидок в зависимости от количества" - вот это с моей точки зрения самое логичное. Тогда выгоднее представить эти 3 скидки как один объект или комбинацию объектов, так как они взаимоисключающие.
4) "если пользователь выбрал 3 продукта одного типа, не задействованных в других скидках" - вроде в задании нет слов "одного типа"
Тебе надо для начала четко определить условия применения этих скидок.
И я считаю, эти 3 скидки на количество выгоднее сделать одним объектом, которому например передается массив количеств и соответствующих скидок. Потому что если применять их по порядку, то нам приходится городить критерии и последняя скидка как-то должна знать, что она последняя и к ней применяется другой критерий.
Что касается комбинирования скидок - вот я не уверен, что тут его стоило делать. Я такие штуки видел во фреймворках, кажется,что они позволяют строить произвольные комбинации объектов, но на практике это не особо нужно и проще в коде прописать нужное условие.
В общем, я предлагаю упростить код и уменьшить число классов и абстракций.
> $pc->hasNames
Удачнее было назвать hasAllOfNames().
> $pc->getOneOfName(['a', 'Z']) == true
Странно, что функция возвращает true/false, из названия кажется ,что возвращается продукт. Если ты хотел проверить на не-пустоту, лучше бы писать assert(!!$pc->getOneOfName(['a', 'Z']));
> public function getByName($name)
Эта функция должна называться getFirstByName() либо же возвращать массив, а не один объект.
> public function getWithoutNames
Лучше будет getAllExceptNames()
Но сама задача, я считаю, хорошая, жизненная.
>>915019
>> Если одновременно выбраны А и один из [K,L,M], то стоимость выбранного продукта уменьшается на 5%
> "Выбранный продукт" это же один из K, L, M? Сделал именно так.
Думаю, да, хотя там правила местами туманно сформулированы.
Классы, представляющие собой виды скидок, стоило бы объединить - либо через наследование от абстрактного класса, либо через реализацию интерфейса.
А так тебе пришлось сделать 2 метода для добавления скидок: addCountDiscount(), addCombinationDiscount(). Это не позволяет нам добавлять новые виды скидок, не меняя класс Calculator. И получается калькулятор у тебя знает слишком много о скидках (какие виды скидок бывают), лучше бы он просто принимал объекты-скидки, не вникая в их устройство. Тут нарушается принцип единой ответственности - у каждого объекта своя зона ответственности и это не дело Калькулятора, по какому именно принципу определяется скидка. Это ответственность объекта-скидки.
Давай попробуем придумать, какой может быть интерфейс у объекта-скидки? Спрячу под спойлер, если ты сначала хочешь подумать сам:
- скидка должна получать список товаров, чтобы искать в нем те, к которым можно примениться
- скидка должна также знать об использованных в предыдущих скидках товарах (чтобы соблюдать правило несуммирования скидок, при этом решение о несуммировании остается за объектом-скидкой. Мне кажется, это гибче, чем удалять товары из коллекции, хотя с другой стороны, может привести к копипасте кода по всем классам скидок)
Это понятно, и это простые вещи. А вот что должна вернуть функция применения скидки? Процент? Сумму скидки в рублях? Не, лучше возвращать подробную информацию, а именно:
- к каким конкретно экземплярам (не названиям!) товаров она была применена (заметим, там есть скидки, применяющиеся к нескольким товарам, и к общей сумме заказа)
- чему равна скидка, вроде тут везде использованы проценты
Тогда например Калькулятор мог бы логгировать подробно процесс применения скидок. И в чеке мы бы могли перечислить примененные скидки.
Эту информацию можно возвращать как-то массивом, объектом, а можно даже хранить в самой скидке, но тогда у нас получится одноразовый калькулятор, который надо пересоздавать перед использованием.
Плохо, что у тебя методы вроде getSumAfterApplyingDiscount() возвращают сразу цену. Мне кажется, они берут на себя задачу Калькулятора, и зоны ответственности Калькулятора и Скидки у тебя не разграничены, они лезут в задачи друг друга.
В общем, я бы советовал начинать проектирование именно с Калькулятора и цикла применения скидок, расчета цены, кто за что отвечает, а потом уже под это затачивать остальные классы.
Что касается CountCriteria - я не уверен, что его надо было делать, для меня выглядит как ненужное усложнение. Лучше было бы просто переупорядочить правила. И вообще, если внимательно почитать задание, то там условие сформулировано очень неудачно. Это можно понимать так:
1) "если пользователь выбрал ровно 3 продукта, не использованных в других скидках, то ..." - тут как-то нелогично получается, что в задании нет слов "не использованных в других скидках".
2) "если пользователь выбрал ровно 3 продукта, ..." - тогда, если пользователь выбрал более 5 продуктов, скидка не применяется, что нелогично.
3) "если ни одна из предыдущих скидок не применена, и пользователь выбрал 3 или более продуктов, то применить одну из этих 3 скидок в зависимости от количества" - вот это с моей точки зрения самое логичное. Тогда выгоднее представить эти 3 скидки как один объект или комбинацию объектов, так как они взаимоисключающие.
4) "если пользователь выбрал 3 продукта одного типа, не задействованных в других скидках" - вроде в задании нет слов "одного типа"
Тебе надо для начала четко определить условия применения этих скидок.
И я считаю, эти 3 скидки на количество выгоднее сделать одним объектом, которому например передается массив количеств и соответствующих скидок. Потому что если применять их по порядку, то нам приходится городить критерии и последняя скидка как-то должна знать, что она последняя и к ней применяется другой критерий.
Что касается комбинирования скидок - вот я не уверен, что тут его стоило делать. Я такие штуки видел во фреймворках, кажется,что они позволяют строить произвольные комбинации объектов, но на практике это не особо нужно и проще в коде прописать нужное условие.
В общем, я предлагаю упростить код и уменьшить число классов и абстракций.
> $pc->hasNames
Удачнее было назвать hasAllOfNames().
> $pc->getOneOfName(['a', 'Z']) == true
Странно, что функция возвращает true/false, из названия кажется ,что возвращается продукт. Если ты хотел проверить на не-пустоту, лучше бы писать assert(!!$pc->getOneOfName(['a', 'Z']));
> public function getByName($name)
Эта функция должна называться getFirstByName() либо же возвращать массив, а не один объект.
> public function getWithoutNames
Лучше будет getAllExceptNames()
Но сама задача, я считаю, хорошая, жизненная.
Тогда да, можно логику в Game, а работу с DOM отдельно. Но это уже полшага на пути к MVC, может стоит дальше в ту сторону и двигаться?
> Не понятно у кого из них будут какие свойства (количество мин, длина, высота, значение времени)
Если игровая логика в Game, то и свойства должны там храниться.
> и что делать когда пользователь решил начать игру заново, создавать ли новый field или чистить его.
Без разницы. Пересоздавать наверно проще.
>>914558
CMS - это для тех, кто любит настраивать сайт через админку
Фреймворк - для тех, кто любит писать кодом
"Что лучше" - поищи сайты-рейтинги CMS.
При этом, чтобы функция не жаловалась, что аргументов мало.
>> Но почему копирует? Функция это же объект и должна быть ссылка!
>Конечно, копирует ссылку на функцию.
Но функция находиться же в объекте obj! В чём подвох?
>>908071
>Задача 1: написать функцию bindContext(fn, that). Она создает новую функцию, которая при вызове вызывает fn с указанным this и переданным аргументами. То по сути есть привязывает произвольное значение this к функции.
https://jsfiddle.net/Luubgokx/
>Задача 2: сделать функцию addProperty(object, name, initialValue) для создания приватных свойств с геттерами и сеттерами на объекте или прототипе объекта.
https://jsfiddle.net/sh21j4p1/
>Uncaught TypeError: obj.getName is not a function
at window.onload ((index):64)
А что это тогда если не функция?
>Задача 3: сделать функцию для добавления в объект или прототип нового метода addMethod(object, name, fn). Использование:
Сначала нужно сделать 2-ую...
>>908076
>12. Некая сеть фастфудов предлагает несколько видов гамбургеров
>
>> if (Error.captureStackTrace) {
>Ага, в JS так просто не унаследуешь исключения, и вообще встроенные классы, известная проблема. Напоминает о том, что в JS все же прототипное ООП, а не классическое.
Я её откуда-то скопировал... Кажется отсюда https://learn.javascript.ru/oop-errors
>>> this.menu = {
>> SIZE_SMALL: {price: 50, calories: 20},
>тут дублируется значение константы, и это плохо. Лучше писать как
>menu[Hamburger.SIZE_SMALL]= ...
Почему-то не получается https://jsfiddle.net/y28h2o2b/3/
13. В одном городе есть электрическая сеть. К ней могут быть подключены
> Я предпологал что отдельный тип панели, это тот в котором "железно" определена мощность. Если этой причины не достаточно, то я просто переопределю конструктор класса-предка написав:
>Идея примерно такая. Так как элементы сети имеют что-то общее (их можно добавить в сеть, и они вносят вклад в баланс), то логично их наследовать от общего предка либо связать интерфейсом (которых в JS нет). Это нам дает такие преимущества, как возможность добавить общие методы, возможность проверять принадлежность к элементам сети через x instanceof NetworkElement.
С идеей я согласен.
...
>А сейчас у тебя копипаста из 4 методов getPower.
>Соответственно, если у всх элементов выработка статичная, логично сделать базовый класс:
>NetworkElement(dayPower, nightPower)
...только мне теперь кажется логичным что у каждого элемента сети должен быть один параметр, это мощность, и этот элемент должен сам определять дальнейшие условия определения этого параметра в соответствии\зависимости со средой (определение дневного\ночного времени).
Нужно сделать так:
NetworkElement(power) {
this.power = condition...
}
//Нужен ли тут гетер, если к свойству можно обратиться через точку?
NewtworkElement.prototype.getPower = function() {
return this.power
}
Но боюсь ради этого нужно будет переделывать всю программу. В другой раз.
>Для ЛЭП проще всего указать нули и учитывать их вклад отдельно (сделать у них методы для получения информации, сколько мощности доступно и по какой цене).
Лучше исключить её из перебора потому, что ЛЭП тоже может вернуть какое-то количество мощности.
>> ElectricalNetwork.prototype.countPrice = function() {
>> price += this.elements.countPrice(balance);
>> balance = this.elements.countPower(balance);
>Вот здесь нехорошо, что код расчета закупок/продаж размазан по 2 классам - ElectricalNetwork и PowerLine. Логичнее его оставить только в ElectricalNetwork. Ну подумай сам: кто принимает решение о закупке: электросеть или начальник на конкретной ЛЭП (или руководство удаленной сети, к которой подключена ЛЭП)? Логично, что решение принимают в центре, а на ЛЭП только сообщают, сколько нужно принять или передать, запрашивают цены и тд. Так как сама ЛЭП не знает про баланс энергии в сети.
С другой стороны электросеть не знает о ценах и о внутреннем устройстве ЛЭП. Моя идея в том, баланс можно просто передать в условный счетчик. Ведь это для ЛЭП свойственно иметь и считать цену.
https://jsfiddle.net/6591a2sL/4/
>14. напиши функцию, определяющую тип переменной. Результат должен быть одной из строк: 'undefined', 'boolean' (для true/false), 'null', 'number', 'string', 'function', 'array', 'array-like', 'object
>> switch(type) {
>> case '[object Function]':
>> return "Function";
>тут проще было использовать хеш: { '[object Function]': 'function', ... }
Что подрузумевается здесь под словом 'хеш'? И какие ещё свойства должны быть за место ... ?
>> if ('length' in variable && '0' in variable) {
>Недостаточно, надо бы проверить что там есть свойства от 0 до length - 1.
>от 0 до length - 1
А как это выразить в условии?
>15. Напиши функцию неглубокого копирования объектов и массивов
>> for (property in object) {
>тут есть подвох, for in перебирает не только свойства объекта, но и его прототипов. Если кто-то расширит стандартный Object.prototype, это свойство или метод попадет в цикл.
Опять же, не могу понять что с этим не так: Если это клон объекта, то этот клон должен иметь тот же прототип что и "донор"(?).
Лучше использовать for ... object.length вместо for in? Как тогда узнать имя свойства для клона?
https://jsfiddle.net/uyey3at1/1/
>16. Напиши функцию глубокого копирования объектов и массивов
>> if (typeof object[property] == 'object') {
>> clone[property] = deepClone(object[property]);
>typeof(null) дает object, а получить (null).constructor нельзя. Клонирование объекта с null в поле даст ошибку.
https://jsfiddle.net/j8pydqsg/2/
>> Но почему копирует? Функция это же объект и должна быть ссылка!
>Конечно, копирует ссылку на функцию.
Но функция находиться же в объекте obj! В чём подвох?
>>908071
>Задача 1: написать функцию bindContext(fn, that). Она создает новую функцию, которая при вызове вызывает fn с указанным this и переданным аргументами. То по сути есть привязывает произвольное значение this к функции.
https://jsfiddle.net/Luubgokx/
>Задача 2: сделать функцию addProperty(object, name, initialValue) для создания приватных свойств с геттерами и сеттерами на объекте или прототипе объекта.
https://jsfiddle.net/sh21j4p1/
>Uncaught TypeError: obj.getName is not a function
at window.onload ((index):64)
А что это тогда если не функция?
>Задача 3: сделать функцию для добавления в объект или прототип нового метода addMethod(object, name, fn). Использование:
Сначала нужно сделать 2-ую...
>>908076
>12. Некая сеть фастфудов предлагает несколько видов гамбургеров
>
>> if (Error.captureStackTrace) {
>Ага, в JS так просто не унаследуешь исключения, и вообще встроенные классы, известная проблема. Напоминает о том, что в JS все же прототипное ООП, а не классическое.
Я её откуда-то скопировал... Кажется отсюда https://learn.javascript.ru/oop-errors
>>> this.menu = {
>> SIZE_SMALL: {price: 50, calories: 20},
>тут дублируется значение константы, и это плохо. Лучше писать как
>menu[Hamburger.SIZE_SMALL]= ...
Почему-то не получается https://jsfiddle.net/y28h2o2b/3/
13. В одном городе есть электрическая сеть. К ней могут быть подключены
> Я предпологал что отдельный тип панели, это тот в котором "железно" определена мощность. Если этой причины не достаточно, то я просто переопределю конструктор класса-предка написав:
>Идея примерно такая. Так как элементы сети имеют что-то общее (их можно добавить в сеть, и они вносят вклад в баланс), то логично их наследовать от общего предка либо связать интерфейсом (которых в JS нет). Это нам дает такие преимущества, как возможность добавить общие методы, возможность проверять принадлежность к элементам сети через x instanceof NetworkElement.
С идеей я согласен.
...
>А сейчас у тебя копипаста из 4 методов getPower.
>Соответственно, если у всх элементов выработка статичная, логично сделать базовый класс:
>NetworkElement(dayPower, nightPower)
...только мне теперь кажется логичным что у каждого элемента сети должен быть один параметр, это мощность, и этот элемент должен сам определять дальнейшие условия определения этого параметра в соответствии\зависимости со средой (определение дневного\ночного времени).
Нужно сделать так:
NetworkElement(power) {
this.power = condition...
}
//Нужен ли тут гетер, если к свойству можно обратиться через точку?
NewtworkElement.prototype.getPower = function() {
return this.power
}
Но боюсь ради этого нужно будет переделывать всю программу. В другой раз.
>Для ЛЭП проще всего указать нули и учитывать их вклад отдельно (сделать у них методы для получения информации, сколько мощности доступно и по какой цене).
Лучше исключить её из перебора потому, что ЛЭП тоже может вернуть какое-то количество мощности.
>> ElectricalNetwork.prototype.countPrice = function() {
>> price += this.elements.countPrice(balance);
>> balance = this.elements.countPower(balance);
>Вот здесь нехорошо, что код расчета закупок/продаж размазан по 2 классам - ElectricalNetwork и PowerLine. Логичнее его оставить только в ElectricalNetwork. Ну подумай сам: кто принимает решение о закупке: электросеть или начальник на конкретной ЛЭП (или руководство удаленной сети, к которой подключена ЛЭП)? Логично, что решение принимают в центре, а на ЛЭП только сообщают, сколько нужно принять или передать, запрашивают цены и тд. Так как сама ЛЭП не знает про баланс энергии в сети.
С другой стороны электросеть не знает о ценах и о внутреннем устройстве ЛЭП. Моя идея в том, баланс можно просто передать в условный счетчик. Ведь это для ЛЭП свойственно иметь и считать цену.
https://jsfiddle.net/6591a2sL/4/
>14. напиши функцию, определяющую тип переменной. Результат должен быть одной из строк: 'undefined', 'boolean' (для true/false), 'null', 'number', 'string', 'function', 'array', 'array-like', 'object
>> switch(type) {
>> case '[object Function]':
>> return "Function";
>тут проще было использовать хеш: { '[object Function]': 'function', ... }
Что подрузумевается здесь под словом 'хеш'? И какие ещё свойства должны быть за место ... ?
>> if ('length' in variable && '0' in variable) {
>Недостаточно, надо бы проверить что там есть свойства от 0 до length - 1.
>от 0 до length - 1
А как это выразить в условии?
>15. Напиши функцию неглубокого копирования объектов и массивов
>> for (property in object) {
>тут есть подвох, for in перебирает не только свойства объекта, но и его прототипов. Если кто-то расширит стандартный Object.prototype, это свойство или метод попадет в цикл.
Опять же, не могу понять что с этим не так: Если это клон объекта, то этот клон должен иметь тот же прототип что и "донор"(?).
Лучше использовать for ... object.length вместо for in? Как тогда узнать имя свойства для клона?
https://jsfiddle.net/uyey3at1/1/
>16. Напиши функцию глубокого копирования объектов и массивов
>> if (typeof object[property] == 'object') {
>> clone[property] = deepClone(object[property]);
>typeof(null) дает object, а получить (null).constructor нельзя. Клонирование объекта с null в поле даст ошибку.
https://jsfiddle.net/j8pydqsg/2/
В JS можно (https://learn.javascript.ru/arguments-pseudoarray) , в PHP — наверное, нельзя, будет ругаться.
Передавай массивы, объекты.
Или, если ты знаешь наверняка сколько это N, в любом языке:
function countSum(first = 0, second = 0, ... ) { ... }
В общем, при написании функции можешь определить дефолтные значения аргументам, они перейдут внутрь при ее вызове.
Может, есть еще способы? Было бы интересно послушать.
Пожалуйста, переходите туда. Здесь будут только ответы на старые вопросы.
Если вам все еще не ответили, напомните о себе в новом треде.
В JS - смотри псевдопеременную arguments и синтаксис с многоточием в новых версиях JS:
- https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Spread_operator
- https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Rest_parameters
В PHP - смотри мануал
- http://php.net/manual/ru/functions.arguments.php
Обрати внимание, что в PHP5.6 добавили удобный синтаксис с многоточием.
makemyskillsg+v^reatagainANUSgGJcmailPUNCTUMcH0|om
Фронт знаю углублённо, php основы + понимании ООП, MVC не выкупил (сегодня пытался)
Бля, я загрузил его в мертвый тред, чтобы поискать через гугл (с телефона не удобно). Откуда ты вылез?
Што тебе?
Вы видите копию треда, сохраненную 12 февраля 2017 года.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.