Role-Based Access Control (RBAC) in Modern Laravel

A Practical Guide for PHP & Back-End Developers

Modern Laravel applications almost always serve multiple user roles: administrators, managers, editors, customers, API clients, and more.
Hard-coding access checks quickly turns into a maintenance nightmare.

That’s why Role-Based Access Control (RBAC) remains one of the most effective and scalable authorization models — especially in Laravel-based backends.

This article is a hands-on guide to implementing RBAC in modern Laravel, focusing on:

  • clean architecture
  • real database migrations
  • practical authorization checks
  • security best practices

What Is RBAC (Quick Recap)

RBAC is an authorization model where:

User → Role → Permissions
  • A user has one or more roles
  • A role groups permissions
  • A permission represents a single allowed action

Instead of checking who the user is, the system checks what they are allowed to do.


Why RBAC Fits Laravel So Well

Laravel’s architecture aligns naturally with RBAC:

  • Middleware for route-level protection
  • Policies for domain-level rules
  • Gates for fine-grained permissions
  • Cache-friendly authorization checks
  • Clean separation from business logic

RBAC also integrates well with:

  • REST APIs
  • SPA backends
  • Admin panels
  • Multi-tenant systems

RBAC Data Model (Laravel-Friendly)

Core Tables

We’ll use a classic, explicit schema — no magic, easy to reason about.

users
roles
permissions
role_user
permission_role

Database Migrations

Roles Table

Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique(); // admin, editor, manager
    $table->string('description')->nullable();
    $table->timestamps();
});

Permissions Table

Schema::create('permissions', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique(); // posts.create, users.manage
    $table->string('description')->nullable();
    $table->timestamps();
});

Pivot: role_user

Schema::create('role_user', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->foreignId('role_id')->constrained()->cascadeOnDelete();
    $table->primary(['user_id', 'role_id']);
});

Pivot: permission_role

Schema::create('permission_role', function (Blueprint $table) {
    $table->foreignId('permission_id')->constrained()->cascadeOnDelete();
    $table->foreignId('role_id')->constrained()->cascadeOnDelete();
    $table->primary(['permission_id', 'role_id']);
});

Eloquent Models & Relationships

User Model

class User extends Authenticatable
{
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    public function hasRole(string $role): bool
    {
        return $this->roles->contains('name', $role);
    }

    public function canDo(string $permission): bool
    {
        return $this->roles->flatMap->permissions
            ->contains('name', $permission);
    }
}

Role Model

class Role extends Model
{
    protected $fillable = ['name', 'description'];

    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
}

Permission Model

class Permission extends Model
{
    protected $fillable = ['name', 'description'];
}

Authorization via Gates (Laravel Way)

Define permissions once — use them everywhere.

AuthServiceProvider

public function boot()
{
    Gate::before(function (User $user, string $ability) {
        return $user->canDo($ability) ?: null;
    });
}

Now Laravel automatically resolves:

$user->can('posts.update');

Route Protection with Middleware

Route::middleware('can:users.manage')->group(function () {
    Route::resource('users', UserController::class);
});

✔️ Clean
✔️ Declarative
✔️ Easy to audit


RBAC with Policies (Recommended)

PostPolicy Example

class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->can('posts.update');
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->can('posts.delete');
    }
}

Usage in controller:

$this->authorize('update', $post);

This keeps authorization logic out of controllers.


Seeding Roles & Permissions

Seeder Example

$admin = Role::create(['name' => 'admin']);
$editor = Role::create(['name' => 'editor']);

$permissions = [
    'posts.create',
    'posts.update',
    'posts.delete',
    'users.manage',
];

foreach ($permissions as $perm) {
    Permission::create(['name' => $perm]);
}

$admin->permissions()->sync(Permission::all());
$editor->permissions()->sync(
    Permission::whereIn('name', ['posts.create', 'posts.update'])->get()
);

Performance: Cache Permissions

Authorization runs a lot.

Best practice:

  • Eager load roles & permissions on login
  • Cache permission lookups
  • Invalidate cache on role updates

Example:

