«Hello, (real) world!» на php в 2017 году

ac1d8765e79f41269b78cde6c9e10c5b.jpgВы наверняка думаете, что писать на php — это просто. И «hello, world» выглядит примерно так так:

Конечно, чего еще ожидать от языка с низким порогом входа. Ну да, именно так и было раньше. Много лет назад. Но теперь, в 2017 году никто так уже не делает. Давайте рассмотрим, почему, и попробуем построить наше более реалистичное hello-world приложение по шагам, а их, скажу сразу, получилось не мало.


→ Полный исходный код «hello, world» можно посмотреть здесь.

Для начала надо осознать тот факт, что без фреймворка сейчас приложения никто не делает. Если вы пишете вручную »echo 'hello, world'», то обрекаете проект на говнокод на веки вечные (кто потом этот велосипед за вас переписывать будет?). Поэтому возьмем какой-нибудь современный, распространенный в мире фреймворк, например Symfony.

Но прежде, чем его устанавливать, надо бы создать базу данных. Зачем базу данных? Ну не хардкодить же строку «hello, world» прямо в тексте программы!

База данных


В 2017 году принято использовать postgresql. Если вы вдруг еще не умеет его устанавливать, я помогу:
sudo apt-get install postgresql

Убунта при установке создаст юзера postgres, из под которого можно запустить команду psql с полными правами на базу.
sudo -u postgres psql

Теперь создадим юзера базы с паролем (придумайте какой-нибудь посложнее).
 CREATE ROLE helloworlduser  WITH PASSWORD '12345'  LOGIN;

И саму базу:
 CREATE DATABASE helloworld  OWNER helloworlduser;

Также надо убедиться, что в pg_hba.conf у вас разрешены коннекты к базе с localhost (127.0.0.1). Там должно быть что-то вроде этого:
host    all             all             127.0.0.1/32            md5

Проверим соединение:
psql -h localhost -U helloworlduser helloworld

после ввода пароля должно пустить в базу. Сразу создадим таблицу:
CREATE TABLE greetings (
     id int,
     greeting text,
     primary key(id)
);

INSERT INTO greetings 
(id, greeting) 
VALUES 
(1, 'Hello, world!');

Ну, супер, с базой всё. Теперь перейдем к фреймворку

php-фреймворк


Надеюсь, что в 2017 году у всех стоит composer на компьютере. Поэтому сразу перейдем к установке фреймворка
 composer create-project symfony/framework-standard-edition  helloworldphp

При установке он сразу спросит параметры соединения с базой:
host: 127.0.0.1
database_name: helloworld
database_user: helloworlduser
database_password: 12345

остальное по умолчанию/по усмотрению.

Надо только в конфиге config.yml поменть драйвер на driver: pdo_pgsql. (У вас ведь установлено php-расширение pdo_pgsql?)

Проверим, что всё более менее работает, запустив

cd helloworldphp
bin/console server:start 

Симофни запустит свой собственный сервер, который слушает порт 8000 и на нем можно дебажить код. Таким образом в браузере по адресу http://localhost:8000/ должно быть что-то вроде «Это симфони, блаблабла».

Уфф! Казалось бы всё, контроллер уже есть, подправить вьюху, создать модель и понеслась, хелло ворлд уже близко!

Но… нет. Извините, но не в 2017-ом. В этом году все делают SPA (single page application).
Php-программист в 2017 году не может обойтись без js и верстки, теперь мы все full stack, а значит и helloworld должен быть соответствующий.

Ну ладно, ладно, еще бывают чистые php-бекенд-разработчики, но давайте возьмем более общий случай

JavaScript и его многочисленные друзья


Поэтому находим в симфони вьюху (а дефолтная вьюха лежит в app/Resources/view/default/index.html.twig) и стираем там всё, заменяя на:



   
   
   



Т.е. всё будет лежат в bundle.js: сжатые javascript файлы прямо вместе со стилями и всем, чем нужно.
Как нам создать этот бандл? Нужно написать приложение и настроить webpack для сборки.

Webpack (или его аналоги) нам все равно бы понадобились, мы же не будем писать код на чистом javascript в 2017-году, когда typescript явно в тренде. А typescript надо как-то преобразовать в обычную js-ку. Это удобно делать, используя webpack.

Разумеется, на чистом typescript тоже никто не пишет. Нужен какой-то фреймворк. Одна из самых модных связок сейчас — это react + redux. А для верстки, так и быть, будем использовать старый добрый олдскульный bootstrap (через sass, конечно же).

Нам понадобится куча js-библиотек. У вас ведь стоит nodejs и npm? Убедитесь, что у вас свежий npm и установите пакеты:

