“Hello, (real) world!” in PHP in 2017
JavaScript PHPYou might think that it’s easy to write in PHP, and that “Hello, world” looks something like this:
Well, what else could one expect from a language with such a gentle learning curve? That’s exactly how it used to be. A long time ago. But now, in 2017, no one does it this way. Let’s see why, and try to build a more realistic hello-world application step by step. I must say, there are quite a few steps.
First, we need to realize that no one makes applications without a framework nowadays. If you simply write echo 'hello, world';
, your code is doomed to be bad forever (who will fix it for you?). Therefore, we’ll take a modern and popular framework, e.g. Symfony. But it’s better to create a database before installing the framework. Why the database? Well, I guess we shouln’t hardcode the “hello, world” string directly in the source code.
Database
They use PostgreSQL in 2017. In case you still don’t know how to install it, I will help:
sudo apt-get install postgresql
During the installation, Ubuntu will create the postgres
user, from which we can run the psql command with full permissions to the database.
sudo -u postgres psql
Now, let’s create a database user with a password (think of a complicated one).
CREATE ROLE helloworlduser WITH PASSWORD '12345' LOGIN;
And the database itself:
CREATE DATABASE helloworld OWNER helloworlduser;
We should also make sure pg_hba.conf is allowed to connect to the database from localhost (127.0.0.1). It should be something like this:
host all all 127.0.0.1/32 md5
Check the connection:
psql -h localhost -U helloworlduser helloworld
After entering the password, you will get to the terminal-based frontend to PostgreSQL. Let’s create a table now:
CREATE TABLE greetings (
id int,
greeting text,
primary key(id)
);
INSERT INTO greetings
(id, greeting)
VALUES
(1, 'Hello, world!');
Great! That’s it with the database. Now, let’s get down to the framework.
PHP-framework
I hope that everyone has composer
installed in 2017. That’s why we can proceed to the installation of the framework
composer create-project symfony/framework-standard-edition helloworldphp
During the installation, it will ask the connection parameters to the database:
host: 127.0.0.1
database_name: helloworld
database_user: helloworlduser
database_password: 12345
The rest is by default/optional.
We should only change the driver to driver: pdo_pgsql
in the config.yml config. Hopefully, you have a pdo_pgsql PHP extension installed on your computer.
Make sure everything works, more or less, by running:
cd helloworldphp
bin/console server:start
Symfony will run its own server that listens on port 8000. Plus, we can debug code on it. So, there should something like “It’s Symfony, blah-blah-blah” when you go to http://localhost:8000/ in the browser. Whew! Seems like that’s it. We’ve got a controller, now we’ll improve the view, then create a model, and “hello, world” is so close!
But…no. Sorry, not in 2017. We should do it SPA (single page application) nowadays. A PHP developer in 2017 cannot work without knowing JavaScript. We’re all full stack engineers now, which means helloworld must follow the rules.
Well, okay, okay, there’re also pure PHP back-end engineers but let’s consider a more common case.
JavaScript and Its Many Friends
Therefore, we find the view in Symfony (the default view is in app/Resources/view/default/index.html.twig) and replace everything there with:
I.e. everything will be in bundle.js: compressed javascript files together with all styles, and everything we need. How to create this bundle? You must write an application and then configure webpack to build that application.
So, we need Webpack, as we can’t write pure JavaScript in 2017 when TypeScript is obviously a trend. We also need to somehow convert TypeScript to a pure JS. This can be easily done with the help of webpack or similar libraries.
It goes without saying that no one writes in pure TypeScript either. We need a framework. One of the trendiest bindings today is react + redux
. As for the styling, we’re going to use the good old bootstrap (via sass, of course).
We’re going to need lots of js libraries. I hope you have nodejs and npm? Make sure you have the latest npm and then run:
npm init
For our dependencies we’ll use write something like this (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"
}
Then run
npm install
We should also run:
npm install webpack –g`
so that the webpack
command is available globally. Alas, there’s much more to come. Since we have typescript, it is also necessary to create tsconfig.json file, something like this:
{
"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"
]
}
Configs are okay now, so let’s get down to our TypeScript application.
First, we create a component to display our text:
// file 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;
Our SPA will fetch the text via Rest API. React is just view-components layer, but we also need the application logic and something to manage the state.
So, we’re going to use redux
, as well as a package to connect redux
and react
(react-redux
). That’s why we should also create a component that will create the Greetings component with the necessary properties and will be able to let the store of state know that a new action happened (data to display has been received)
Disclaimer: I just started to learn redux, so don’t judge me please.
This component looks, say, like this:
// file 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);
Well, and also the application entry point, creation of the redux store, the dispatcher, and so on. I made it all quite simple, but I guess it’s okay for the helloworld:
// loads styles 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")
);
Here is what’s happening here:
- The initial state of the system —
{isReady: false, text: ''}
. - A reducer named app is created. It can process the
FETCH_GREETING
action and return a new state of the system.
- A store is created to process states.
- Everything is rendered to the element we’ve added ro the view:
Oh, yeah, I forgot. The webpack config:
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;
Now, we can run webpack
or NODE_ENV=production webpack
(to get a minimized version of bundle.js
)
Pomodoro
I don’t know about you, but I’m sick and tired of writing this “Hello, world”. In 2017, we have to work efficiently, which implies that it’s necessary to take a break (the Pomodoro Technique, etc.) So, that’s exactly what I’m going to do.
[some time later…]
Let’s go on. We already know how to load code from /greetings/1
on the front-end, but the PHP-side isn’t ready at all.
Doctrine
Lot of time has already been spent, but we haven’t created any back-end entities yet. Let’s fix it:
id;
}
public function getGreeting()
{
return $this->greeting;
}
}
Super! There’s just a bit more.
REST
We need to make a simple REST API that can at least spit out json when you query GET /greetings/1
To do this, we will add a new method to the controller (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()]);
}
That’s it, we can run it now. “Hello, world!” is displayed on the screen. It looks almost like the result of (except for the bootstrap font), but now it’s a modern application according to all the canons. Well, almost according to all canons (except for tests, error checking, and much more), but I’m really sick and tired of doing it :)
Summary
Disputes like “Why do I need PHP if there’s Java?” have become more frequent nowadays. I don’t know who’s in the right, Holly Wars are tricky. But each dispute has an argument in favor of PHP — it’s easy for beginners. To my mind, this argument isn’t valid anymore, which is exactly what I was trying to say in this article. A novice programmer will have to learn lots of new things, as well as write tons of configs: frameworks (very similar to java frameworks), databases, linux, shminux, JavaScript with all its baggage, http-protocol, various tooling and many more. Even if it’s not SPA.
Even if my article doesn’t become popular, I’m not going to change my mind. It’s something like this:
- SPA comes more and more into our lives, and we should know how to deal with it, at least in general terms.
- You can’t build a decent modern application without frameworks.
→ You can find the the complete “Hello, World!” source code here.
Comments
David Rasch
Kabir