Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 7.3 RC2, Codeception 2.5 и другие релизы, типизированные свойства в PHP 7.4, концепт предзагрузки кода от Дмитрия Стогова, порция полезных инструментов, и многое другое.
Приятного чтения!
Новости и релизы
- PHP 7.3 RC2 — Второй релиз-кандидат доставлен по расписанию. Следующий выпуск ожидается 11 октября. Еще один обзор нововведений PHP 7.3.
- PhpStorm 2018.3 EAP — Продолжается программа раннего доступа, в рамках которой можно использовать PhpStorm бесплатно. В свежих сборках добавлена поддержка <generic> нотации для коллекций из PSR-5 PHPDoc, а также пересечений типов
Foo&Bar
. Подробнее о пересечении и объединении типов от автора PHPStan. - Codeception 2.5 — В апдейте представлены возможности для снэпшот-тестирования, поддержка подключения к нескольким БД и другие улучшения.
- doctrine/mongodb-odm 2.0 Alpha 1
- nikic/FastRoute — разыскивает мейнтейнера! Несколько лет назад Никита Попов доказал, что роутинг может быть очень быстрым на PHP. Напомню про его отличную статью Fast request routing using regular expressions.
- Yandex PHP meetup, 18 октября 2018, Москва — В московском офисе Яндекса пройдёт встреча PHP-разработчиков.
PHP Internals
- [RFC] Typed Properties 2.0 — Предложение по типизированным свойства принято практически единогласно: 70 против 1! Типизированные свойства будут добавлены в PHP 7.4.Скрытый текст
class Example { // All types with the exception of "void" and "callable" are supported public int $scalarType; protected Type $type; private ?Type $nullableType; // Types are also legal on static properties public static iterable $staticProp; // Types can also be used with the "var" notation var bool $flag; // Typed properties may have default values (more below) public string $str = "foo"; public ?string $nullableStr = null; // The type applies to all properties in one declaration public float $x, $y; // equivalent to: public float $x; public float $y; }
- [PR] An attempt to implement «preloading» ability — Дмитрий Стогов главный автор проекта PHPNG, который стал базой для PHP 7. На этот раз Дмитрий представил концепт, который позволяет приложениям предварительно загружать код еще до того как PHP обработает HTTP-запрос.
Также Дмитрий работает над реализацией JIT в PHP, которая ожидается в PHP 8.
Инструменты
- spiral/roadrunner — Высокопроизводительный сервер приложений, балансировщик нагрузки и менеджер процессов для PHP реализованный на Go. Добавлена поддержка HTTPS и HTTP/2 Push.
- ankitpokhrel/tus-php — Реализация сервера и клиента tus.io открытого протокола возобновляемых загрузок файлов. Протокол используется в Vimeo, Cloudflare, и Git LFS.
- TiBeN/CrontabManager — Менеджер cron-задач.
- paragonie/easydb — Простая и удобная обертка над PDO. Доступно кэширование подготовленных запросов paragonie/easydb-cache.
- zendframework/zend-diactoros 2.0.0 — Обновление популярной реализации PSR-7 и теперь еще PSR-17.
- liuggio/fastest — Простое решение для параллельного запуска тестов. Альтернатива более сложному paratestphp/paratest
- konsulting/porter — Мультисайт окружение для локальной разработки на базе Docker.
Материалы для обучения
Symfony
- KnpUniversity переименован в SymfonyCasts
- enumag/no-thanks — Убирает напоминание о symfony/thanks в symfony/flex :-)
- Неделя Symfony #612 (17-23 сентября 2018), #613 (24-30 сентября 2018)
Laravel
- Laravel Zero 5.7
- Что такое APP_KEY и для чего он нужен
- Пошаговое руководство по созданию первого приложения на Laravel
- Building LaraShoppe — Скринкаст по созданию магазина с нуля с помощью Laravel, Laravel Nova, Vue.js, и Tailwind CSS.
- antonioribeiro/health — Мониторинг Laravel приложений с уведомлениями.
- spatie/laravel-blade-x — Позволяет использовать кастомные HTML-компоненты в Blade-шаблонах вместо использования директивы
@include
. - railken/lara-eye — Конвертирует строку запроса в набор вложенных конструкций where для QueryBuilder.
Async PHP
- ReactPHP Tutorial #12: Routing
- friends-of-reactphp/mysql 0.4 — Асинхронный клиент для MySQL. В обновлении представлен интерфейс на промисах вместо колбэков и стриминг больших результатов. Позволяет эффективно обрабатывать тысячи и миллинов записей из MySQL с помощью ReactPHP. Подробнее в посте автора.
CMS
- Magento Tech Digest #32: Sept 17 – 24, 2018
- Массовая атака на сайты с необновлённым движком WordPress
- 7 советов по написанию исключений
- Не используйте библиотеки для моков
- Assertions and assertion libraries
- Методы шифрования в PHP для паролей и других данных
- Paragon Initiative: Slice Of PIE #00 — Почему гибридное шифрование лучше чем асимметричное? Какие алгоритмы шифрования стоит исопльзовать PHP разработчикам?
- Создаем простое PHP-приложение на Google App Engine
- Конфигурация XDebug в Docker с помощью переменных окружения
- Нужно поднимать Kubernetes кластер, но я всего лишь программист кода. Выход есть
- Хранение большого количества файлов
- MVC + Scenario против Толстых Контроллеров
Аудио и видеоматериалы
- Пишем PSR-7 фреймворк: Консольные команды, базы данных и ресурсы
- Пятиминутка PHP Выпуск №39 — Как протестировать проект под PHP 7.3?
- Пятиминутка PHP Выпуск №38 — DevOops и эволюция DevOps в 2018 году
- PHP Roundtable #78: DocBlocks, Annotations, PSR-5
Занимательное
- Список самых желаемых фич для PHP 8 из опроса в твиттере — На первом месте оказались дженерики (RFC), а затем короткие лямбды (RFC), многопоточность (pthreads), скалярные объекты (nikic/scalar_objects), перегрузка методов, асинхронные возможности (PHP 8), и прочее.
- RemotePHP.io — Борда с вакансиями на удаленную работу.
Спасибо за внимание!
Если вы заметили ошибку или неточность — сообщите, пожалуйста, в личку.
Вопросы и предложения пишите на почту или в твиттер.
Прислать ссылку
Поиск ссылок по всем дайджестам
< Предыдущий выпуск: PHP-Дайджест № 139
Комментарии (10)
Akdmeh
01.10.2018 11:52Говорили, говорили, говорили не размещать .svn, .git, .env в папках, которые доступны веб-серверу, а воз и нынче там…
qWici
01.10.2018 12:27Где-то на StackOverflow видел как рекомендовали при использовании Apache все с public выносить в корень, так как он дефолтно смотрит в корень xDDD
SerafimArts
01.10.2018 14:15[RFC] Typed Properties 2.0 — Предложение по типизированным свойства принято практически единогласно: 70 против 1! Типизированные свойства будут добавлены в PHP 7.4.
А против проголосовал как раз Дмитрий Стогов. Интересно, кстати, почему?pronskiy Автор
01.10.2018 14:24+1Думаю из-за оверхеда по производительности gist.github.com/dstogov/b9fc0fdccfb8bf7bae121ce3d3ff1db1 ведь Дмитрий как раз производительностью занимается, а эта фича ухудшает ее.
SerafimArts
01.10.2018 14:38+2О, да, вполне возможно, кстати. Но по-моему, что это всего лишь обуславливается «сыростью» патча, т.к. он на перспективу позволяет облегчить Zval, избавив поля от лишних данных в режиме strict_types.
Fesor
01.10.2018 15:17+3Ник на рэддите [объяснял](https://www.reddit.com/r/PHP/comments/9j2oel/rfc_about_typed_properties_has_been_accepted/e6o94hd/).
boodda
01.10.2018 21:34Прикольный слон. Кстати сделан скорее всего с помощью https://github.com/fogleman/primitive.
Вот бота писал для телеграмм
Заголовок спойлераconst fs = require('fs'); const exec = require('child_process').execSync; const temp = require("temp").track(); const pathComponent = require('path'); const TelegramBot = require('node-telegram-bot-api'); const Settings = function () { this.defaultSettings = { rect: 1, type: 0 }; this.container = {}; }; Settings.prototype = { getValue: function (chatId, value = null) { if (this.container.hasOwnProperty(chatId)) { return value ? this.container[chatId][value] : this.container[chatId] } return value ? this.getDefault()[value] : this.getDefault(); }, setValue: function (chatId, key, value) { if (!this.container.hasOwnProperty(chatId)) { this.container[chatId] = this.getDefault(); } if (Object.keys(this.defaultSettings).indexOf(key) === -1) { console.log('Try to set unknown setting: ' + key); return; } if (key === 'type' || key === 'rect') { value = parseInt(value, 10) } this.container[chatId][key] = value; }, getDefault: function () { return Object.assign({}, this.defaultSettings); } }; const settingsContainer = new Settings(); // Устанавливаем токен, который выдавал нам бот. const token = ''; const downloadDir = './downloaded/'; const rectOptions = { reply_markup: JSON.stringify({ inline_keyboard: [ [{text: 'Треугольники', callback_data: 'rect_1'}], [{text: 'Прямоугольники', callback_data: 'rect_2'}], [{text: 'Эллипсы', callback_data: 'rect_3'}], [{text: 'Круги', callback_data: 'rect_4'}], [{text: 'Прямоугольники(rotated)', callback_data: 'rect_5'}], [{text: 'Кривые', callback_data: 'rect_6'}], [{text: 'Эллипсы(rotated)', callback_data: 'rect_7'}], [{text: 'Полигоны', callback_data: 'rect_8'}], ] }) }; const typeOptions = { reply_markup: JSON.stringify({ inline_keyboard: [ [{text: 'Простая картинка', callback_data: 'type_0'}], [{text: 'Движущаяся картинка', callback_data: 'type_1'}] ] }) }; // Включить опрос сервера const bot = new TelegramBot(token, {polling: true}); bot.onText(/\/start/, function (msg) { bot.sendMessage(msg.chat.id, '/status /settype /setfig'); }); bot.onText(/\/status/, function (msg) { bot.sendMessage(msg.chat.id, 'I am alive!'); }); bot.onText(/\/setfig/, function (msg) { bot.sendMessage(msg.chat.id, 'Выберите фигуры из которых будет формируется изображение:', rectOptions); }); bot.onText(/\/settype/, function (msg) { bot.sendMessage(msg.chat.id, 'Выберите тип возвращаемого изображения:', typeOptions); }); bot.on('callback_query', function (msg) { let chatId = msg.message.chat.id; let answer = msg.data.split('_'); if (answer.length === 2) { settingsContainer.setValue(chatId, answer[0], answer[1]); } bot.sendMessage(chatId, 'ok'); }); bot.on('message', function (msg) { console.log(msg); }); bot.on('photo', onPhoto); async function onPhoto (msg) { let chatId = msg.chat.id; try { let image = initImage(msg); let originalImagePath = await bot.downloadFile(image.file_id, downloadDir); let ext = pathComponent.extname(originalImagePath); let resizedImagePath = temp.path({suffix: ext}); let processedImagePath; await resizeImage(image, originalImagePath, resizedImagePath); let settings = settingsContainer.getValue(chatId); console.log(settings); if (settings.type === 0) { processedImagePath = temp.path({suffix: ext}); makeSimpleImage(settings, resizedImagePath, processedImagePath); await bot.sendPhoto(chatId, processedImagePath, {}); } else if (settings.type === 1) { processedImagePath = temp.path({suffix: '.gif'}); await makeGif(settings, image, resizedImagePath, processedImagePath); await bot.sendVideo(chatId, processedImagePath, {}); } temp.cleanupSync(); } catch (err) { console.log(err); return bot.sendMessage(chatId, err); } } function initImage(msg) { let photo = msg.photo || []; let lastFileSize = 0; let maxResId = -1; let i; for (i = 0; i < photo.length; i++) { if ((photo[i].file_size || 0) > lastFileSize) { maxResId = i; } } if (maxResId === -1) { throw new Error('No photo info'); } let msgPhoto = photo[maxResId]; ['file_id', 'width', 'height'].forEach(function (prop) { if (!msgPhoto.hasOwnProperty(prop)) { throw new Error('No photo size'); } }); return msgPhoto; } function resizeImage (image, from_path, to_path) { let resize = image.width > image.height ? '256x' : 'x256'; return exec('convert -geometry ' + resize + ' ' + from_path + ' ' + to_path) } function makeSimpleImage(settings, from_path, to_path) { return exec('primitive -n 250 -m ' + settings.rect + ' -i ' + from_path + ' -o ' + to_path) } async function makeGif(settings, image, from_path, to_path) { let dir = './series/' + makeRandName(); let size = image.width > image.height ? '512:' : ':512'; if (!fs.existsSync(dir)){ fs.mkdirSync(dir); } for (let i = 1; i <=5 ; i++) { makeSimpleImage(settings, from_path, '/ ' + i + '.svg'); exec('node node_modules/svgexport/bin/index.js ' + dir + '/' + i + '.svg ' + dir + '/' + i +'.png ' + size); } let makeGifCommand = 'convert -loop 0 -delay 12 ' + dir + '/1.png ' + dir + '/2.png ' + dir + '/3.png ' + dir + '/4.png ' + dir + '/5.png ' + to_path; console.log(makeGifCommand); return exec(makeGifCommand); } function makeRandName() { let text = ""; let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < 8; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; }
qWici
pronskiy ошибочка «Пошаговое УРКоводство по созданию первого приложения на Laravel»
eee
Зато звучит
pronskiy Автор
Поправил, спасибо