Welcome to the guide on how to build web app using PHPy 2 framework.
It's recommended to use PHP & Nginx for serving apps based on PHPy. Install latest stable versions:
sudo apt install php-fpm nginx
Clone PHPy:
git clone git clone https://github.com/mrcrypster/phpy.git
Create empty folder for the new app:
mkdir /var/www/my-app
Init new app:
php phpy/phpy.php /var/www/my-app
This will generate app structure and show Nginx config to use for the app:
server { root /var/www/my-app/web; index index.php; server_name myapp; location / { try_files $uri /index.php?$args /index.php?$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; } }
Make sure fastcgi_pass points to your fpm socket and add to Nginx (reload configs with nginx -s reload).
Point browser to http://localhost (or other host configured, like localhost:88 in our case) and check if you see the following:
You'll find following files under my-app folder:
web/ # images, styles and other public resources goes here /index.php # entry point to process all app requests app/ # our app is implemented here /layout.php # global HTML layout (actions are rendered inside) /default.php # default action (for "/" requests) to process and render
Global app/layout.php file is used to render common app elements and resources (that appear on all pages). Let's edit it:
<?php return ['html' => [
':v' => 1, # this is css/js files cache versions
':title' => 'PHPy2 App', # HTML page title
'h1#logo' => 'My app', # <h1 id="logo">My app</h1>
'div.content' => phpy() # <div class="content">...</div>
]];
As you can see, there's a phpy() function call. What is does, is executes and renders nested action based on request URI.
When we call root / path of our app, default.php action is being called and rendered (inside layout.php). We'll edit it a little:
<?php return [
'h2' => 'I am default page',
'p' => 'It is ' . date('H:i') . ' now',
];
Now let's check how it all looks in the browser:
Request URIs are automatically mapped to actions. E.g. in order to process /test URI, we should create app/test.php action:
<?php return [
'p' => $_SERVER['REQUEST_URI'] # which will print current URI
];
Now let's look what http://localhost/test shows:
We can put actions into folders. E.g. creating app/docs/article.php will handle http://localhost/docs/article requests. Infinite level of folding is allowed.
We can also create default.php file under folders, which will be executed when URI is the same as folder path. E.g. let's create app/my/default.php action:
<?php return [
'p' => 'You are under "/my" folder'
];
Navigate our browser to http://localhost/my to see the following:
We can nest actions to keep files small, which is highly recommended. E.g. let's add app/my/menu.php action:
<?php return [
'ul' => [
['li' => ['a:/my' => 'My home']],
['li' => ['a:/' => 'Public home']]
]
];
In order to nest this action into my/default.php action we need to use phpy() function with an action we want to nest:
<?php return [
'div.menu' => phpy('/my/menu'), # menu will be rendered inside
# <div class="menu"></div> block
'p' => 'You are under "/my" folder'
];
Navigate our browser to http://localhost/my to see how nesting works:
We can pass variables to nested actions as a second phpy() argument:
<?php return [
'div.menu' => phpy('/my/menu', ['page' => 'default']),
# Now $page variable will be accessible in menu.php
];
Infinite level of nesting is allowed.
PHPy comes with Javascript layer to manipulate actions on the client side. Button elements are recommended to be used to trigger actions. Let's add a button to default.php action:
<?php return [
'h2' => 'I am default page',
'p#time' => 'It is ' . date('H:i') . ' now',
'button:/time' => 'Update time'
];
Now /time action will be called once button is clicked:
Let's create time.php action to render something in return once button is clicked:
<?php return [
'#time' => date('H:i:s')
];
PHPy will automatically find DOM element with id=time and place action rendered output to it:
Any number of DOM elements can be updated from single action:
<?php return [
'#time' => date('H:i:s'),
'#other' => 'Some other element content',
...
];
We can call custom Javascript functions as well. Let's add one more button that calls hi() function:
<?php return [
'...',
'button:hi()' => 'Say hi'
];
The Javascript hi() function itself can be defined anywhere, but it's recommended to place relevant JS implementations under separate JS files for each action. That's why we create app/default.js with the following code:
function hi() {
alert("Hi!");
}
By default, all JS files are automatically collected and loaded to app, so we don't have to do anything else before checking browser:
Forms are handled by PHPy in the same manner as buttons. We just declare form and specify endpoint action to process submitted data. Let's create a form in /my/default.php action
<?php return [
'div.menu' => phpy('/my/menu'),
'form:/my/submit' => [
'input:email' => '',
'submit' => 'Go',
'#result' => ''
]
];
This form will be submitted to /my/submit action. We have also created empty #result element which we'll use to render response from action. Let's declare /my/submit.php action:
<?php
$email = $_POST['email'];
return [
'#result' => 'You have submitted ' . $email . ' email'
];
After we fill form field and click "Go" button, we can see result rendered by server:
CSS is also automatically collected and loaded. It is recommended to put action-related styles into separate files and have global styles in layout.css file:
app/ /layout.css # General CSS styles ... /action.php # PHP action implementation /action.js # Javascript client-side handlers/listeners/... /action.css # CSS styles for this action
Let's add some styling to our lauout.css file:
body { padding: 1em; }
h1 { border-bottom: 1px solid #eee; padding: 0 0 .5em 0; }
ul { padding: 0; margin: 0 0 2em 0; }
ul li { display: inline-block; padding: 0; margin: 0 1em 0 0; }
form { background: #eee; text-align: center; padding: 2em; }
input { margin: 0 1em 0 0; }
Now everything feels a bit better:
If we need to process specific endpoint in a highly custom way, we can do that using phpy::on() in web/index.php file:
phpy::on('/test', function() {
echo 'Custom response!!!';
});
This callback function will be executed once http://localhost/test URI is called:
curl http://localhost/test Custom response!!!
If callback returns true then standard flow will be executed after function is finished:
phpy::on('/admin', function() {
if ( i_am_admin() ) {
return true;
}
else {
die('Access denied');
}
});
We can use regular expressions as well as stack multiple handlers:
phpy::on('/some.*/', function() { ... }); # all '/some*' endpoints
phpy::on('/some', function() { ... }); # single '/some' endpoint
Session are not started automatically, so we need to add session_start() to web/index.php if you plan you use sessions:
<?php
session_start(); # insert at the top of the file
...
Now standard PHP session becomes accessible in any part of the app:
$_SESSION['pages'] += 1;
User specific areas access control can be easily implemented using sessions and custom routing handlers:
phpy::on('/my.*/', function() {
return $_SESSION['user_id'] > 0 ? true : false;
});
This will allow access to all /my* endpoints only for users with declared $_SESSION['user_id'] variable (which should obviously be set within while processing signin form).