npm init 

в зависимостях (в файле package.json) пропишем примерно такое:
"dependencies": {
 "@types/react": "^15.0.11",
 "@types/react-dom": "^0.14.23",
 "babel-core": "^6.23.1",
 "babel-loader": "^6.3.2",
 "babel-preset-es2015": "^6.22.0",
 "babel-preset-react": "^6.23.0",
 "bootstrap-sass": "^3.3.7",
 "css-loader": "^0.26.1",
 "node-sass": "^4.5.0",
 "react": "^15.4.2",
 "react-dom": "^15.4.2",
 "react-redux": "^5.0.2",
 "redux": "^3.6.0",
 "resolve-url-loader": "^2.0.0",
 "sass-loader": "^6.0.1",
 "style-loader": "^0.13.1",
 "ts-loader": "^2.0.0",
 "typescript": "^2.1.6",
 "url-loader": "^0.5.7",
 "webpack": "^2.2.1",
 "@types/node": "^7.0.5"
}

И выполним

npm install

и еще нужно установить:
npm install webpack -g

чтобы была доступна команда webpack.

Увы, это еще далеко не всё. Так как у нас typescript, еще надо создать файл tsconfig.json, примверно такой:

tsconfig.json
{
  "compilerOptions": {
    "module": "es6",
    "moduleResolution": "node",
    "sourceMap": false,
    "target": "esnext",
    "outDir": "web/ts",
    "lib": [
      "dom",
      "scripthost",
      "es5",
      "es6",
      "es7"
    ],
    "jsx": "react"
  },
  "include": [
    "frontend/**/*.ts",
    "frontend/**/*.tsx"
  ]
}


С конфигами пока что ок, теперь займемся нашим приложением на typescript.

Сначала создадим компонент для отображения нашего текста:

// файл frontend/components/Greetings.tsx
import * as React from 'react';

export interface GreetingsProps {
    text: string;
    isReady: boolean;
    onMount();
}

class Greetings extends React.Component {

    componentDidMount() {
        this.props.onMount();
    }

    render() {
        return (
            

{this.props.text}

); } } export default Greetings;

Наше SPA будет подгружать текст надписи через Rest API. React — это просто view-компоненты, а нам еще нужна логика приложения и управление состоянием.

Так что будем использовать redux, а также пакет для связи redux и react (react-redux). Поэтому надо будет еще создать компонент, который будет создавать наш компонент Greetings с нужными properties, и сможет сообщить хранилищу (store) состояния, что появилось новое действие (получены данные для отображения).

Disclaimer: я только начал изучать redux, поэтому наверняка тут есть за что «бить по рукам».

Выглядит этот компонент, допустим, примерно так:

// файл frontend/components/App.tsx
import * as React from 'react';

import {connect} from 'react-redux'
import Greetings from './Greetings';


const mapStateToProps = (state) => {
    return state;
}

const mapDispatchToProps = (dispatch) => {
    return {
        onMount: () => {
            fetch("/greetings/1").then((response) => {
                return response.json();
            }).then((json) => {
                dispatch({type: 'FETCH_GREETING', text: json.greeting})
            });
        }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Greetings);

Ну и точка входа приложения, создание redux-стора, диспатчера и т.д. Тут всё сделано немного по рабоче-крестьянски, но для хелловорлда сойдет, пожалуй:
// подгружает стили  bootstrap
import 'bootstrap-sass/assets/stylesheets/_bootstrap.scss';

import * as React from 'react';
import * as ReactDOM from "react-dom";
import {Provider} from 'react-redux';
import App from './components/App';
import {createStore} from 'redux';

const app = (state = {isReady: false, text: ''}, action) => {
    switch (action.type) {
        case 'FETCH_GREETING':
            return Object.assign({}, state, {isReady: true, text: action.text});
    }
    return state;
}

const store = createStore(app);

ReactDOM.render(
    
        
    ,

    document.getElementById("root")
);

Примерно здесь происходит следующее:
  • Первоначальное состояние системы — {isReady: false, text: ''}.
  • Создан reducer под названием app, который умеет обрабатывать действие FETCH_GREETING и возвращать новое состояние системы.
  • Создан store для обработки состояний.
  • Всё отрендеривается в элемент, который мы прописали во вьюхе

Ах да, совсем забыл. Конфиг вебпака:
webpack.config.js
const webpack = require('webpack');
const path = require('path');
const ENVIRONMENT = process.env.NODE_ENV || 'development';

let config = {
    context: path.resolve(__dirname, "frontend"),
    entry: './index.tsx',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, "web/js")
    },
    resolve: {
        extensions: [ ".js", ".jsx", '.ts', '.tsx']
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: [{
                    loader: 'babel-loader',
                    query: {
                        presets: ['es2015', 'react']
                    }
                }, {
                    loader: 'ts-loader'
                }]
            },

            {
                test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                loader: 'url-loader'
            },

            {
                test: /\.scss$/,
                use: [
                    {
                        loader: "style-loader"
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "resolve-url-loader"
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(ENVIRONMENT)
        })
    ],
    node: {
        process: false
    }
};