Cache::remember(
    "user_permissions_{$user->id}",
    now()->addHour(),
    fn () => $user->roles->flatMap->permissions->pluck('name')
);

Common RBAC Mistakes in Laravel

❌ Checking roles directly in controllers
❌ Mixing authentication with authorization
❌ Using role names in business logic
❌ Not testing policies
❌ No audit trail for permission changes


When RBAC Alone Is Not Enough

RBAC answers “can the user do X?”
It does NOT answer:

  • Does the user own this resource?
  • Is this allowed at this time?
  • Is this allowed in this context?

Combine RBAC with policy conditions:

$user->can('posts.update') && $post->author_id === $user->id

Final Thoughts

RBAC in Laravel is not about libraries — it’s about clean architecture.

A well-designed RBAC system gives you:

  • Predictable security
  • Clean controllers
  • Scalable authorization
  • Confidence during audits

If you’re building serious backend systems, RBAC is not optional — it’s foundational.

New Digital Product: Creational Design Patterns (GoF) for Modern PHP Developers

Over the years of working with PHP — from small scripts to large production systems — I’ve noticed one recurring issue:
object creation logic tends to get messy very quickly.

At first, everything looks simple:

$user = new User();

But as applications grow, this turns into:

  • too many new operators scattered across the codebase
  • tight coupling between classes
  • complex constructors with many dependencies
  • code that’s hard to test and even harder to change

This is exactly the problem space where creational design patterns shine.


Why Creational Design Patterns Matter

Creational design patterns help answer fundamental questions:

  • Who should be responsible for creating objects?
  • How can we decouple business logic from instantiation?
  • How do we create complex objects without unreadable constructors?
  • How do we keep code flexible and testable as requirements change?

These patterns are part of the classic Gang of Four (GoF) catalog and remain highly relevant today — especially for PHP developers working with frameworks, DI containers, and layered architectures.


What I’ve Created

I’ve just released a new digital guide:

Creational Design Patterns (GoF):
A Practical Guide for Modern PHP Developers

This is a concise, practical guide focused on understanding and applying the five GoF creational patterns:

  • Factory Method
  • Abstract Factory
  • Builder
  • Prototype
  • Singleton (including when not to use it)

The goal of this guide is simple:

Help PHP developers understand when and why to use these patterns — not just how to implement them.


What’s Inside the Guide

✔ Clear explanations without unnecessary theory
✔ Modern PHP-oriented examples
✔ Real-world context (not academic diagrams only)
✔ Decision guidance: use it / don’t use it
✔ Clean structure suitable for learning or quick reference

The product includes:

  • 📘 PDF version (ready to read and share)
  • 📝 Markdown source files (editable, reusable, future-proof)

Delivered as a ZIP bundle.


Who This Is For

This guide is useful if you are:

  • a junior or middle PHP developer building architectural foundations
  • a senior developer or team lead mentoring others
  • preparing for technical interviews
  • trying to write cleaner, more testable PHP code
  • working with frameworks like Laravel, Symfony, or custom architectures

Where to Get It

You can find the product here:

👉 https://ko-fi.com/s/e4858bc015

If you decide to check it out — feedback is always welcome.
This kind of work evolves best through real developer experience and discussion.

Composer: The PHP Dependency Management Tool You Need to Master

Composer logo

When you are working on modern PHP applications, any modern MVC frameworks, one tool stands at the core of nearly every project: Composer. Whether you’re starting a new Laravel project, integrating Symfony components, or pulling in a package for logging, Composer makes dependency management simple and reliable.

In this article, we’ll explore what Composer is, its history, the most commonly used commands, and how it has evolved over time.


What is Composer?

Composer is a dependency management tool for PHP. Instead of manually downloading libraries, including them in your project, and worrying about compatibility, Composer automates the process. It reads your project’s requirements (from

composer.json

) and installs the exact versions of libraries you need, ensuring compatibility and reproducibility.


Why Use Composer?

  • Saves time: No manual library management.
  • Handles versions: Ensures libraries are compatible with each other.
  • Standard in PHP: Virtually all modern PHP frameworks (Laravel, Symfony, Drupal, etc.) rely on Composer.
  • Autoloading: Automatically generates an autoloader for your classes.

