Why PHP Can Make You a Better Web Developer (Part 1)

Harry Zhu
8 min readJul 5, 2024

--

I graduated from college with my computer science degree. I have been the sole developer at my college’s marketing department. I have taken numerous classes on web development, but only 1 that made us write in PHP.

I get it.. PHP sucks! But it is so fundamentally important to learn as a web developer in 2024.

Originally, this project was done for a Client-Server Systems course at George Fox University. However, the original specification was basic and boring.

I like to be unique so people can’t make the excuse that “it’s just a class project and that it’s not original” because people like to judge I guess.

Before we dive into the technical details, let me share why I believe this project is more than just a class assignment. It’s a stepping stone towards understanding real-world web development challenges.

Anyways, let’s get right into it!

System Architecture and Design

I decided to use PHP and a service-oriented architecture to organize the backend. Here’s what the overall system design looks like and it’s pretty simple!

This architecture not only demonstrates modern web development practices but also showcases how PHP can be integrated into a cutting-edge tech stack.

By using PHP for the backend services, we’re leveraging its strengths in server-side processing while benefiting from the scalability of serverless deployments.

This diagram illustrates the architecture of a modern web application deployed on Vercel. The system is divided into two main components: the Backend Server and the Frontend Client.

Backend Server: At the core of the backend is a Router, which directs incoming requests to the appropriate services. The backend is composed of several key services:

  1. Auth SDK Service: Handles user authentication and authorization
  2. Feed SDK Service: Manages the content feed
  3. API Services: Separated into User, Post, and Comment services, each handling specific database operations
  4. DatabaseService: Acts as a centralized interface for database operations

All these API services interact with a Digital Ocean SQL Server for data persistence. (Thank you Github Education Pack for $200 in credits!)

Frontend Client: The frontend is built using React, a popular JavaScript library for building user interfaces. It communicates with the backend through the Router.

Deployment: The entire application is deployed on Vercel, a cloud platform for static and serverless deployment, ensuring scalability and performance.

Fun fact: All APIs deployed on Vercel is actually a separate serverless function!

This architecture demonstrates a clear separation of concerns, with distinct services for different functionalities, promoting modularity and maintainability. The use of a centralized Database Service is for an efficient database management and connection pooling.

Database Design

I started with designing the database. There are three tables: Users, Posts, Comments. The database is currently hosted on a Digital Ocean bucket.

  • Users include these attributes: ID, Username, Password Hash
  • Posts include these attributes: ID, Content, Extra JSON body, Post Date, Post Author
  • Comments include an ID, Post ID, Content, and Comment Date

Choosing the right database structure is crucial. It sets the foundation for your entire application. I spent considerable time thinking about the relationships between users, posts, and comments to ensure scalability.

While PHP might seem outdated to some, its robust database handling capabilities make it an excellent choice for learning these fundamental concepts.

The simplicity of PHP’s database interactions allows developers to focus on database design principles without getting bogged down in complex ORM setups, but I do include models of what a post, comment, or user is in my PHP code to quickly validate responses from database operations.

Database Connectivity

Now, let’s narrow in and focus on the one of the most critical aspects of any web application: database connectivity. I implemented a singleton pattern to manage our database connection efficiently.

This is how we’re going to connect to our database, and this will use a single database instance on a singleton pattern!