if (ENVIRONMENT == 'production') {
    config.plugins.push(
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                drop_console: false,
                warnings: false
            }
        })
    );
}

module.exports = config;


Теперь мы можем запустить webpack или NODE_ENV=production webpack (чтобы получить минифицированную версию bundle.js)

Pomodoro


Не знаю как вы, а я уже задолбался писать этот hello, world. В 2017 году надо работать эффективно, а это подразумевает, что надо делать перерывы в работе (метод Pomodoro и т.д.). Так что, пожалуй, прервусь не надолго.

[прошло какое-то время]

Давайте продолжим. Мы уже умеем подгружать код с /greetings/1 на стороне javascript, но php-часть еще совершенно не готова.

Doctrine


Уже потрачено много времени, а в php-коде не создано ни одной сущности. Давайте исправим положение:
id;
    }

    public function getGreeting()
    {
        return $this->greeting;
    }
}

Супер. Осталось совсем чуть-чуть.

REST


Надо сделать-таки простенький REST API, который может хотя бы отдать json по запросу GET /greetings/1

Для этого в контроллере (файл src/AppBundle/Controller/DefaultController.php) добавим метод с роутом:

    /**
     * @Route("/greetings/{id}")
     */
    public function greetings($id)
    {
        $greeting = $this->getDoctrine()->getRepository("AppBundle:Greeting")->find($id);
        return new JsonResponse(['greeting' => $greeting->getGreeting()]);
    }

Всё, можно запускать. На экране отображается «Hello, world!». Внешне он, конечно, выглядит почти также как результат (если не считать бутстраповского шрифта), но теперь это современное приложение по всем канонам. Ну, скажем так, почти по всем канонам (не хватает тестов, проверок ошибок и много чего еще), но я уже задолбался это делать :)

Выводы


В последнее время сильно участились споры «зачем нужен php, если есть java». Уж не знаю, кто прав, а кто нет, холивары — дело такое. Но в каждом споре один из аргументов в пользу php — это простота для новичков. Как мне кажется, этот аргумент уже давно не валиден, что я и хотел показать этой статьёй. Новичку все равно придется кучу всего узнать и 100500 конфигов настроить: фреймворки (очень похожие на фреймворки java), базы данных, linux, javascript со всем своим зоопарком, верстка, http-протокол, различный тулинг и многое-многое другое. Даже если это не SPA.