Most Commonly Used Composer Commands

Here are the commands you’ll use most often in daily PHP development:

1. Install dependencies

composer install

Reads

composer.json

and installs all dependencies listed in

composer.lock

.


2. Add a new package

composer require vendor/package

Example:

composer require monolog/monolog

Adds Monolog as a dependency.


3. Remove a package

composer remove vendor/package

Removes the package and updates your project configuration.


4. Update dependencies

composer update

Updates all packages to the latest versions according to version constraints.


5. Dump the autoloader

composer dump-autoload

Rebuilds the autoloader after adding or modifying classes.


6. Check outdated packages

composer outdated

Lists which dependencies have newer versions available.


7. Run scripts

composer run-script script-name

Executes custom scripts defined in

composer.json

.


Version History of Composer

Composer has been around for over a decade, evolving alongside PHP itself:

  • 2011 – Composer was introduced by Nils Adermann and Jordi Boggiano.
  • 2012 – Official public release, quickly adopted by the Symfony and Laravel communities.
  • 2015–2019 – Composer became the standard tool for PHP projects worldwide.
  • 2020 (Composer 2.0) – Released with major performance improvements (up to 2x faster dependency resolution), reduced memory usage, and parallel downloads.
  • 2021–2023 (Composer 2.x) – Continuous improvements: enhanced platform checks, new plugin APIs, and better handling of complex dependency graphs.
  • Today (Composer 2.7, 2024–2025) – Actively maintained, powering nearly every PHP ecosystem, with focus on speed, security, and stability.

Example: Starting a New Project with Composer

Let’s say you want to start a new Laravel project. Instead of downloading files manually, you just run:

composer create-project laravel/laravel blog

That’s it — Composer fetches everything, sets up dependencies, and you can start coding immediately.


Conclusion

Composer has revolutionized PHP development. It’s no longer just a helper tool — it’s the backbone of the modern PHP ecosystem. Whether you’re working on a simple script or a large enterprise application, knowing how to use Composer efficiently is essential for every PHP developer.

If you haven’t already, try out some of the commands above in your next project — and enjoy the productivity boost.

Happy 34th Birthday, Linux

Happy 34th Birthday, OS Linux! 🎂🐧

On August 25, 1991, a young Finnish student named Linus Torvalds made a modest announcement on the Usenet group comp.os.minix. He wrote that he was working on “just a hobby, nothing big and professional” — a free operating system kernel for 386/486 AT clones. That “hobby” would soon grow into Linux, the foundation of one of the most influential open-source movements in history.

Today, in 2025, we celebrate 34 years of Linux — a system that powers everything from supercomputers, smartphones, and cloud servers to IoT devices, cars, and even space missions.


From Hobby Project to Global Phenomenon

What started as a personal learning project quickly gained traction. Developers from all over the world joined Linus, contributing code, testing features, and improving performance. Within months, Linux had transformed from a rough kernel into the cornerstone of a community-driven ecosystem.

By 1992, Linux was officially released under the GNU General Public License (GPL), ensuring its growth as free and open software. This decision shaped its future, making collaboration and transparency its DNA.


The Impact of Linux

  • Servers & Cloud: More than 90% of the world’s top 1 million servers run Linux.
  • Supercomputers: Every single system in the Top500 supercomputer list runs Linux.
  • Smartphones: Android, the most popular mobile OS, is built on the Linux kernel.
  • Innovation: From AI research to Kubernetes orchestration, Linux is everywhere.

Simply put: without Linux, modern computing as we know it wouldn’t exist.


Why Developers Love Linux

  1. Freedom & Flexibility — open code means endless possibilities.
  2. Community Support — thousands of contributors worldwide.
  3. Stability & Security — a proven track record in mission-critical environments.
  4. Learning & Growth — Linux remains the go-to platform for students, sysadmins, and developers alike.

Looking Ahead

As we mark Linux’s 34th anniversary, the journey is far from over. With advances in AI, edge computing, and open hardware, Linux continues to push boundaries and empower innovation. Its story is a reminder of what’s possible when curiosity, collaboration, and community come together.


