Let’s get straight to the short answer: PHP supports AND/OR instead of &&/||, but because they have lower precedence, they can cause incorrect wait behavior.
What is Lower precedence?
Lower precedence is the same as “lower priority”; in this context, we are talking about the order in which an operator is evaluated after other operators in the same expression.
In other words: When PHP reads a line of code with multiple operators, it decides which parts to calculate first based on operator precedence.
Let’s look at some examples below to clarify the concept.
Using && / ||
$a=true;$b=false;$c=false;//waited expression$res= ($a&&$b) ||$c;//typed expression (it's the same above)$res=$a&&$b||$c;//practical exampleif($res) {echo'it\'s true';}else {echo'it\'s false';}//output: it's false
That makes sense, since express says: if variables a and b are true, or if variable c is true, show “true”, otherwise show “false”.
Using and / or
$a=true;$b=false;$c=false;//waited expression$res= ($aand$b) or$c;//typed expression (it isn't the same above)$res=$aand$bor$c;//practical exampleif($res) {echo'it\'s true';}else {echo'it\'s false';}//output: it's true
Due to and has very low precedence in PHP, lower than the assignment operator =, so the expression is interpreted as:
($res=$a) and$bor$c;
The rest of the expression, and $b or $c, is then evaluated and discarded because it is not assigned to anything. This logical operation uses the value of $a as its first operand (true).
Setting up Xdebug isn’t always straightforward. Depending on your setup, you might need to wire it into your IDE or get it running inside a Docker container.
So in this tutorial, we’ll walk through a simple, foolproof way to set up Xdebug on Ubuntu Linux.
Instalation
First of all, check your PHP version with php -v.
php-vPHP8.4.15 (cli) (built:Nov20202517:43:25) (NTS)Copyright (c) The PHP GroupBuiltbyDebianZendEnginev4.4.15,Copyright (c) Zend TechnologieswithZendOPcachev8.4.15,Copyright (c), by Zend TechnologieswithXdebugv3.4.7,Copyright (c) 2002-2025, by Derick Rethans
Ondřej Surý’s PPA
Now, if you installed PHP from Ondřej Surý’s repository, which is always the most up-to-date, you can install xdebug with.
Note that you need to change your xdebug version to match your PHP version.
sudoapt-getinstallphp8.4-xdebug
Default PPA
But, if you are using the standard version of PHP included with Ubuntu, which is usually outdated, simply run:
sudoapt-getinstallphp-xdebug
Run the php -v again and you should see the Xdebug information on the last line:
php-vPHP8.4.15 (cli) (built:Nov20202517:43:25) (NTS)Copyright (c) The PHP GroupBuiltbyDebianZendEnginev4.4.15,Copyright (c) Zend TechnologieswithZendOPcachev8.4.15,Copyright (c), by Zend TechnologieswithXdebugv3.4.7,Copyright (c) 2002-2025, by Derick Rethans
Then, run the built-in web server and you will also see a warning:
php-S127.0.0.1:8000[Sat Nov 2212:00:22 2025] PHP Warning: JIT is incompatible with third party extensions that override zend_execute_ex(). JIT disabled. in Unknown on line 0[Sat Nov 2212:00:22 2025] PHP 8.4.15 Development Server (http://127.0.0.1:8000) started
This warning is normal when you’re using Xdebug on PHP 8.1+ (including 8.4). Nothing is broken… So, what this warning means?
Xdebug overrides the internal zend_execute_ex()function, which the PHP Just-In-Time compiler (JIT) also needs, since both can’t run at the same time, PHP automatically disables JIT.
This scenario, with no JIT and using Xdebug, is generally satisfactory for a development environment.
Usage
Step Debugging
Xdebug’s step debugger allows you to interactively walk through your code to debug control flow and examine data structures.
Configuring Step Debugging
On the linux shell, run the command below to find the current php.ini file.
Then, change (or include) the setting to xdebug.mode=debug, in case of inclusion, you can put it at the bottom of the file.
In setups where PHP/Xdebug and your IDE all run on the same host, this is all you need to configure on the PHP and Xdebug side.
Command Line Debug Client
The command line debug client allows you to debug PHP scripts without having to set up an IDE. This might be useful if you don’t want to tie your xdebug to a specific IDE.
A binary for Linux, macOS, and Windows is available on the Xdebug downloads page. Here I will use Linux (x86_64).
It’s not always possible to update your Ubuntu version with just one command. In these cases, we need to set up a new server and migrate the files between them.
In this tutorial we will see how to copy your projects from Ubuntu 20.04 to Ubuntu 24.04 under WSL.
Listing your current WSL distros
PowerShell
wsl -l -v
You will see something like this:
PowerShell
Ubuntu-20.04 Running 2
Create the new Ubuntu distro
In the Windows Command Prompt ou Powershell run the command below, and you will create a new Ubuntu WSL instance, without replace the current.
PowerShell
wsl --updatewsl --install -d Ubuntu-24.04
List the current WSL distros again with wsl -l -v , you should see both the newest instance now.
PowerShell
Ubuntu-20.04 Running 2Ubuntu-24.04 Stopped 2
Copy files between instances
Someone could copy the files between the WSL instances using a Windows command like robocopy, but this method would be extremely slow.
Others might try copying the files from within the Linux instances, which could be more work than expected.
Instead, we will copy the files directly inside the Windows Explorer, the simpliest way.
In Windows Explorer, find the folder of both WSL, and just copy the files that you need from origin folder to destiny folder:
Easy, no? But…
The copies will be made. However, in the destination directory, we will have the Zone.Identifier files included; It is an NTFS Alternate Data Stream (ADS) automatically created by Windows.
We don’t need them on our Linux system, so let’s remove them. Enter in the newest Linux created typint this in cmd or PowerShell:
Okay, now we only have the same files from the old Ubuntu in the new Ubuntu.
Finally, we now just need to adjust the permissions that are set for the root user to work on the files contained in /home/fellipe/projects which currently are granted to the root user. For it type:
Bash
sudochown-Rfellipe:fellipe/home/fellipe/projects
Okay…
Now you have your files on a new server, ready to be worked on.
Remember, since you’re working from a new Ubuntu, the entire work environment will also need to be configured; things like the web server and PHP will need to be installed.
Sometimes xdebug can be tricky to install and configure, especially in scenarios where it wasn’t designed to work natively, such as running your project in WSL or Docker.
Today I will show you two simpler alternatives for debugging PHP, which will help you debug your project without wasting time on long installations.
Pre-installation
What we will use in this tutorial:
PHP 8.4
Composer 2.8
Note: Since all versions of PHP 7 and Composer 1 are no longer supported, avoid using them.
The first debug tool is the Kint. Think of Kint as a smart evolution of a var_dump(), but much more complete and interactive.
Instalation
./composerrequirekint-php/kint--dev
Usage with Composer
<?phpinclude'vendor/autoload.php';d('Dumped with Kint');$time=newDateTime();$data=['id'=>42,'status'=>'active','features'=>['debug','logging','profiling'],'settings'=>['theme'=>'dark','notifications'=>true]];$object=newstdClass();$object->title="My Symfony Clone";$object->author="Fellipe";$object->version=1.0;d($time,$data,$object);
Resources
Live search – This feature let you to fint values through complex data with ease.
Dump Data – you can unfold and explore instantly arrays, jsons, php objects, etc.
Generates code – Kint generates the exact code you need to access specific fields.
The next tool is the PHP Debug Bar, that displays a debug bar in the browser with information from PHP.
Installation
./composerrequire--devphp-debugbar/php-debugbar
Usage with Composer
<?phprequire'vendor/autoload.php';use DebugBar\StandardDebugBar;$time=newDateTime();$data=['id'=>42,'status'=>'active','features'=>['debug','logging','profiling'],'settings'=>['theme'=>'dark','notifications'=>true]];$dataJson=json_encode($data);$object=newstdClass();$object->title="My Symfony Clone";$object->author="Fellipe";$object->version=1.0;$debugbar=newStandardDebugBar();$debugbarRenderer=$debugbar->getJavascriptRenderer();// --- MESSAGES ---$debugbar["messages"]->addMessage("Hello world in debugbar!");$debugbar["messages"]->addMessage("Current time: ".$time->format("Y-m-d H:i:s"));$debugbar["messages"]->addMessage($data);$debugbar["messages"]->addMessage("JSON data: ".$dataJson);$debugbar["messages"]->addMessage($object);$debugbar["time"]->addMeasure("Script started",0,microtime(true));?><html><head><?php echo$debugbarRenderer->renderHead();?></head><body><h1>DebugBar Test</h1><?php echo$debugbarRenderer->render();?></body></html>
Resources
Search – This feature let you to fint values through complex data with ease.
Dump Data – you can unfold and explore instantly arrays, jsons, php objects, etc.
PHP Debug Bar have a lot of other nice features, you can see all them here: https://php-debugbar.com/
Conclusion
Both are useful tools for debugging. While Kint generates the appropriate code to access data, the PHP Debug Bar seems to make debugging easier with its toolbar.
Keep in mind that both have more advanced features that weren’t covered here. So, which one do you prefer? Try them out and decide for yourself!
Interfaces let you write code that depends on abstractions, not on concrete classes. When you programming this way, your code becomes more flexible, easier to test, easier to replace, and less dependent on specific implementations.
Let’s see it in some exemples below:
//Bad :-( //Programming to a concrete class with tightly coupled codeclassMySqlDatabase{publicfunctionconnect(){echo"Connected to MySQL";}}classUserService{privateMySqlDatabase$db;publicfunction__construct(){$this->db=newMySqlDatabase();// tightly coupled}publicfunctionloadUser(){$this->db->connect();}}
//Good :-)//Define an interface and have decoupled code, like Lego bricks!interface DatabaseConnection {publicfunctionconnect();}//Create multiple implementationsclassMySqlDatabaseimplementsDatabaseConnection{publicfunctionconnect(){echo"Connected to MySQL";}}classPostgresDatabaseimplementsDatabaseConnection{publicfunctionconnect(){echo"Connected to PostgreSQL";}}//Use only the interface inside your main classclassUserService{privateDatabaseConnection$db;// Any database that implements the interface can be injectedpublicfunction__construct(DatabaseConnection$db){$this->db=$db;}publicfunctionloadUser(){$this->db->connect();}}//Choose the implementation at runtime$service=newUserService(newPostgresDatabase());//Postgres: I Choose You!$service->loadUser();
Sometimes it isn’t always clear how to properly segregate your interface or to predict future changes;
So, if in the future your interface becomes too bloated, don’t hesitate to divide it into smaller and more focused sections, each representing a single responsibility.
When designing interfaces, you should always strive to be as precise as possible; since they describe what the parts of your system are capable of doing, the clearer that description is, the easier it will be to build, update, and maintain your software.
When a subclass inherits from a super class, the subclass will gain knowledge and access to all of the super classes attributes and methods as long as their access modifiers are not private.
This means that if you have multiple levels of inheritance, a subclass at the bottom of the inheritance tree can potentially have a way to access attributes and behaviors of all the super classes.
It’s “bad” not because inheritance is wrong, but because deep or excessive inheritance creates several structural problems in software design.
So, what can we do to avoid this?
Composing Objects Principle
You can use the composing objects principle to gain a high amount of code reuse without using inheritance. This principle states that classes should achieve code reuse through aggregation rather than inheritance.
Composing objects does not force you to try and find commonalities between two classes, and couple them together like with inheritance. Instead, you’re able to design classes that can work together without having to share anything between them. This flexibility will also help you if the system requirements change.
To clarify, let’s look at a code snippet below, comparing both modes.
What you can’t do with inheritance, once this relationship is fixed in the source code:
classUserServiceextendsLogger{}
Now, it’s not to say that inheritance should never be used. Composing objects in inheritance both have their place. Composing will give you better flexibility and less coupling while maintaining reusability, but that doesn’t mean it should always be used over inheritance.
You need to examine the needs of your system in order to determine which design principle is appropriate.
Do you have a set of related classes or unrelated classes?
What is a common behavior between them?
Do you need specialized classes to handle specific cases or do you simply need a different implementation of the same behavior?
This are the different kinds of questions that you’ll need to ask yourself when designing your software systems.
Extract it into a new method and replace the original code with a call to that method:
//Good :-)//Details moved to another functionfunctionshowProfile(){$this->showHeader();$this->printUserInfo();}functionprintUserInfo(){echo"User: ".$this->username;echo"Email: ".$this->email;}
If you change the user info, you only change one function, and you can reuse it.
Change temporary variables by a query
Sometimes, you create a variable just to store a value temporarily so you can use it later in your code. This makes the code harder to reuse, harder to clean up, and harder to extract into smaller methods:
//Bad :-(//Using a temp variable$totalCost=$this->cups*$this->pricePerCup;if($totalCost>20){return"You get a free cookie!";}else{return"No free cookie this time.";}
Create a small function that can calculate the value anytime you ask:
//Good :-)//Replace temp var with queryif($this->totalCost()>20){return"You get a free cookie!";}else{return"No free cookie this time.";}functiontotalCost(){return$this->cups*$this->pricePerCup;}
The total cost is calculated in one single place, where you can change the price or rule.
Change parameters that are repeated by an object
Your methods contain a repeating group of parameters:
Less repetition, cleaner code, and the ability to make changes within the object in a centralized way.
Preserve whole object
You write a method that needs several pieces of data from the same object, you pass three or four values taken from the same object for instance just to call another method:
A common problem that you’ll need to address when designing your systems is dependency.
Software dependency is basically about how much one part of your system relies on another.
When your software is highly coupled, trying to substitute one class or resource for something else is not easily done, or sometimes even possible. To address this issue, we use the Dependency Inversion Principle (DIP), its premise is:
“Depend on abstractions, not on concretions.”
The idea is simple, interfaces and abstract classes are considered high level resources and shouldn’t depend on concrete classes that are considered low-level modules.
An interface or abstract class defines a general set of behaviors, and the concrete classes provide the implementation for these behaviors.
<?php//Interfaceinterface Device{publicfunctionturnOn():void;publicfunctionturnOff():void;publicfunctionisOn():bool;}//Concrete Implementation Device 1 (Fan)classFanimplementsDevice{privatebool$on=false;publicfunctionturnOn():void{$this->on=true;}publicfunctionturnOff():void{$this->on=false;}publicfunctionisOn():bool{return$this->on;}}//Concrete Implementation Device 2 (Lamp)classLampimplementsDevice{privatebool$on=false;publicfunctionturnOn():void{$this->on=true;}publicfunctionturnOff():void{$this->on=false;}publicfunctionisOn():bool{return$this->on;}}//High-Level Class (SwitchButton) depending on the interfaceclassSwitchButton{privateDevice$device;publicfunction__construct(Device$device){$this->device=$device;}publicfunctiontoggle():void{if(!$this->device->isOn()){$this->device->turnOn();}else{$this->device->turnOff();}}}
Since building a flexible, reusable and maintainable system will prolong the life of your software, applying this design principle will help you achieve those goals.
Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
This design pattern uses a number of different design principles, to provide ease of access to complex classes, what we call subsystem. This is done by encapsulating the subsystem classes into a Facade class, and then hiding them from the client classes so that the clients do not know about the details of the subsystem.
When use it: Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.
<?php// --- Subsystems --- //classInventoryService{publicfunctioncheckStock(string$productId):bool{echo"Checking stock for product: $productId\n";// Simulated logicreturntrue;}publicfunctionreserveItem(string$productId):void{echo"Reserving item: $productId\n";}}classPaymentService{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing payment of $$amount via $method\n";// Simulated logicreturntrue;}}classShippingService{publicfunctionship(string$productId,string$address):void{echo"Shipping product '$productId' to $address\n";}}classNotificationService{publicfunctionsendConfirmation(string$email,string$message):void{echo"Sending confirmation email to $email: $message\n";}}// --- Facade --- //classOrderFacade{privateInventoryService$inventory;privatePaymentService$payment;privateShippingService$shipping;privateNotificationService$notification;publicfunction__construct(){$this->inventory=newInventoryService();$this->payment=newPaymentService();$this->shipping=newShippingService();$this->notification=newNotificationService();}/** * Simplified order process using the Facade */publicfunctionplaceOrder(string$productId,float$amount,string$paymentMethod,string$address,string$customerEmail):void{echo"Starting order process...\n";if(!$this->inventory->checkStock($productId)){echo"Product not available in stock.\n";return;}$this->inventory->reserveItem($productId);if(!$this->payment->processPayment($amount,$paymentMethod)){echo"Payment failed. Order canceled.\n";return;}$this->shipping->ship($productId,$address);$this->notification->sendConfirmation($customerEmail,"Your order for '$productId' has been successfully processed!");echo"Order completed successfully.\n";}}// --- Client Code --- //$orderFacade=newOrderFacade();$orderFacade->placeOrder( productId:'SKU12345', amount:199.99, paymentMethod:'Credit Card', address:'123 Business Street, Cityville', customerEmail:'customer@example.com');
As you can see, a facade class can be used to wrap all the interfaces and classes for a subsystem. It is your decision as to what you want to wrap.
Now let’s improve this code by using Dependency Injection (DI) in it.
Dependency Injection means giving an object its dependencies from something outside, instead of the object creating them by itself.
<?php// --- Interfaces --- //interface InventoryInterface {publicfunctioncheckStock(string$productId):bool;publicfunctionreserveItem(string$productId):void;}interface PaymentInterface {publicfunctionprocessPayment(float$amount,string$method):bool;}interface ShippingInterface {publicfunctionship(string$productId,string$address):void;}interface NotificationInterface {publicfunctionsendConfirmation(string$email,string$message):void;}// --- Concrete Implementations --- //classInventoryServiceimplementsInventoryInterface{publicfunctioncheckStock(string$productId):bool{echo"Checking stock for product: $productId\n";returntrue;}publicfunctionreserveItem(string$productId):void{echo"Reserving item: $productId\n";}}/** * Stripe payment service */classStripePaymentServiceimplementsPaymentInterface{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing payment of $$amount via $method\n";returntrue;}}/** * PayPal payment service */classPaypalPaymentServiceimplementsPaymentInterface{publicfunctionprocessPayment(float$amount,string$method):bool{echo"Processing PayPal payment of $$amount using $method\n";returntrue;}}classShippingServiceimplementsShippingInterface{publicfunctionship(string$productId,string$address):void{echo"Shipping product '$productId' to $address\n";}}classNotificationServiceimplementsNotificationInterface{publicfunctionsendConfirmation(string$email,string$message):void{echo"Sending confirmation email to $email: $message\n";}}// --- Facade --- //classOrderFacade{publicfunction__construct(privateInventoryInterface$inventory,privatePaymentInterface$payment,privateShippingInterface$shipping,privateNotificationInterface$notification){}/** * High-level method hiding all subsystem complexity */publicfunctionplaceOrder(string$productId,float$amount,string$paymentMethod,string$address,string$customerEmail):void{echo"Starting order process...\n";if(!$this->inventory->checkStock($productId)){echo"Product not available in stock.\n";return;}$this->inventory->reserveItem($productId);if(!$this->payment->processPayment($amount,$paymentMethod)){echo"Payment failed. Order canceled.\n";return;}$this->shipping->ship($productId,$address);$this->notification->sendConfirmation($customerEmail,"Your order for '$productId' has been successfully processed!");echo"Order completed successfully.\n";}}// --- Client Code --- //$inventory=newInventoryService();$payment=newPaypalPaymentService();$shipping=newShippingService();$notification=newNotificationService();$orderFacade=newOrderFacade($inventory,$payment,$shipping,$notification);$orderFacade->placeOrder( productId:'SKU12345', amount:199.99, paymentMethod:'PayPal', address:'123 Business Street, Cityville', customerEmail:'customer@example.com');
What changed?
Interfaces: define contracts for each subsystem. You can swap implementations easily (e.g., replace PayPalPaymentService with StripePaymentService).
Dependency Injection: the Facade doesn’t create its dependencies — they’re injected externally in client code.
Loose coupling: classes depend on abstractions, not concrete classes.
Testable: in unit tests, you can inject mock objects.