Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 84 additions & 34 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,85 @@
## PhP course
<h1 align="center">PHP Course: Build a Blogging Platform</h1>

This project is meant to teach PhP fundamentals by creating a blog, step by step. It uses the [Bootstrap](https://getbootstrap.com/docs/4.6/getting-started/introduction/) framework and stores its data in [JSON](https://en.wikipedia.org/wiki/JSON) files.
> This project is a step-by-step guide to building a blogging platform using PHP. It's designed to teach PHP fundamentals through practical application. The project uses the [Bootstrap](https://getbootstrap.com/docs/4.6/getting-started/introduction/) framework and stores data in [JSON](https://en.wikipedia.org/wiki/JSON) files.

This is how the final app looks like:
## Preview of the Final Application

Here's a preview of the final application:

![Travel Blog Screenshot](public/img/Screenshot.jpg)

Follow these steps to continuously build a server-side Blog web app with PhP.
## Table of Contents

- [Preview of the Final Application](#preview-of-the-final-application)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
- [1. Data model](#1-data-model)
- [2. Test data](#2-test-data)
- [3. Controllers](#3-controllers)
- [4. Views](#4-views)
- [5. UI framework](#5-ui-framework)
- [6. Layout template](#6-layout-template)
- [7. Router](#7-router)
- [8. 'Create post' feature](#8-create-post-feature)
- [9. 'Post detail' view](#9-post-detail-view)
- [10. 'Delete post' feature](#10-delete-post-feature)
- [11. 'Edit existing post' feature](#11-edit-existing-post-feature)
- [12. 'Create and edit users' feature](#12-create-and-edit-users-feature)

## Getting Started

To start the project, follow these steps:

1. Clone the repository to your local machine.
2. Open the project in your IDE.
3. Open a terminal and run `php -S localhost:8000 -t public`.
4. Open `http://localhost:8000` in your browser.

### 1. Data model

Create 3 model classes in the 'model' folder: `Post`, `User` and `Category`.

### 2. Test data
### 2. Test data

Write scripts in the `scripts` folder that generate test data in JSON format in the `data` folder.

### 3. Controllers

Create controllers in the `controllers` folder that load and display the JSON data.

### 4. Views

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.

### 5. UI framework

Chose a UI framework. We recommend:<br/>

1. [Bootstrap](https://getbootstrap.com/) or [Material Design Lite](https://getmdl.io/started/index.html)
2. search for a demo layout page and copy its HTML code to the `views\posts.php` file.<br/>
For example: [AdminLTE starter page](https://adminlte.io/themes/v3/starter.html)
For example: [AdminLTE starter page](https://adminlte.io/themes/v3/starter.html)
3. open the `controllers\posts.php` page in your browser and check the result.
4. correct all the CSS, JS and image references using CDN links (for starters).
5. remove all unnecessary UI elements and place your own labels.

### 6. Layout template

Create a `layout template`. To do so, follow these steps:
1. create a `views\partials` sub-directory.

1. create a `views\partials` sub-directory.
2. cut out the code of HTML `<head>`, top navigation, sidebar and footer and paste it into their corresponding PhP files.
3. `require` all partials in the `loadView()` function.
4. create a `public` folder and `css`, `img`, `js` and `webfonts` sub-folders. Download the corresponding resources to these directories and modify the links to use the local copies.
5. tell the PhP server to use the `public` directory with these parameters:<br/>
`php -S localhost:8080 -t public`

### 7. Router
Implement a basic router:
`php -S localhost:8080 -t public`

### 7. Router

Implement a basic router:

1. inside `public\index.php`, insert this code:<br/>
```

```php
$uri = parse_url($_SERVER['REQUEST_URI'])['path'];
$routes = [
'/' => '../controllers/posts.php',
Expand All @@ -57,22 +95,25 @@ Implement a basic router:
loadView("404");
}
```

2. add an error page: `view\404.php`
3. add dynamic titles by adding a `$title` parameter to the `loadView($view, $title)` function. Pass a title in the corresponding controller. Example:
`loadView("posts", "Memories of our travels")`<br/>
Display that title in the view by using the `$title` variable: `<h1 class="m-0"><?= $title ?></h1>`
`loadView("posts", "Memories of our travels")`<br/>
Display that title in the view by using the `$title` variable: `<h1 class="m-0"><?= $title ?></h1>`
4. place the proper page links in the top-navbar and sidebar: `<a href="/posts" class="nav-link">Posts</a>`
5. highlight the current nav link in the sidebar by checking the `$view` variable: `<a href="/posts" class="nav-link <?= $view=='posts'? 'bg-indigo' : ''?>">`

### 8. 'Create post' feature

Implement the `create post` feature. For this:

1. create the `controllers\posts` directory and move `controllers\posts.php` to `constrollers\posts\index.php`. Correct all necessary links.
2. create the `views\posts` directory and move `views\posts.php` to `views\posts\index.php`. Correct all necessary links.
3. create both `controllers\posts\create.php` and `views\posts\create.php`.
4. in `public\index.php`, add this route to the `$routes`:<br/>
`'/posts/create' => BASE_PATH . '/controllers/posts/create.php'`
`'/posts/create' => BASE_PATH . '/controllers/posts/create.php'`
5. in `views\posts\create.php`, insert a HTML form with these fields:
1. `title` as simple input field,
1. `title` as simple input field,
2. `categories[]` as multiple select box and
3. `body` as textarea. You may use a rich text editor like [TinyMCE](https://www.tiny.cloud/).
4. you may add [jQuery Validation](https://jqueryvalidation.org/) to it.
Expand All @@ -81,14 +122,16 @@ Implement the `create post` feature. For this:
6. in `functions.php`, write a `saveData($key, $newEntry)` function.
7. implement `controllers\posts\store.php`:
1. create a new Post with the fields submitted in the form:<br/>
`$newPost = new Post($_POST['title'], $_POST['body'], $_POST['userId'], $_POST['categories'])`
`$newPost = new Post($_POST['title'], $_POST['body'], $_POST['userId'], $_POST['categories'])`
2. save that post: `saveData('posts', $newPost)`
3. redirect to the posts overview page: `header("location: /posts")`

### 9. 'Post detail' view

Implement the 'post detail' view:

1. create a new dynamic rule in the router:
```
```php
// check if `$uri` starts with 'posts'
if (strpos($uri, "/posts/") == 0) {
// parse any ID after the slash
Expand All @@ -99,34 +142,38 @@ Implement the 'post detail' view:
}
```
2. implement `controllers\posts\read.php`:<br/>
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])`
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])`
3. create `views\posts\read.php`, showing the post's details.<br/>
Add a `close` button to return to the overview.
Add a `close` button to return to the overview.

### 10. 'Delete post' feature

Implement the 'delete post' feature:

1. add a new rule to the routes:<br/>
`'/posts/delete' => BASE_PATH . '/controllers/posts/delete.php'`
`'/posts/delete' => BASE_PATH . '/controllers/posts/delete.php'`
2. in `views\posts\read.php`, add a 'delete' button that is only visible if the current user is identical to the post's user:<br/>
`if($_SESSION['currentUser'] == $post->userId)`
3. upon button click, show a dialog that asks: 'Do you really want to delete this post?'.
4. in the dialog, implement a small `<form method="post" action="/posts/delete">` with a hidden `<input name="postId" type="hidden" value="<?= $post->id ?>">`.
`if($_SESSION['currentUser'] == $post->userId)`
3. upon button click, show a dialog that asks: 'Do you really want to delete this post?'.
4. in the dialog, implement a small `<form method="post" action="/posts/delete">` with a hidden `<input name="postId" type="hidden" value="<?= $post->id ?>">`.
5. create the `controllers\posts\delete.php` controller and insert this code:
```
```php
$postId = $_POST['postId'];
unset($GLOBALS['posts'][$postId]);
saveData('posts');
header("location: /posts");
```

### 11. 'Edit existing post' feature

Implement the 'edit existing post' feature:

1. add a new rule to the routes:<br/>
`'/posts/update' => BASE_PATH . '/controllers/posts/update.php'`
2. remember the current user id in a session variable. To do so, insert this code at the beginning of `loadView()`:
```
```php
if (!isset($_SESSION['currentUser'])) {
session_start();
// start with UserId = 2
Expand All @@ -137,7 +184,7 @@ Implement the 'edit existing post' feature:
`if($_SESSION['currentUser'] == $post->userId)`
4. add a small `<form method="post" action="/posts/update">` with a hidden `<input name="postId" type="hidden" value="<?= $post->id ?>">`.
5. create the `controllers\posts\update.php` controller and insert this code:
```
```php
$postId = $_POST['postId'];
$post = $GLOBALS['posts'][$postId];
if (!$post) {
Expand All @@ -146,17 +193,17 @@ Implement the 'edit existing post' feature:
loadView("posts/edit", "[Edit] " . $post->title, ['post' => $post]);
```
6. in `controllers\posts\create.php`, create a new, empty `Post` and pass it to the view:
```
```php
$newPost = new Post("", "", null, []);
loadView("posts/edit", "New blog post", ['post' => $newPost]);
```
7. rename `views\posts\create.php` to `edit.php`. Insert these hidden fields right after the `<form>`:
```
```php
<input type="hidden" name="isExistingPost" value="<?=$post->userId ?>">
<input type="hidden" name="id" value="<?= $post->id ?>">
```
8. fill all fields' values with the post's data:
```
```php
<input name="title" value="<?= $post->title ?>">
<select name="categories[]">
<?php foreach ($GLOBALS['categories'] as $category) : ?>
Expand All @@ -168,7 +215,8 @@ Implement the 'edit existing post' feature:
<textarea name="body"><?= $post->body ?></textarea>
```
9. modify the code in `controllers\posts\save.php`:
```

```php
$isExistingPost = $_POST['isExistingPost'];
// remove temporary field
unset($_POST['isExistingPost']);
Expand All @@ -187,5 +235,7 @@ Implement the 'edit existing post' feature:
}
header("location: /posts");
```

### 12. 'Create and edit users' feature

Description will follow...
2 changes: 1 addition & 1 deletion controllers/posts/create.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php
$newPost = new Post("", "", null, []);
loadView("posts/edit", "New blog post", ['post' => $newPost]);
loadView("posts/edit", "New blog post", ['post' => $newPost]);
13 changes: 9 additions & 4 deletions controllers/users/save.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php
//require_once BASE_PATH . "/model/User.php";
require_once BASE_PATH . "/model/User.php";

$isExistingUser = $_POST['isExistingUser'];
// remove temporary fields
Expand All @@ -8,14 +8,19 @@

if ($isExistingUser) {
$userId = $_POST['id'];
$user = $GLOBALS['posts'][$userId];
$user = $GLOBALS['users'][$userId];
if ($user) {
// update the existing user with the <form> fields
// Check if password was provided and hash it
if (!empty($_POST['password'])) {
$_POST['password'] = password_hash($_POST['password'], PASSWORD_DEFAULT);
}

// Update the existing user with the <form> fields
$GLOBALS['users'][$userId] = array_merge((array)$user, $_POST);
saveData('users');
}
} else {
$newUser = new User($_POST['name'], $_POST['email'], $_POST['password'], $_POST['avatarUrl']);
saveData('users', $newUser);
}
header("location: /users");
header("location: /users");
8 changes: 8 additions & 0 deletions data/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@
"password": "p@$$w0rd23!3",
"created": 1701457197,
"avatarUrl": "\/img\/avatar.png"
},
"4": {
"id": 4,
"name": "BobBob",
"email": "[email protected]",
"password": "$2y$10$39OtZNDIwycfnQrggvu8rOzj\/APxMLtlpNHl.QLsZJhITUkmj0L\/m",
"created": 1701892785,
"avatarUrl": "\/img\/avatar.png"
}
}
24 changes: 21 additions & 3 deletions model/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,30 @@ class Category

/**
* Creates a new blog category
* @param $name
* @param string $name
*/
public function __construct($name)
public function __construct(string $name)
{
$this->name = $name;
$this->id = self::$id_counter;
self::$id_counter++;
}
}

/**
* Get the ID of the category
* @return int
*/
public function getId(): int
{
return $this->id;
}

/**
* Get the name of the category
* @return string
*/
public function getName(): string
{
return $this->name;
}
}
41 changes: 33 additions & 8 deletions model/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ class Post
public $created;

/**
* Creates a new blog entry
* @param $title
* @param $body
* @param $userId
* @param $categories
* Creates a new blog post
* @param string $title
* @param string $body
* @param int|null $userId
* @param array $categories
* @return void
*/
public function __construct($title, $body, $userId, $categories = [1, 3])
public function __construct(string $title, string $body, int|null $userId, array $categories = [1, 3])
{
$this->title = $title;
$this->body = $body;
Expand All @@ -33,9 +34,33 @@ public function __construct($title, $body, $userId, $categories = [1, 3])
Post::$idCounter++;
}

public static function setIdCounter($idCounter)

/**
* Set the ID counter for the Post class
*
* @param int $idCounter The new value for the ID counter
* @return void
*/
public static function setIdCounter(int $idCounter): void
{
self::$idCounter = $idCounter;
}

}
/**
* Get the categories of the post
* @return array
*/
public function getCategories(): array
{
return $this->categories;
}

/**
* Set the categories of the post
* @param array $categories
*/
public function setCategories(array $categories): void
{
$this->categories = $categories;
}
}
Loading