Final Thought

From a Usenet post in 1991 to the backbone of the internet in 2025 — Linux has come a long way. Today, we don’t just celebrate software; we celebrate an idea that changed the world.

Happy Birthday, OS Linux! 🐧🎉 Thank you, Linux Torvalds.

Laravel Best Practices: Repository Pattern for Clean and Scalable Code

Imagine situation: when you start building applications with Laravel, it’s very tempting to place all logic directly in controllers. After all, Eloquent models make querying the database straightforward, so why not just use them everywhere?

But as your application grows, you’ll notice that this approach quickly leads to fat controllers, tangled logic, and difficulties with testing or making future changes. That’s where the Repository Pattern comes into play.

What is the Repository Pattern?

The Repository Pattern is a design pattern that acts as a mediator between your application logic and data access layer. Instead of querying Eloquent models directly inside controllers or services, you put that logic into dedicated repository classes.

This separation of concerns gives you:
• Cleaner controllers – focused only on handling HTTP requests and responses.
• Testability – you can mock repositories in your unit tests without touching the database.
• Maintainability – changes in data access (e.g., switching from MySQL to PostgreSQL, or even to an API) require minimal changes in your business logic.
• Reusability – one repository can be used by multiple services or controllers.

Example: Implementing a User Repository

Let’s look at a simple example.

Instead of this (in a controller):

public function login(Request $request)
{
    $user = User::where('email', $request->input('email'))->first();
    // authentication logic...
}

We’ll create a separate repository class:

// app/Repositories/UserRepository.php
namespace App\Repositories;

use App\Models\User;

class UserRepository 
{
    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }
}

Now, inject this repository into your controller:

// app/Http/Controllers/AuthController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\UserRepository;

class AuthController extends Controller
{
    public function login(Request $request, UserRepository $users)
    {
        $user = $users->findByEmail($request->input('email'));
        
        // handle authentication...
    }
}

Adding Interfaces for More Flexibility

To make it even more flexible, you can define an interface for your repository. This allows you to swap implementations easily (e.g., switch to an external API instead of a database).

// app/Repositories/UserRepositoryInterface.php
namespace App\Repositories;

use App\Models\User;

interface UserRepositoryInterface 
{
    public function findByEmail(string $email): ?User;
}

Then, implement it in your repository:

// app/Repositories/UserRepository.php
namespace App\Repositories;

use App\Models\User;

class UserRepository implements UserRepositoryInterface
{
    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }
}

And finally, bind the interface to the implementation in a service provider:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(
        \App\Repositories\UserRepositoryInterface::class,
        \App\Repositories\UserRepository::class
    );
}

Now your controller can depend on the interface, not the concrete class:

public function login(Request $request, UserRepositoryInterface $users)
{
    $user = $users->findByEmail($request->input('email'));
    // handle authentication...
}

When Should You Use It?
• Medium to large projects with complex business logic.
• Projects where testing is important (mocking repositories instead of hitting the database).
• Systems that might change data sources in the future.

For small projects, the Repository Pattern may feel like overkill. But in most real-world Laravel applications, it helps you keep code clean, testable, and future-proof.

Conclusion

The Repository Pattern is one of the most practical best practices you can apply in Laravel. It enforces separation of concerns, keeps controllers slim, improves testability, and makes your project more maintainable in the long run.

If you’re building anything beyond a toy app, consider introducing repositories early—it’s a small investment that pays off big as your project grows.

How to Find Large Files on Ubuntu Linux (and Clean Up Disk Space)

🧹 How to Find Large Files on Ubuntu 18.04 (and Clean Up Disk Space)

If you’ve ever run into the dreaded “No space left on device” error on your Linux system, you’re not alone. Over time, log files, package caches, and forgotten downloads can fill up your disk. In this guide, we’ll walk through several ways to identify large files and directories on your Ubuntu 18.04 system — using both command-line tools and interactive utilities.


📁 Why You Might Need This

  • Your system is running low on disk space.
  • You’re managing a server and want to optimize storage.
  • You want to clean up unused data before creating a backup.
  • You’re just curious what’s eating up all your GBs.