Upd. Статья уходит в глубокий минус, но я не собираюсь менять мнение. Оно примерно такое:
1) SPA всё больше проникает в наш мир, и надо это уметь, хотя бы в общих чертах.
2) Без фреймворков не построишь хорошее современное приложение.

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

  • 20 февраля 2017 в 11:02

    0

    Вы смешали frontend (где действительно избыточно много абстракций) и backend.
    Достаточно было создать модель и вывести «Hello world!» из базы во вьюхе через контроллер.
    • 20 февраля 2017 в 11:05

      +2

      Согласен, получилось скорее hello, world SPA. Но сейчас SPA со страшной силой шагает по планете. Зайдите в любую современную админку банка и т.д. — там будет реакт. Мне кажется, надо уметь хотя бы хелловорлд всего этого дела.
  • 20 февраля 2017 в 11:02

    +1

    А если я не знаю PHP, хочу его изучить, мне сразу начинать с фрейморвка? Мне кажется более логично изучить голый PHP.
    • 20 февраля 2017 в 11:07

      0

      Согласен. Но при этом не надо обманываться насчет того, что голого php хватит для реального приложения. Поэтому почти сразу же надо начинать юзать фреймворк
      • 20 февраля 2017 в 11:15

        +1

        сейчас да пожалуй, но как же раньше писались веб-приложения, когда фреймворков не было
        • 20 февраля 2017 в 11:24

          +3

          Сперва приходилось писать свой фреймворк. А потом на нём писать веб-приложение.
        • 20 февраля 2017 в 11:32

          +1

          тысячами разработчиками писались одни и те же вещи, часто с одними и теми же ошибками. Попробуйте написать такое же (желательно так же с объектом Greeting, получаемым из базы, а не массивом или stdClass) приложение на голом PHP и количество кода вас неприятно удивит. Или в коде будут грубые ошибки.

      • 20 февраля 2017 в 11:24 (комментарий был изменён)

        0

        Это смотря какое приложение. Да и фреймворки разные есть. Тут больше важно что нужно знать современные концепции и стандарты современного веб приложения. Вот composer — да; PSR’ы; Неймспейсы; Автолоадинг; Система контроля версий; Единая точка входа; Паблик директория; Роутинг; Шаблонизация; Query builder. Остальное имхо опционально.


        И если уж говорить о самой статье, то замените symfony на laravel и react на vue. И статья выйдет короче)

      • 20 февраля 2017 в 11:35

        0

        Что за бред?
        Часто видел когда люди сразу начинали учить cms или фреймворк и потом даже не понимали где cms, а где сам php.
    • 20 февраля 2017 в 11:25

      0

      ИМХО, следует сначала учить чистый PHP, потом попробовать написать свой фреймворк (все должны через это пройти, чтобы исчезло желание писать велосипеды), и только потом пробовать уже существующие фреймворки.
  • 20 февраля 2017 в 11:05

    +2

    >надо осознать тот факт, что без фреймворка сейчас приложения никто не делает.
    Вот поэтому о PHP и JavaScript многие такого плохого мнения — на каждый чих свой фреймворк. Все они разные. Все велосипедные и…
    >кто потом этот велосипед за вас переписывать будет?
  • 20 февраля 2017 в 11:18

    +1

    И всё же, hello world на PHP выглядит именно так:
    • 20 февраля 2017 в 11:21

      +1

      он выглядит вот так:
      Hello, world!
      

      потому что для вывода строки не нужно открывать пхпшный тег вообще.
  • 20 февраля 2017 в 11:21 (комментарий был изменён)

    0

    не увидел обвеса статьи в «sarcasm»

  • 20 февраля 2017 в 11:24

    +2

    Автор умничал-умничал и в итоге написал всю бизнес-логику прямо в контроллер. Молодец.
    • 20 февраля 2017 в 11:35

      0

      Доставание объекта из репозитория не бизнес-логика. Здесь бизнес-логики по сути нет вообще, только бизнес-данные.

  • 20 февраля 2017 в 11:24

    0

    Полагаю, что требования к Hello world звучат примерно так «нужно уметь выводить надпись Hello world». Точка.
    Даже помня, что «выводить» можно теоретически не только в консоль, реализация слишком усложнена.
    Откуда у вас взялись REST, Postgres и куча фреймворков? Где тогда load balancing, горячий кэш данных и (конечно же!) MongoDB? Усложнять так усложнять!
    • 20 февраля 2017 в 12:34

      +1

      Полагаю, что требования к Hello world звучат примерно так «нужно уметь выводить надпись Hello world». Точка.

      Надо быть готовым, что в последствии нужно будет выводить надпись «Good evening world» или «Good morning world» в зависимости от времени суток, на выбранном языке, а потом дать возможность пользователю самому выбрать свое приветствие, и так далее.
      • 20 февраля 2017 в 12:42 (комментарий был изменён)

        0

        Не забывайте о дизайне! Кому нужно некрасивое приложение?! Без Sass и Compass это не современное PHP Hello world!
  • 20 февраля 2017 в 11:27

    +3

    Сейчас тяжело найти нормального программиста, который умеет писать на ГОЛОМ PHP! Нет, движок у проекта есть, но модули почти на 80% состоят из обычного функционального программирования. Попросишь написать небольшую фичу, так он привязывает еще тонну файлов фреймворка.

    Это относится как к Frontend, и в большей степени к Backend — где одни демоны, кроны и консоли.

    Если программист может решить задачу на голом php, то он сможет и разобраться в фреймворке. А вот наоборот — не суждено.

    • 20 февраля 2017 в 11:28

      0

      Крайне спорное утверждение

    • 20 февраля 2017 в 11:51

      +3

      Может потому что нормальные программисты не хотят сидеть и допиливать проект из »80% обычного функционального программирования», которое на самом деле процедурное?)
      • 20 февраля 2017 в 12:46

        0

        А чего допиливать? Например, проекту более 5 лет, он не падает после yum update, нет каких-либо зависимостей, нет отсутствия обратной зависимости. 100% кода в бэкенде такое, выглядит красиво, работает стабильно, дебажится стандартными инструментами, нет скрытой логики.

        Frontend действительно стоить держать в современном виде, но главное не зарываться. Сайт Тинькофф-банка грузится сотней запросов к серверам (бесит). Сайт старого банка — двумя запросами. Скорость не в пользу Тинькова…

    • 20 февраля 2017 в 12:37

      0

      На голый пхп, да ещё процедурный, для приложений с запретом использования сторонних библиотек на пхп вернусь только за очень большие деньги.
  • 20 февраля 2017 в 11:29

    +5

    Автор, В 2017 держать под гитом .idea моветон)

    • 20 февраля 2017 в 11:35

      –3

      да, забыл добавить в .gitignore)
      • 20 февраля 2017 в 11:46 (комментарий был изменён)

        +2

        Есть мнение что помещать в .gitignore тоже моветон) Нужно в .git/info/exclude. Но это конечно спорно

        • 20 февраля 2017 в 12:01

          +1

          Век живи, век учись. Спасибо.
    • 20 февраля 2017 в 11:37

      0

      workspace.xml точно моветон, остальное — дело вкуса и соглашений в команде

  • 20 февраля 2017 в 11:44

    +7

    Создавать таблицу без миграции в 2017.
    • 20 февраля 2017 в 11:44

      0

      да, фейл
  • 20 февраля 2017 в 11:46 (комментарий был изменён)

    –1

    Простите за оффтоп, но как же раздражают многочисленные «в 2017 году». Т.е. в 2016 можно было говнокодить по страшному и резко, меньше чем за 2 месяца все прозрели и стали писать абсолютно по другому?

  • 20 февраля 2017 в 11:49

    +2

    Примерно из той же оперы
    411d596ae36941bbb62b67522bdf11f2.gif

  • 20 февраля 2017 в 11:59

    –2

    Не согласен с автором. Любой нормальный программист должен пройти этап эволюции от говнокода до написания собственных фреймворков. А не использовать готовые, которые сводят на нет суть программирования.
    • 20 февраля 2017 в 12:01

      +3

      Я надеюсь, вы не используете готовые высокоуровневые языки, которые сводят на нет суть программирования? Только машинный код?

      • 20 февраля 2017 в 12:10

        +1

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

        Типичный пример. Мне надо было сделать интеграцию самописной CRM системой с 1С по REST\OData через HTTP-сервис, который появился недавно в платформе 1С. И что вы думаете? Я обзвонил сотни программистов, поговорил в живую с десятком и не нашел того, кто это может сделать. Они только и делали круглые глаза, даже не представляя себе, что это возможно.

        Две недели упорного труда и сам реализовал то что мне нужно не имея никакого опыта программирования в 1С.

        Быть инсталлятором фреймов и готовых библиотек — одно, решать задачи не прибегая к готовым решениям — другое. Это и есть путь нормального эволюционного процесса хорошего программиста.

        • 20 февраля 2017 в 12:16

          0

          Ну вы прям утрируете. Где вы находили эти сотни программистов которые не умеют работать с REST и не способных потратить пару вечеров на ознамление с новой ачивкой для себя?
        • 20 февраля 2017 в 12:16

          +1

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

          Программист — это тот, кто решает задачу заказчика с необходимым качеством (и прочими критериями выполнения). CMS, фреймворк — это не имеет значения.


          А уж кто как себя позиционирует — так это личное дело каждого.


          решать задачи не прибегая к готовым решениям — другое. Это и есть путь нормального эволюционного процесса хорошего программиста.

          Так зачем же вы используете «готовое решение» PHP и «готовое решение» OData? Это совсем не путь нормального эволюционного процесса. Пишите свой язык, делайте свой протокол.

          • 20 февраля 2017 в 12:20

            0

            это вы утрируете… и путаете понятия. фреймы — это надстройки. Я не предлагаю изобретать новые стандарты и протоколы. Я о том, что взять чужой код и использовать его, иногда даже не понимая как он работает — одно, разобраться и написать свое — другое.
            • 20 февраля 2017 в 12:21 (комментарий был изменён)

              +3

              Вы уже разобрались, как работает интерпретатор PHP? До какого уровня вниз?

            • 20 февраля 2017 в 12:43

              0

              Фреймы — заботливые обертки для моего кода, берущие на себя десятки рутинных задач, которые раньше у меня ходили из проекта в проект. С фреймом я занимаюсь решением бизнес-задач, а не роутингом, валидацией форс и объектов, работой с БД и прочим, что для решения бизнес-задач если не необходимо, то крайне желательно, но решением не является.
  • 20 февраля 2017 в 12:04 (комментарий был изменён)

    0

    del

© Habrahabr.ru