Hello again - welcome in another Coding DIY in PHP
Today we will be making simple DM system
Requirements
In this tutorial I assume that you are familiar with basic HTML, CSS, JavaScript, PHP and SQL syntax
You also should know how to execute PHP and SQL code with XAMPP - I won't be covering those in this tutorial
So, when we have this said, Let's go
Coding
First thing we need to do is prepare our template
I was thinking about it for a bit, but I've decided I'll use the same database and login script as in first part of this "series"
But don't worry, you can download it's code from Github repository I'll add at the end (This code will also be there)
But let's go back to our lovely DM's
Template
I'll divide this project into following files -
Now secret.php
will contain textarea and input for username. Apart from that, we will have a big <section>
tag with listed usernames of people with whom user chatted before. Using buttons there, we will be able to fetch data from server to iframe on the website
We will also create new files called dm_send.php
and dm_receive.php
They will be responsible for (something no one expected) sending and receiving DM's
There will also be a bit of updates regarding database - but about it you will learn in specific section
So, to sum up - What will we learn?
- How to create DM system in PHP and SQL
- How to handle exceptions in PHP
- How to use ternary operator in PHP
- How to write SQL queries with
RIGHT JOIN
andUNION
- How to use
DISTINCT
operator in SQL
And also we will sharpen our skills in PHP, SQL, HTML and JS
So, let's start - we've covered every theoretical thing needed for now
Creating Front-end
So, our first task will be to create HTML structure - place it inside <body>
tag at secret.php
<h1>Welcome on secret page - Private DM's</h1>
<main>
<section class = "sendMsg">
<article class = "conversation">
</article>
<form method="POST" action="dm_send.php">
</form>
</section>
<section class = "friends">
<ul class = "friends-list">
</ul>
</section>
</main>
Next thing is creating actual form fields - new content of <form>
tag
<label> To:
<input type="text" name="to" placeholder="To:" class = "form-field"/></label>
<br/><br/>
<label> Content:<br/>
<textarea id="" cols="30" rows="5" maxlength ="249" name="message"></textarea><br/></label>
<br/>
<button type="submit" class = "btn-submit">Send</button>
Then, I want to add some, let's say, 'placeholder' for friends-list
ul
<ul class = "friends-list">
<li><button class="friend">Friend 1</button></li>
<li><button class="friend">Friend 2</button></li>
<li><button class="friend">Friend 3</button></li>
</ul>
Now, we need a last part of creating template - styles
I won't be styling it a lot - Just positioning. It's not a CSS course - but it doesn't mean we don't need it
Create new file called dm.css
- link it inside secret's <head>
tag
<link rel="stylesheet" href="dm.css" />
Then, let's align that HTML skeleton :)
main{
display: flex;
width: 94%;
justify-content: center;
padding: 3%;
height: 100%;
}
.sendMsg{
width: 50%;
text-align: center;
}
.friends{
width: 50%;
}
.conversation{
height: 50%;
}
.friend{
background-color: #fff;
border: none;
}
.friend:hover{
color: #333;
}
I've also edited buttons for friends - so they look like normal styled links, but their semantic role stays the same
I think that's it for now - second part is updating our database
Database update
Get to http://localhost/phpmyadmin
We need a new table - I'll call it messages
and it will contain 5 columns
-
id
- It's our primary key inint
format -
content
- As it says, typetext
orvarchar
-
destination
- id of a user to whom is message directed asint
-
sender
- id of a sender -
timestamp
- when was message sent. I'll usedatetime
format here
After all our table looks like this
Here is the query for creating this table
CREATE TABLE `login-form`.`messages` ( `id` INT NOT NULL AUTO_INCREMENT , `content` TEXT NOT NULL , `to` INT NOT NULL , `from` INT NOT NULL , `timestamp` DATETIME NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
whole database dump will also be available in mentioned Github repository
This table will stay empty - We will fill it in the future
Let's leave phpmyadmin
and focus on the most important part for us
Creating sending script
We should start our work on creating sending script - create and open dm_send.php
To begin with, we need to check if user is even logged in
<?php
session_start();
if(!isset($_SESSION['isLoged'])){
header("Location: index.php");
exit();
}
Then, we need to escape some dangerous characters from content and save it to a variable - never trust data coming from users
$to = htmlentities($_POST['to'], ENT_QUOTES, "UTF-8");
$content = htmlentities($_POST['message'], ENT_HTML5, "UTF-8");
I've used ENT_QUOTES
in $to
variable to avoid eventual conflicts when comparing usernames
I think you know, that sending message to non-existing user has no sense - let's check it
We need to make some changes in login.php
- I want to extract database credentials and dsn to separate file, and require it every time I need to use it - in my code it's named creds.php
Then, in both login.php
and dm_send.php
import that file
require_once "creds.php";
Also for login.php
I'd like to get our uid and login - right before isLoged
session variable add
$_SESSION['uid'] = $result['id'];
$_SESSION['login'] = $result['login'];
Okay, when it's ready we can run a query, and write that condition
$db = new PDO($db_dsn, $db_user, $db_pass);
$sql = "SELECT * FROM `users` WHERE `login` = ?";
$stmt = $db->prepare($sql);
$stmt->execute([$to]);
if(!$stmt->rowCount()){
$_SESSION['err'] = "That user does not exist";
header("Location: secret.php");
exit();
}
That's actually only condition we need to check - now let's instead of login
, put user's id
inside that database
We should start with writing a query - that's simple SELECT
$sql = "SELECT `id` FROM `users` WHERE `login` = ?";
Next - execute it
$stmt = $db->prepare($sql);
$stmt->execute([$to]);
At this point we just need to fetc that friend's id
$fid = $stmt->fetch(PDO::FETCH_ASSOC)['id'];
The most important part is left - sending actual message
First - prepare a insert
query
$sql = "INSERT INTO `messages` VALUES (?????)";
Then - secure yourself from unwanted errors with exceptions
try{
}catch(PDOException $e){
$_SESSION['err'] = "Server error - please try again later";
header("Location: index.php");
exit();
}
That's common implementation of exceptions in PHP
PDO is really clever - If something goes wrong, it will tell us. We just need to catch
it
We don't have to share with user technical aspects of that bug - just tell something wrong happened and if we need to, we can log it. But here I'll skip this part
Now - prepare and execute written query inside try
block
$stmt = $db->prepare($sql);
$stmt->execute([NULL, $content, $fid, $_SESSION['uid'], date("Y-m-d H:i:s")]);
$_SESSION['err'] = "Message sent";
header('Location: secret.php');
Last thing we need to do is to add error handling in secret.php
Under submit button I'll add
- Check if
err
variable is set - If true, show it's content to the user
- Unset that variable
<br/>
<?php
if(isset($_SESSION['err'])){
echo $_SESSION['err'];
unset($_SESSION['err']);
}
?>
Our sending is ready - now we need to code script for receiving messages
Receiving messages
When we can send some texts, we should also be able to receive them - create new file called dm_receive.php
First thing we need it uid
- check if it's even present
<?php
session_start();
if(!isset($_SESSION['uid'])){
echo "Server error - no user id available";
exit();
}
Then, when we have it, I can explain how it will work
- JavaScript callback put in
setInterval
function will request from server refresh of theiframe
element onsecret.php
. As GET parameter we will addlogin
of another account. - Server acknowledges request, and executes query and displays new content of the message.
- Messages window is refreshed
And in case we want to change the person, we will just update src
in iframe
with new login
This may sound complicated but don't worry - writing it's code is easier
First - assign friend's login
and user's uid
to variables.
$uid = $_SESSION['uid'];
$friend = htmlentities($_GET['friend'], ENT_QUOTES, "UTF-8");
$isValid = true;
And let's start executing queries
In first one - check if friend even exist (this sounds a bit schizophrenic actually). If yes, then get their userid
try{
require_once "creds.php";
$db = new PDO($db_dsn, $db_user, $db_pass);
$sql = "SELECT `id` FROM `users` WHERE `login` = ? OR `login` = ?";
$stmt = $db->prepare($sql);
$stmt->execute([$friend, $uid]);
if(!$stmt->rowCount()){
echo "This user does not exist";
$isValid = false;
}
$fid = $stmt->fetch(PDO::FETCH_ASSOC)['id'];
}catch(PDOException $e){
echo "Internal server error - try again";
}
An if we are sure that we are not talking to any imaginary friend - let's fetch the messages
Our most important step is to write a query - without it we won't do anything - It'll be a bit harder
$sql = "SELECT * FROM `messages` WHERE (`destination` = ? AND `sender` = ?) OR (`destination`= ? AND `sender` = ?) LIMIT 15";
At the and I've added LIMIT 15
- This indicates that I want only 15 newest results
Now, execute it
$stmt = $db->prepare($sql);
$stmt->execute([$fid, $uid, $uid, $fid]);
And fetch the results into array
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
I've used fetchAll
to be perfectly sure, that this will give me all of the results and not only the first one
Lastly we need to show it in some clever form - I've got a idea
foreach($messages as $message){
$datetime = $message['timestamp'];
$content = $message['content'];
$sender = $message['sender'] === $fid ? $friend : $_SESSION['login'];
echo<<<END
$content
<br/>
Sent at $datetime
<br/><br/>
END;
}
I'll focus on this one line
$sender = $message['sender'] === $fid ? $friend : $_SESSION['login'];
Here, I've used ternary operator - that's short form of if-else
statement. That line basically means
" If value under sender
key in $message array is equal to $fid then assign $friend variables value to $sender. Otherwise assign value from $_SESSION['login']
there "
Finally after I entered dm_receive.php
I've seen this
Note: For tests I've sent a bit of messages before, using our script. You can do this too if you want. I've also added one new user for realism - as I said whole dump will be available on Github
Now, let's go back to secret.php
- that's the last part
Merging everything together
When we go back to secret.php
we need to add 2 things
iframe
element, but as we will be adding it with JavaScript, some info for people with it disabled needs to be present. I'll usenoscript
tag for itList of friends.
We can start with creating that list - you can get rid of the placehoder inside ul
Creating list of friends
Most here is to run the query but select only unique items - that's how DISTINCT
in SQL work
So, let's write the query, this one will be a bit complicated - of course we need to put it in php tags. Don't forget closing one too
<?php
$sql = "SELECT DISTINCT `users`.`login`
FROM `messages`
RIGHT JOIN `users` ON `users`.`id` = `messages`.`sender`
WHERE `destination` = ?
UNION
SELECT DISTINCT `users`.`login`
FROM `messages`
RIGHT JOIN `users` ON `users`.`id`=`messages`.`destination`
WHERE `sender` = ?
";
Let's explain it a bit, as we used 3 new things here, just to send less queries to database - we are sending only one
UNION
- This operator is used to combine results of 2SELECT
statements. So here we will have combined results from records where logged user is either sender or receiver of the message - instead of 2 queries we have 1. How cool is thisRIGHT JOIN
- It's one of SQLJOIN
types.RIGHT
means that second table (Hereusers
) is more important than the first one.
What does it mean?
It means, that if we have a record from first table (messages) that doesn't match anything from the second table (users) then it will be skipped - that's exactly what we need
-
DISTINCT
- that's an operator used to select only unique items in the set - so if we would have 2 messages from the same user to us this operator will choose only the first one, skipping second.
Okay, this is covered - let's execute the query
require_once "creds.php";
try{
$db = new PDO($db_dsn, $db_user, $db_pass);
$stmt = $db->prepare($sql);
$stmt->execute([$_SESSION['uid'], $_SESSION['uid']]);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}catch(PDOException $e){
echo "Server error - Can't fetch friends </br>".$e;
}
?>
Last part is to show it - set buttons name
attribute as login from database
Like this
foreach($result as $user){
echo '<li><button class="friend" name = '.$user['login'].'>'.$user['login'].'</button></li>';
}
And we have it ready - now let's display it on secret.php
Creating iframe with messages
First put noscript
inside conversation
article tag
We need to be sure that user knows why there are no messages displayed
<noscript>Can't load iframe with messages - enable your JavaScript and refresh the page</noscript>
Now, when we have this secured. Let's write JavaScript
Create a file called conversation.js
and link it at the bottom of secret.php
right before </body>
<script src ="conversation.js"></script>
Now open that file and start writing
First, we need to get every button previously made into array of objects
const friendListButtons = document.querySelectorAll('.friend');
Then let's determine which conversation is active
let activeConversation = friendListButtons[0].name
We also need a function to generate our iframe
const createIframe = (conversation) =>{
document.querySelector('.conversation')
.innerHTML =
`<iframe src="dm_receive.php?friend=${conversation}" frameborder="0"></iframe>`
}
Now, the most important part - to each button add a event listener that will change activeConversation
variable and regenerate iframe
friendListButtons.forEach((el)=>{
el.addEventListener("click", ()=>{
activeConversation = el.name
createIframe(activeConversation);
})
})
Last part - let's refresh this iframe each 10 seconds
createIframe(activeConversation)
setInterval(()=>{createIframe(activeConversation)}, "10000")
I've also added one call for that function - so we don't have to wait at the beginning just to get the first conversation
And that's it - Congratulations, you've successfully created DM system in PHP and learned a lot of new things
Conclusion and some comments from the author
This was (I think) the longest article I've ever written - and I really enjoyed it
I've had a bit of false start with this article (Instead of save draft
I've clicked `Publish) but here is the finished version
Let me know what do you think - If there are any improvements I could make, feel free to suggest them in comments here or on Github
Speaking of Github here is the link for the repository (I won't be mad if you give me a star or a follow too ;))
Every account here has the same password - MyCoolPassword
What should I code next? Share your ideas and feedback here on dev.to and check out my other articles
Bye and see you soon
Top comments (0)