🔍 Method 1: Find Large Files Using

find

Let’s say you want to find files larger than 100 MB:

sudo find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k 5 -hr | head -n 20

What this does:

  • find /

    — search the whole system

  • -type f

    — look for files only

  • -size +100M

    — files larger than 100 megabytes

  • ls -lh

    — show file size in human-readable format

  • sort -k 5 -hr

    — sort files by size, descending

  • head -n 20

    — show only the top 20 results

🔐 Note: You may need

sudo

to access some directories.


📦 Method 2: Show Largest Directories with

du

To find out which folders are taking up the most space, use:

du -h --max-depth=1 | sort -hr

This lists the size of each subdirectory in the current folder. Example output from

/var

:

3.4G    ./log
1.2G    ./cache
5.0G    .

You can also search the whole system:

sudo du -ah / | sort -rh | head -n 30

This command lists the 30 biggest files and folders system-wide.


🖥️ Method 3: Use

ncdu

– The Friendly Disk Usage Viewer

Prefer something more visual and interactive? Try

ncdu

:

Install it:

sudo apt update
sudo apt install ncdu

Run it:

sudo ncdu /

Navigate the disk usage tree using arrow keys. Press

d

to delete files directly from within the tool (be careful!).


🧼 Bonus Tips: Clean Up with Built-in Tools

Clear APT cache:

sudo apt clean

Remove old kernels (Ubuntu auto handles this, but still):

sudo apt autoremove --purge

Check disk space usage at a glance:

df -h

🔚 Wrapping Up

Identifying and removing large files can dramatically improve system performance and free up valuable space. Whether you’re a sysadmin managing a fleet of servers or just cleaning up your dev laptop, these tools will help you stay in control.


💬 What’s your favorite way to clean up disk space on Linux?

Feel free to share your tips in the comments box below this blog post or contribute to the conversation on Reddit, X (Twitter), or your favorite dev community.

🔍 How to Verify Inserted Rows Using a CTE in SQL (WITH expected AS Pattern)

🔍 How to Verify Inserted Rows Using a CTE in SQL (

WITH expected AS

Pattern)

Whether you’re writing seeders, migration scripts, or integration tests — verifying that specific records were inserted into a table is a common and critical task.

Let me show you a clean and portable SQL technique using a Common Table Expression (CTE) to check that a set of expected rows exists in your database — without writing 6 separate

SELECT

queries or messing with application-level checks.


✅ Use Case

Imagine you just ran this

INSERT

into a table called

template

:

INSERT INTO template (service__id, id, os__id, ...)
VALUES
  ('wpsquared', 'cloudlinux8-wp2-v1', 'cloudlinux-8', ...),
  ('wpsquared', 'cloudlinux9-wp2-v1', 'cloudlinux-9', ...),
  ('wpsquared', 'cloudlinux9-wp2-v2', 'cloudlinux-9', ...),
  ('wpsquared', 'cloudlinux9-wp2-v3', 'cloudlinux-9', ...),
  ('plesk',     'ubuntu2204-plesk-v1', 'ubuntu-22.04', ...),
  ('cpanel',    'ubuntu2404-cpanel-v1', 'ubuntu-24.04', ...);

Now you want to confirm that all 6 rows exist.


🧠 The Pattern:

WITH expected AS (...)

Here’s the SQL snippet:

-- Step 1: Define the rows we expect
WITH expected AS (
    SELECT 'wpsquared' AS service__id, 'cloudlinux8-wp2-v1'  AS id UNION ALL
    SELECT 'wpsquared', 'cloudlinux9-wp2-v1'  UNION ALL
    SELECT 'wpsquared', 'cloudlinux9-wp2-v2'  UNION ALL
    SELECT 'wpsquared', 'cloudlinux9-wp2-v3'  UNION ALL
    SELECT 'plesk',     'ubuntu2204-plesk-v1' UNION ALL
    SELECT 'cpanel',    'ubuntu2404-cpanel-v1'
)

-- Step 2: Check which of them are missing
SELECT  e.service__id,
        e.id
FROM    expected e
LEFT JOIN template t
       ON  t.id = e.id
       AND t.service__id = e.service__id
