PHP OOP: Guestbook
November 21, 2020
Line breaks must be kept nl2br
<?php
$title = "Guest book";
require_once 'includes/header.php';
require_once 'class/Message.php';
require_once 'class/GuestBook.php';
$errors = null;
$success = false;
$guestbook = new GuestBook(__DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'messages');
if(isset($_POST['username'], $_POST['message'])) {
$message = new Message($_POST['username'], $_POST['message'], new Datetime());
if ($message->isValid()) {
$guestbook->addMessage($message);
$success = true; // Display a success message is successfully added to the 'messages' file (as a JSON string)
$_POST = []; // Empty the form;
} else {
$errors = $message->getErrors();
}
}
$messages = $guestbook->getMessages();
?>
<p class="lead mb-5">This guestbook system has been realized with <b>object oriented</b> PHP.</p>
<div class="row">
<div class="col-md-6 mb-4">
<h2 class="mb-4">Share what you think!</h2>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">Invalid form</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">Thank you for your message!</div>
<?php endif; ?>
<form action="" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" value="<?= isset($_POST['username']) ? htmlentities($_POST['username']) : '' ?>" class="form-control <?= isset($errors['username']) ? 'is-invalid' : '' ?>"></input>
<?php if (isset($errors['username'])): ?>
<div class="invalid-feedback"><?= $errors['username'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label>Your message</label>
<textarea name="message" rows="3" class="form-control <?= isset($errors['message']) ? 'is-invalid' : '' ?>"><?= isset($_POST['message']) ? htmlentities($_POST['message']) : '' ?></textarea>
<?php if (isset($errors['message'])): ?>
<div class="invalid-feedback"><?= $errors['message'] ?></div>
<?php endif; ?>
</div>
<button type="submit" class="btn btn-primary">Send a thought</button>
</form>
</div>
<div class="col-md-6">
<h2 class="mb-4">Posts from our users:</h2>
<?php if(!empty($messages)): ?>
<div>
<?php foreach ($messages as $message): ?>
<?= $message->toHTML() ?>
<?php endforeach ?>
</div>
<?php else: ?>
<p>No message yet. Be the first to write one!</p>
<?php endif; ?>
</div>
</div>
<?php require 'includes/footer.php'; ?>
<?php
require_once 'Message.php';
class GuestBook {
public $file;
public function __construct(string $file)
{
if (!is_dir(dirname($file))) { // Check if file's path folders exist (if not: create them)
mkdir(dirname($file), 0777, true);
}
if (!file_exists($file)) { // Check if file exists (if not: create it)
touch($file); // touch() function allows to create a new file.
}
$this->file = $file;
}
public function addMessage(Message $message): void
{
file_put_contents($this->file, $message->toJSON() . PHP_EOL, FILE_APPEND);
}
public function getMessages()
{
$content = trim(file_get_contents($this->file));
$messages = [];
if(!empty($content)) {
$lines = (array)explode( PHP_EOL, $content);
foreach ($lines as $line) {
$messages[] = Message::fromJSON($line);
}
}
return array_reverse($messages);
}
}
<?php
class Message {
const LIMIT_USERNAME = 3; // We create constants to be able to change them easily if needed later on.
const LIMIT_MESSAGE = 10;
private $username;
private $message;
private $date;
public function __construct(string $username, string $message, ?DateTime $date = null)
// Question mark before the value type (DateTime) to say: "if value is null, then value will have to be a DateTime object"
{
$this->username = $username;
$this->message = $message;
$this->date = $date ?: new DateTime(); // If $date = null, thencreate a new DateTime object
}
public function isValid(): bool
{
return empty($this->getErrors());
}
public function getErrors(): array
{
$errors = [];
if (strlen($this->username) < self::LIMIT_USERNAME) {
$errors['username'] = "Username is too short (3 characters min.)";
}
if (strlen($this->message) < self::LIMIT_MESSAGE) {
$errors['message'] = "Message is too short (10 characters min.)";
}
return $errors;
}
public function toJSON(): string
{
return json_encode([
'username' => $this->username,
'message' => $this->message,
'date' => $this->date->getTimestamp()
]);
}
public function toHTML()
{
$username = htmlentities($this->username);
$this->date->setTimeZone(new DateTimeZone('Europe/Paris')); // Change the timezone to Paris on display
$date = 'on ' . $this->date->format("F n, Y") . ' at ' . $this->date->format("g:i a");
$message = nl2br(htmlentities($this->message));
// nl2br() function cahnge the linebreaks that are not readen in HTML into <br> tags
return <<<HTML
<div class="card mb-2">
<div class="card-body">
<div class="mb-2">
<b>$username</b>
<span class="text-muted"><i>$date</i></span>
</div>
$message
</div>
</div>
HTML;
}
public static function fromJSON(string $json): Message
{
$data = json_decode($json, true);
$date = new DateTime("@" . $data['date']);
return new self($data['username'], $data['message'], $date);
// It's necessary to convert back into an array of objects because we'll use methods on $message in guestbook.php
// new DateTime("@$ts") is a shortened version of new DateTime instantiation + setTimestamp() --> ref: https://www.php.net/manual/en/datetime.settimestamp.php ('Notes' paragraph)
}
}