Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 7.3 RC2, Codeception 2.5 и другие релизы, типизированные свойства в PHP 7.4, концепт предзагрузки кода от Дмитрия Стогова, порция полезных инструментов, и многое другое.
Приятного чтения!



Новости и релизы



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.


Материалы для обучения




Аудио и видеоматериалы




Занимательное



Спасибо за внимание!

Если вы заметили ошибку или неточность — сообщите, пожалуйста, в личку.
Вопросы и предложения пишите на почту или в твиттер.

Прислать ссылку
Поиск ссылок по всем дайджестам
< Предыдущий выпуск: PHP-Дайджест № 139

Комментарии (10)


  1. qWici
    01.10.2018 10:29

    pronskiy ошибочка «Пошаговое УРКоводство по созданию первого приложения на Laravel»


    1. eee
      01.10.2018 11:10
      +5

      Зато звучит


    1. pronskiy Автор
      01.10.2018 11:15

      Поправил, спасибо


  1. Akdmeh
    01.10.2018 11:52

    Говорили, говорили, говорили не размещать .svn, .git, .env в папках, которые доступны веб-серверу, а воз и нынче там…


    1. qWici
      01.10.2018 12:27

      Где-то на StackOverflow видел как рекомендовали при использовании Apache все с public выносить в корень, так как он дефолтно смотрит в корень xDDD


  1. SerafimArts
    01.10.2018 14:15

    [RFC] Typed Properties 2.0 — Предложение по типизированным свойства принято практически единогласно: 70 против 1! Типизированные свойства будут добавлены в PHP 7.4.

    А против проголосовал как раз Дмитрий Стогов. Интересно, кстати, почему?


    1. pronskiy Автор
      01.10.2018 14:24
      +1

      Думаю из-за оверхеда по производительности gist.github.com/dstogov/b9fc0fdccfb8bf7bae121ce3d3ff1db1 ведь Дмитрий как раз производительностью занимается, а эта фича ухудшает ее.


      1. SerafimArts
        01.10.2018 14:38
        +2

        О, да, вполне возможно, кстати. Но по-моему, что это всего лишь обуславливается «сыростью» патча, т.к. он на перспективу позволяет облегчить Zval, избавив поля от лишних данных в режиме strict_types.


    1. Fesor
      01.10.2018 15:17
      +3

      Ник на рэддите [объяснял](https://www.reddit.com/r/PHP/comments/9j2oel/rfc_about_typed_properties_has_been_accepted/e6o94hd/).


  1. 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;
    }