WHERE   t.id IS NULL;

📋 Result

  • If the query returns 0 rows → ✅ You’re good! All expected rows are present.
  • If any rows are returned → ⚠️ These are missing or mismatched by primary key or foreign key.

🔄 Bonus: Quick Boolean Check

Want a compact version that tells you how many rows are missing?

WITH expected AS (
    -- same as above
)
SELECT COUNT(*) AS missing_count
FROM   expected e
LEFT   JOIN template t
       ON t.id = e.id AND t.service__id = e.service__id
WHERE  t.id IS NULL;

🧬 Cross-Database Compatibility

This pattern works on:

  • PostgreSQL
  • MySQL / MariaDB
  • SQLite
  • SQL Server 2005+ (with some minor syntax adaptation)

The key is that all of these databases support CTEs (Common Table Expressions) and

LEFT JOIN

logic.


🧑‍💻 When Should You Use This?

  • To test database seeders
  • To verify data migration scripts
  • During CI pipelines or test automation
  • For quick manual checks in staging or dev

📎 Final Tip

You can also extend this pattern to check not just existence, but field-level equality, version numbers, or even timestamps — just join on more columns and compare.


If you liked this trick, follow me or join my community [insert your Reddit, blog, or channel link here] where I share more real-world PHP + SQL tips like this one!

How to create a simple IP address tracker with geolocation in Symfony 6+

Do you want to know who visits your website, where they’re from, and when?
In this post, I’ll show you how to build a small Symfony project that:

  • Detects the visitor’s IP address
  • Retrieves geolocation data based on the IP
  • Stores this data in a database
  • Displays the IP, country, and city on the homepage

⚙️ Project Setup

Creating a new Symfony project:

composer create-project symfony/skeleton:"6.4.*" ip-tracker  
cd ip-tracker

Installing required dependencies:

composer require symfony/twig-bundle  
composer require symfony/http-client  
composer require symfony/orm-pack doctrine/doctrine-bundle  
composer require nesbot/carbon  
composer require symfony/maker-bundle --dev

🧱 Creating the Controller

File:

src/Controller/ClientIpController.php

<?php

namespace App\Controller;

use App\Entity\VisitorLog;
use App\Service\IpGeolocationService;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ClientIpController extends AbstractController
{
    #[Route('/', name: 'app_home')]
    public function index(Request $request, IpGeolocationService $ipService, EntityManagerInterface $em): Response
    {
        $ip = $request->getClientIp();
        $geo = $ipService->getGeolocation($ip);

        $log = new VisitorLog();
        $log->setIp($ip);
        $log->setCountry($geo['country'] ?? null);
        $log->setCity($geo['city'] ?? null);
        $log->setCreatedAt(Carbon::now());

        $em->persist($log);
        $em->flush();

        return $this->render('client_ip/index.html.twig', [
            'ip' => $ip,
            'geo' => $geo,
        ]);
    }
}

🌐 Geolocation Service

File:

src/Service/IpGeolocationService.php

<?php

namespace App\Service;

use Symfony\Contracts\HttpClient\HttpClientInterface;

class IpGeolocationService
{
    public function __construct(private HttpClientInterface $client) {}

    public function getGeolocation(string $ip): array
    {
        $response = $this->client->request('GET', "https://ipapi.co/{$ip}/json/");
        return $response->toArray();
    }
}

🗃 Creating an Entity for Logging

Command:

php bin/console make:entity VisitorLog

Fields:

  • ip

    : string

  • country

    : string (nullable)

  • city

    : string (nullable)

  • createdAt

    : datetime_immutable

Migrations:

php bin/console make:migration  
php bin/console doctrine:migrations:migrate

🖥 Template for Displaying Information to Users

File:

templates/client_ip/index.html.twig

{% extends ‘base.html.twig’ %}

{% block title %}Your IP Address is: {{ ip_address }}{% endblock %}

{% block body %}
<div class="ip-address-wrapper"><h1>Your IP Address is: {{ ip_address }}</h1></div>
{% if geo %}
<ul class="ip-info">
<li>Country: {{ geo.country }}</li>
<li>City: {{ geo.city }}</li>
</ul>
{% endif %}
{% endblock %}


