This project is meant to teach PhP fundamentals by creating a blog, step by step. It uses the Bootstrap framework and stores its data in JSON files.
This is how the final app looks like:
Follow these steps to continuously build a server-side Blog web app with PhP.
Create 3 model classes in the 'model' folder: Post, User and Category.
Write scripts in the scripts folder that generate test data in JSON format in the data folder.
Create controllers in the controllers folder that load and display the JSON data.
Create PHP/HTML views in the views folder. Write a view function that loads data from the corresponding JSON file and loads the proper view template to display it.
Chose a UI framework. We recommend:
- Bootstrap or Material Design Lite
- search for a demo layout page and copy its HTML code to the
views\posts.phpfile.
For example: AdminLTE starter page - open the
controllers\posts.phppage in your browser and check the result. - correct all the CSS, JS and image references using CDN links (for starters).
- remove all unnecessary UI elements and place your own labels.
Create a layout template. To do so, follow these steps:
- create a
views\partialssub-directory. - cut out the code of HTML
<head>, top navigation, sidebar and footer and paste it into their corresponding PhP files. requireall partials in theloadView()function.- create a
publicfolder andcss,img,jsandwebfontssub-folders. Download the corresponding resources to these directories and modify the links to use the local copies. - tell the PhP server to use the
publicdirectory with these parameters:
php -S localhost:8080 -t public
Implement a basic router:
- inside
public\index.php, insert this code:
$uri = parse_url($_SERVER['REQUEST_URI'])['path']; $routes = [ '/' => '../controllers/posts.php', '/posts' => '../controllers/posts.php', '/categories' => '../controllers/categories.php', '/users' => '../controllers/users.php' ]; if(array_key_exists($uri, $routes)) { require $routes[$uri]; } else { http_response_code(404); loadView("404"); } - add an error page:
view\404.php - add dynamic titles by adding a
$titleparameter to theloadView($view, $title)function. Pass a title in the corresponding controller. Example:loadView("posts", "Memories of our travels")
Display that title in the view by using the$titlevariable:<h1 class="m-0"><?= $title ?></h1> - place the proper page links in the top-navbar and sidebar:
<a href="/posts" class="nav-link">Posts</a> - highlight the current nav link in the sidebar by checking the
$viewvariable:<a href="/posts" class="nav-link <?= $view=='posts'? 'bg-indigo' : ''?>">
Implement the create post feature. For this:
- create the
controllers\postsdirectory and movecontrollers\posts.phptoconstrollers\posts\index.php. Correct all necessary links. - create the
views\postsdirectory and moveviews\posts.phptoviews\posts\index.php. Correct all necessary links. - create both
controllers\posts\create.phpandviews\posts\create.php. - in
public\index.php, add this route to the$routes:
'/posts/create' => BASE_PATH . '/controllers/posts/create.php' - in
views\posts\create.php, insert a HTML form with these fields:titleas simple input field,categories[]as multiple select box andbodyas textarea. You may use a rich text editor like TinyMCE.- you may add jQuery Validation to it.
- add
method="post" action="/posts/save"to the<form>tag. - add
'/posts/save' => BASE_PATH . '/controllers/posts/store.php'to the$routes.
- in
functions.php, write asaveData($key, $newEntry)function. - implement
controllers\posts\store.php:- create a new Post with the fields submitted in the form:
$newPost = new Post($_POST['title'], $_POST['body'], $_POST['userId'], $_POST['categories']) - save that post:
saveData('posts', $newPost) - redirect to the posts overview page:
header("location: /posts")
- create a new Post with the fields submitted in the form:
Implement the 'post detail' view:
- create a new dynamic rule in the router:
// check if `$uri` starts with 'posts' if (strpos($uri, "/posts/") == 0) { // parse any ID after the slash $postId = intval(substr($uri, 7)); if ($postId) { require BASE_PATH . '/controllers/posts/read.php'; } } - implement
controllers\posts\read.php:
Get the current post by its id:$post = $GLOBALS['posts'][$postId]. Load the detail view and pass the current post:loadView("posts/read", $post->title, ['post'=>$post]) - create
views\posts\read.php, showing the post's details.
Add aclosebutton to return to the overview.
Implement the 'delete post' feature:
- add a new rule to the routes:
'/posts/delete' => BASE_PATH . '/controllers/posts/delete.php' - in
views\posts\read.php, add a 'delete' button that is only visible if the current user is identical to the post's user:
if($_SESSION['currentUser'] == $post->userId) - upon button click, show a dialog that asks: 'Do you really want to delete this post?'.
- in the dialog, implement a small
<form method="post" action="/posts/delete">with a hidden<input name="postId" type="hidden" value="<?= $post->id ?>">. - create the
controllers\posts\delete.phpcontroller and insert this code:$postId = $_POST['postId']; unset($GLOBALS['posts'][$postId]); saveData('posts'); header("location: /posts");
Implement the 'edit existing post' feature:
- add a new rule to the routes:
'/posts/update' => BASE_PATH . '/controllers/posts/update.php' - remember the current user id in a session variable. To do so, insert this code at the beginning of
loadView():if (!isset($_SESSION['currentUser'])) { session_start(); // start with UserId = 2 $_SESSION['currentUser'] = 2; } - only a post's author should have the right to edit it. So in
views\posts\read.php, add an 'Edit' button that is only visible if the current user is identical to the post's user:
if($_SESSION['currentUser'] == $post->userId) - add a small
<form method="post" action="/posts/update">with a hidden<input name="postId" type="hidden" value="<?= $post->id ?>">. - create the
controllers\posts\update.phpcontroller and insert this code:$postId = $_POST['postId']; $post = $GLOBALS['posts'][$postId]; if (!$post) { header("location: /posts"); } loadView("posts/edit", "[Edit] " . $post->title, ['post' => $post]); - in
controllers\posts\create.php, create a new, emptyPostand pass it to the view:$newPost = new Post("", "", null, []); loadView("posts/edit", "New blog post", ['post' => $newPost]); - rename
views\posts\create.phptoedit.php. Insert these hidden fields right after the<form>:<input type="hidden" name="isExistingPost" value="<?=$post->userId ?>"> <input type="hidden" name="id" value="<?= $post->id ?>"> - fill all fields' values with the post's data:
<input name="title" value="<?= $post->title ?>"> <select name="categories[]"> <?php foreach ($GLOBALS['categories'] as $category) : ?> <option value="<?= $category->id ?>" <?= $post->categories && in_array($category->id, $post->categories) ? 'selected' : '' ?> > <?= $category->name ?> </option> <?php endforeach; ?> <textarea name="body"><?= $post->body ?></textarea> - modify the code in
controllers\posts\save.php:$isExistingPost = $_POST['isExistingPost']; // remove temporary field unset($_POST['isExistingPost']); if ($isExistingPost) { $postId = $_POST['id']; $post = $GLOBALS['posts'][$postId]; if ($post) { // update the existing post with the <form> fields $GLOBALS['posts'][$postId] = array_merge((array)$post, $_POST); saveData('posts'); } } else { $newPost = new Post($_POST['title'], $_POST['body'], intval($_POST['userId']), $_POST['categories']); saveData('posts', $newPost); } header("location: /posts");
Description will follow...
