Вы наверняка думаете, что писать на php — это просто. И «hello, world» выглядит примерно так так:
Конечно, чего еще ожидать от языка с низким порогом входа. Ну да, именно так и было раньше. Много лет назад. Но теперь, в 2017 году никто так уже не делает. Давайте рассмотрим, почему, и попробуем построить наше более реалистичное hello-world приложение по шагам, а их, скажу сразу, получилось не мало.
→ Полный исходный код «hello,world» можно посмотреть здесь.
Для начала надо осознать тот факт, что без фреймворка сейчас приложения никто не делает. Если вы пишете вручную "
Но прежде, чем его устанавливать, надо бы создать базу данных. Зачем базу данных? Ну не хардкодить же строку «hello, world» прямо в тексте программы!
В 2017 году принято использовать postgresql. Если вы вдруг еще не умеете его устанавливать, я помогу:
Убунта при установке создаст юзера postgres, из под которого можно запустить команду psql с полными правами на базу.
Теперь создадим юзера базы с паролем (придумайте какой-нибудь посложнее).
И саму базу:
Также надо убедиться, что в pg_hba.conf у вас разрешены коннекты к базе с localhost (127.0.0.1). Там должно быть что-то вроде этого:
Проверим соединение:
после ввода пароля должно пустить в базу. Сразу создадим таблицу:
Ну, супер, с базой всё. Теперь перейдем к фреймворку
Надеюсь, что в 2017 году у всех стоит composer на компьютере. Поэтому сразу перейдем к установке фреймворка
При установке он сразу спросит параметры соединения с базой:
остальное по умолчанию/по усмотрению.
Надо только в конфиге config.yml поменть драйвер на
Проверим, что всё более менее работает, запустив
Симфони запустит свой собственный сервер, который слушает порт 8000 и на нем можно дебажить код. Таким образом в браузере по адресу
Уфф! Казалось бы всё, контроллер уже есть, подправить вьюху, создать модель и понеслась, хелло ворлд уже близко!
Но… нет. Извините, но не в 2017-ом. В этом году все делают SPA (single page application).
Php-программист в 2017 году не может обойтись без js и верстки, теперь мы все full stack, а значит и helloworld должен быть соответствующий.
Ну ладно, ладно, еще бывают чистые php-бекенд-разработчики, но давайте возьмем более общий случай
Поэтому находим в симфони вьюху (а дефолтная вьюха лежит в 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 и установите пакеты:
в зависимостях (в файле package.json) пропишем примерно такое:
И выполним
и еще нужно установить:
чтобы была доступна команда webpack.
Увы, это еще далеко не всё. Так как у нас typescript, еще надо создать файл tsconfig.json, примерно такой:
С конфигами пока что ок, теперь займемся нашим приложением на typescript.
Сначала создадим компонент для отображения нашего текста:
Наше SPA будет подгружать текст надписи через Rest API. React — это просто view-компоненты, а нам еще нужна логика приложения и управление состоянием.
Так что будем использовать redux, а также пакет для связи redux и react (react-redux). Поэтому надо будет еще создать компонент, который будет создавать наш компонент Greetings с нужными properties, и сможет сообщить хранилищу (store) состояния, что появилось новое действие (получены данные для отображения).
Disclaimer: я только начал изучать redux, поэтому наверняка тут есть за что «бить по рукам».
Выглядит этот компонент, допустим, примерно так:
Ну и точка входа приложения, создание redux-стора, диспатчера и т.д. Тут всё сделано немного по рабоче-крестьянски, но для хелловорлда сойдет, пожалуй:
Примерно здесь происходит следующее:
Ах да, совсем забыл. Конфиг вебпака:
Теперь мы можем запустить webpack или NODE_ENV=production webpack (чтобы получить минифицированную версию bundle.js)
Не знаю как вы, а я уже задолбался писать этот hello, world. В 2017 году надо работать эффективно, а это подразумевает, что надо делать перерывы в работе (метод Pomodoro и т.д.). Так что, пожалуй, прервусь не надолго.
[прошло какое-то время]
Давайте продолжим. Мы уже умеем подгружать код с /greetings/1 на стороне javascript, но php-часть еще совершенно не готова.
Уже потрачено много времени, а в php-коде не создано ни одной сущности. Давайте исправим положение:
Супер. Осталось совсем чуть-чуть.
Надо сделать-таки простенький REST API, который может хотя бы отдать json по запросу GET /greetings/1
Для этого в контроллере (файл src/AppBundle/Controller/DefaultController.php) добавим метод с роутом:
Всё, можно запускать. На экране отображается «Hello, world!». Внешне он, конечно, выглядит почти также как результат <?php echo «hello, world» ?> (если не считать бутстраповского шрифта), но теперь это современное приложение по всем канонам. Ну, скажем так, почти по всем канонам (не хватает тестов, проверок ошибок и много чего еще), но я уже задолбался это делать :)
В последнее время сильно участились споры «зачем нужен php, если есть java». Уж не знаю, кто прав, а кто нет, холивары — дело такое. Но в каждом споре один из аргументов в пользу php — это простота для новичков. Как мне кажется, этот аргумент уже давно не валиден, что я и хотел показать этой статьёй. Новичку все равно придется кучу всего узнать и 100500 конфигов настроить: фреймворки (очень похожие на фреймворки java), базы данных, linux, javascript со всем своим зоопарком, верстка, http-протокол, различный тулинг и многое-многое другое. Даже если это не SPA.
Upd. Статья уходит в глубокий минус, но я не собираюсь менять мнение. Оно примерно такое:
1) SPA всё больше проникает в наш мир, и надо это уметь, хотя бы в общих чертах.
2) Без фреймворков не построишь хорошее современное приложение.
<?php
echo '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) и стираем там всё, заменяя на:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="root"></div>
<script src="js/bundle.js"></script>
</body>
</html>
Т.е. всё будет лежат в 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<GreetingsProps, undefined> {
componentDidMount() {
this.props.onMount();
}
render() {
return (
<h1>{this.props.text}</h1>
);
}
}
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(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById("root")
);
Примерно здесь происходит следующее:
- Первоначальное состояние системы —
{isReady: false, text: ''}
. - Создан reducer под названием app, который умеет обрабатывать действие FETCH_GREETING и возвращать новое состояние системы.
- Создан store для обработки состояний.
- Всё отрендеривается в элемент, который мы прописали во вьюхе
<div id="root"></div>
Ах да, совсем забыл. Конфиг вебпака:
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-коде не создано ни одной сущности. Давайте исправим положение:
<?php
// src/AppBundle/Entity/Greeting.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="greetings")
*/
class Greeting
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
*/
private $id;
/**
* @ORM\Column(type="string", length=100)
*/
private $greeting;
public function getId()
{
return $this->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 echo «hello, world» ?> (если не считать бутстраповского шрифта), но теперь это современное приложение по всем канонам. Ну, скажем так, почти по всем канонам (не хватает тестов, проверок ошибок и много чего еще), но я уже задолбался это делать :)
Выводы
В последнее время сильно участились споры «зачем нужен php, если есть java». Уж не знаю, кто прав, а кто нет, холивары — дело такое. Но в каждом споре один из аргументов в пользу php — это простота для новичков. Как мне кажется, этот аргумент уже давно не валиден, что я и хотел показать этой статьёй. Новичку все равно придется кучу всего узнать и 100500 конфигов настроить: фреймворки (очень похожие на фреймворки java), базы данных, linux, javascript со всем своим зоопарком, верстка, http-протокол, различный тулинг и многое-многое другое. Даже если это не SPA.
Upd. Статья уходит в глубокий минус, но я не собираюсь менять мнение. Оно примерно такое:
1) SPA всё больше проникает в наш мир, и надо это уметь, хотя бы в общих чертах.
2) Без фреймворков не построишь хорошее современное приложение.