📦 Project Dependencies

According to the list in

composer.json

, this project uses:

  • Symfony 6.4
  • Doctrine ORM
  • Twig
  • Carbon
  • HttpClient
  • MakerBundle (dev)

📝 Conclusion

Together we’ve built a full-featured IP logger on Symfony:

✅ Detects IP
✅ Retrieves geolocation
✅ Stores data in the database
✅ Displays it on the page

This functionality can serve as the foundation for a more complete analytics system, access control, or content personalization for your users.

How to Open Unverified Apps on macOS: MarkText Example

🚀 How to Open Unverified Apps on macOS: MarkText Example

If you’ve ever tried launching an app like MarkText on macOS and saw the warning:

“The application can’t be opened because the developer cannot be verified”

— don’t worry. You’re not alone, and yes, there’s a safe way around it.

In this article, I’ll show you how to launch unverified but trusted applications like MarkText, step-by-step.


🔒 Why Does macOS Block Some Apps?

macOS has a security feature called Gatekeeper that only allows apps from:

  • The Mac App Store
  • Verified developers registered with Apple

This is great for security but can block open-source tools and smaller projects — even if they’re totally safe.


✅ Option 1: Allow Once via System Settings

Here’s how to bypass the block and launch an app manually:

  1. Try to launch the app — you’ll get a warning popup.

  2. Open System SettingsPrivacy & Security

  3. Scroll down and find the section saying:

    “MarkText was blocked from use because it is not from an identified developer.”

  4. Click Allow Anyway

  5. Go back and right-click the app icon, then select Open

  6. This time, you’ll get a new popup with an Open button. Click it.

That’s it — your app will now launch!


🧑‍💻 Option 2: Use Terminal to Bypass Gatekeeper (Power Users)

If you’re comfortable with Terminal, here’s a quick command to remove the "quarantine" flag that macOS adds when downloading apps from the internet:

sudo xattr -rd com.apple.quarantine /Applications/MarkText.app

🧠 Tip: If your app is located somewhere else (like Downloads), adjust the path accordingly.

This command tells macOS: “Hey, I trust this app — stop treating it like malware.”


⚠️ A Word of Caution

Only bypass Gatekeeper for software from trusted sources like GitHub or official project websites. If you’re not 100% sure an app is safe, don’t ignore the warning — it’s there for your protection.


💡 Conclusion

You now know how to safely launch unverified apps like MarkText on your Mac. Whether you prefer the GUI route or the terminal method, you’re back in control.

Let your creativity flow — Markdown away! 😉

What is Docker and Why Developers Should Care

Docker is a tool that allows you to run your projects inside special isolated environments called containers.
Imagine packing your app together with all its dependencies — PHP version, MySQL, Redis, configs — and giving it to another developer or running it on a server without the classic “but it worked on my machine!”.

A container is like a mini-computer inside your system: it starts in seconds, can be copied or destroyed easily, and doesn’t eat up gigabytes of RAM like traditional virtual machines.


Why web developers love Docker:

  • Consistent environments. The same code runs identically on your local, staging, and production servers.
  • Fast onboarding. A new team member? One command and they’re ready to code.
  • Service isolation. PHP, MySQL, Redis, Nginx — each in its own container. Easily swappable and scalable.
  • Effortless deployment. A container is a portable unit ready to be deployed anywhere.
  • CI/CD integration. Works smoothly with GitLab CI, GitHub Actions, Jenkins, and more.

A few key terms to know:

  • Docker Engine – the core software that manages containers.
  • Image – a template used to create containers (e.g., php:8.2-fpm).
  • Container – a running instance of an image.
  • Dockerfile – a file that defines how to build your own image.
  • – Docker Compose – a file that defines and runs multi-container applications (like php, mysql, nginx, etc.).

In the next post, I’ll show how to install Docker on Ubuntu and other operating systems.
But for now, ask yourself: “How much time do I waste configuring dev environments?”
Docker might save you days.


If you found this post helpful — save it, share it, or follow the series. More practical stuff coming soon!