class DatabaseService {
private static $db = null; // DB Connection instance

// Ensuring single database instance
private function __construct() {}

// Connect to the postgres instance and store it
public static function database() {

if (self::$db == null) {

$conn_uri = "pgsql:host=" . $_ENV['DB_HOST'] . ";port=" . $_ENV['DB_PORT'] . ";dbname=" . $_ENV['DB_NAME'];

// Creates a new database connection with the parameters
try {
self::$db = new PDO($conn_uri, $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (PDOException $err) {
echo $err->getMessage();
}
}

return self::$db;
}
}

You might wonder why I chose a singleton pattern here. It’s all about resource management and ensuring we don’t open multiple unnecessary database connections.

Internal API Services

With our database connection sorted, let’s look at how we can interact with it. I created separate services for different functionalities to keep the code organized and maintainable.

And here’s an example of how we can use this Database Service. This is our User Read Service.

class UserReadService {

public static function getUsername($id) {
$stmt = DatabaseService::database()->prepare(
"SELECT username FROM user WHERE id = :id;"
);
$stmt->bindParam(":id", $id);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['username'] : null;
}

public static function getAllUsersFromDB() {
$stmt = DatabaseService::database()->prepare(
"SELECT DISTINCT id, username from user;"
);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}

This modular approach to service design is not unique to modern frameworks. PHP’s flexibility allows us to implement these patterns, preparing developers for working with more complex systems in the future. It’s a testament to PHP’s adaptability in modern web development paradigms.

Internal SDKs

And this our wrapper for the above service that includes proper response bodies that incorporates the results and message of the request.

class AuthService {
[...]

public function retrieveAllUsers() {
$result = UserReadService::getAllUsersFromDB();
if (isset($result)) {
return json_encode([
"results" => $result,
"message" => "Success"
]);
} else {
return json_encode([
"results" => null,
"message" => "Failed"
]);
}
}

[...]
}

Error handling and proper response formatting are often overlooked in student projects. However, they’re crucial in real-world applications for debugging and providing a smooth user experience.

Now, here’s where things get interesting. I decided to challenge myself by creating a custom router. This approach might seem unconventional, but it’s an excellent way to understand how modern frameworks handle routing under the hood.

Custom Routing

Custom routing might seem unnecessary in an era of framework-driven development, but understanding how routing works under the hood is invaluable.

This knowledge becomes crucial when optimizing performance or troubleshooting issues in production environments, regardless of the technology stack.

Another thing that stands out in this project is the router that I cooked up. Typically, Apache uses directory indexing to direct the client to certain resources like APIs and HTML template pages.

I made a shortcut. Instead of creating separate files, I programmatically parse out the input to retrieve such paths.

class Router {
private $feed; // Blog Feed CRUD Service
private $authenticator; // Authentication Service

// Constructor for the Router that initializes the blog controller service
// that performs CRUD operations for posts and comments
public function __construct() {
$this->feed = new FeedService();
$this->authenticator = new AuthService();
}

public function handleRequest() {
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$token = $this->getTokenFromReqBody();

// Transforms string into array
$uriArray = explode('/', $uri);

// Ensure URI starts with /api/v1
if (substr($uri, 0, 7) !== '/api/v1') {
header('HTTP/1.1 400 Bad Request');
echo json_encode([
'status' => 'Invalid endpoint'
]);
exit;
}

$startingIndex = 3; // which is after "/api/v1"

// Skip past primary domain and "/api/v1" and prepend '/'
$base_uri = '/' . $uriArray[$startingIndex];;

// Secured endpoints
$endpoints_to_gatekeep = ['/posts', '/comments', "/users", "/profile"];

$is_secured = in_array($base_uri, $endpoints_to_gatekeep);

// Check for token on secured endpoints
if ($is_secured && !$this->authenticator->isAliveToken($token)) {
http_response_code(401);
echo json_encode(['message' => 'Unauthorized']);
exit(1);
}

if ($method == "POST" && $base_uri == "/login") {
echo $this->authenticator->login();
}

// Add comment to a post by identifier
// POST /posts/:id/comments
elseif ($method == 'POST' && $base_uri == "/posts" && is_numeric($uriArray[$startingIndex + 1])
&& $uriArray[$startingIndex + 2] == "comments"
) {
echo $this->feed->addComment();
}

else {
http_response_code(404);
echo json_encode([
'error' => "Resource not found",
]);
exit(1);
}

return "Success";
}

[...]
}

Implementing this router was both challenging and rewarding. It gave me a deeper appreciation for the complexities that web frameworks handle behind the scenes.

I was thrilled when my professor recognized the innovation in this approach. It’s always satisfying when your efforts to go above and beyond are acknowledged.

She even recognized this as a shortcut that modern libraries do nowadays with Flask and Node. It’s a great flex to know PHP in 2024 even though no one cares about it anymore. (Vulnerabilities, hello?)

Reflections on PHP

PHP is great language to force yourself to learn the fundamentals of web server routing and resource handling. Forget the frameworks — learn the basics first!

While PHP might not be the trendiest language in 2024, this project reinforced my belief that understanding core web development concepts is invaluable. It’s not just about using the latest frameworks; it’s about grasping the fundamental principles that power the web.

While it’s true that PHP has had its share of security concerns, it’s important to note that many of these issues have been addressed in recent versions.

Modern PHP, when used correctly, can be as secure as any other language. The key lies in understanding potential vulnerabilities, catching up with OWASP Top 10 issues in web applications, and implementing best practices.

While PHP may not be the go-to language for new projects in 2024, the principles and patterns we’ve explored here are foundational to web development.

Whether you’re using PHP, Node, Scala, Go, Rust, Python, or any other server-side language, understanding these concepts will make you a more versatile and insightful engineer.

The next time you encounter a complex web application, you’ll have a deeper appreciation for what’s happening behind the scenes — and that’s the true value of learning PHP in today’s tech landscape.

Deployment

If you stuck around for this long, check out my production ready website at blog.harryzhu.com and register with a username and password.

Bye bye!

See in you part two, when I talk about the frontend design decisions!

Spoiler Alert: I’m not much of a frontend designer, but I do understand it, having been a web developer at my school’s marketing office and taken a course on Human-Computer Interaction.

Follow me for more content about tech, post-grad life, and security writeups. If you’re also in the world of helping others, connect with me on LinkedIn!

I am a software developer with a passion for engineering secure, performant, and creative software and a heart for telling my stories and digital manifestations.

--

--

Harry Zhu

harryzhu.com / Computer Science 2024 Graduate / Software Development / Technology / Post-College Life / Independence / Religious Deconstruction