In this article, we will enhance the product catalog microservice we built earlier by integrating Xdebug for profiling, specifically tailored for development in Visual Studio Code (VS Code). We will include detailed setup instructions, ensuring a smooth experience while profiling your application.
Overview
Key Components
- PHP 8: The programming language for our microservice.
- MySQL: The database for storing product data.
- Redis: The caching layer for frequently accessed product data.
- Xdebug: A profiling tool for identifying performance bottlenecks.
Step 1: Setting Up Your Environment with DDEV
1. Install DDEV
Ensure DDEV is installed on your machine. Follow the installation instructions here.
2. Create a New DDEV Project
Open your terminal and create a new project directory:
mkdir product-catalog-microservice
cd product-catalog-microservice
Initialize DDEV:
ddev config
Choose "php" as the project type and select PHP 8.
Start the DDEV environment:
ddev start
3. Install Composer Dependencies
Inside the DDEV environment, run:
ddev composer require predis/predis
If you see isssue with predis/predis, setup redis with docker compose, refer to this article for more details.
Step 2: Setting Up the MySQL Database
Create the Database
DDEV automatically sets up a MySQL database for you. Access the MySQL shell with:
ddev mysql
Run the following commands to create the products table:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Insert Sample Data
Insert some sample products:
INSERT INTO products (name, description, price) VALUES
('Product A', 'Description for Product A', 10.00),
('Product B', 'Description for Product B', 20.00),
('Product C', 'Description for Product C', 30.00);
Google Ad 1
Step 3: Implementing the Microservice
Create the Entry Point
Create an index.php file in the web directory of your DDEV project (./web/index.php):
require 'vendor/autoload.php';
use Predis\Client;
class ProductCatalog {
private Client $cache;
private PDO $db;
public function __construct() {
$this->cache = new Client(['host' => 'redis']);
$this->db = new PDO('mysql:host=db;dbname=db', 'db', 'db'); // DDEV uses 'db' as the default MySQL host
}
public function getProducts() {
$cacheKey = 'product_catalog';
// Check cache
$cachedProducts = $this->cache->get($cacheKey);
if ($cachedProducts) {
return json_decode($cachedProducts, true);
}
// Fetch from database
$stmt = $this->db->query("SELECT * FROM products");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Store in cache
$this->cache->set($cacheKey, json_encode($products), 'EX', 3600); // Cache for 1 hour
return $products;
}
public function getProduct($id) {
$stmt = $this->db->prepare("SELECT * FROM products WHERE id = :id");
$stmt->execute(['id' => $id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function createProduct($name, $description, $price) {
$stmt = $this->db->prepare("INSERT INTO products (name, description, price) VALUES (:name, :description, :price)");
$stmt->execute(['name' => $name, 'description' => $description, 'price' => $price]);
// Clear cache
$this->cache->del('product_catalog');
return $this->db->lastInsertId();
}
public function updateProduct($id, $name, $description, $price) {
$stmt = $this->db->prepare("UPDATE products SET name = :name, description = :description, price = :price WHERE id = :id");
$stmt->execute(['id' => $id, 'name' => $name, 'description' => $description, 'price' => $price]);
// Clear cache
$this->cache->del('product_catalog');
return $stmt->rowCount();
}
public function deleteProduct($id) {
$stmt = $this->db->prepare("DELETE FROM products WHERE id = :id");
$stmt->execute(['id' => $id]);
// Clear cache
$this->cache->del('product_catalog');
return $stmt->rowCount();
}
}
// Handle requests
$requestMethod = $_SERVER['REQUEST_METHOD'];
$catalog = new ProductCatalog();
switch ($requestMethod) {
case 'GET':
if (isset($_GET['id'])) {
$product = $catalog->getProduct($_GET['id']);
header('Content-Type: application/json');
echo json_encode($product);
} else {
$products = $catalog->getProducts();
header('Content-Type: application/json');
echo json_encode($products);
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
$id = $catalog->createProduct($data['name'], $data['description'], $data['price']);
header('HTTP/1.1 201 Created');
echo json_encode(['id' => $id]);
break;
case 'PUT':
$data = json_decode(file_get_contents('php://input'), true);
$updated = $catalog->updateProduct($data['id'], $data['name'], $data['description'], $data['price']);
header('Content-Type: application/json');
echo json_encode(['updated' => $updated > 0]);
break;
case 'DELETE':
$id = $_GET['id'] ?? null;
if ($id) {
$deleted = $catalog->deleteProduct($id);
header('Content-Type: application/json');
echo json_encode(['deleted' => $deleted > 0]);
} else {
header('HTTP/1.1 400 Bad Request');
echo json_encode(['error' => 'Product ID is required']);
}
break;
default:
header('HTTP/1.1 405 Method Not Allowed');
break;
}
Explanation of the Code
- ProductCatalog Class: This class manages product data, including methods to get all products, get a specific product, create, update, and delete products. It interacts with both MySQL and Redis.
- Caching: The Redis cache is used to store the list of products to reduce database queries. The cache is cleared when a product is created, updated, or deleted.
- HTTP Methods: The microservice handles different HTTP methods (GET, POST, PUT, DELETE) to allow full CRUD operations.
Google Ad 2
Step 4: Testing the Microservice
You can test your microservice by accessing it through the DDEV URL. Run:
ddev launch
This will open your default web browser to the project URL. You can test the endpoints using cURL or tools like Postman.
Example cURL Commands
GET: Get all products
curl http://.ddev.site
GET: Get a specific product
curl http://.ddev.site?id=1
POST: Create a new product
curl -X POST http://.ddev.site -H "Content-Type: application/json" -d '{"name":"Product D","description":"Description for Product D","price":40.00}'
PUT: Update a product
curl -X PUT http://.ddev.site -H "Content-Type: application/json" -d '{"id":1,"name":"Updated Product A","description":"Updated Description","price":15.00}'
DELETE: Delete a product
curl -X DELETE http://.ddev.site?id=1
Step 5: Setting Up Xdebug for Profiling
1. Install Xdebug
To install Xdebug in your DDEV environment, run:
ddev ssh
pecl install xdebug
2. Configure Xdebug
Edit the Xdebug configuration by running:
ddev ssh
nano /etc/php/8.0/fpm/conf.d/20-xdebug.ini
Add the following configuration:
zend_extension=xdebug.so
xdebug.mode=profile
xdebug.output_dir=/var/www/html/xdebug
xdebug.profiler_enable=1
xdebug.profiler_output_name=cachegrind.out.%p
Settings Explained:
- xdebug.mode=profile: Enables profiling.
- xdebug.output_dir: Directory where profiling files are saved.
- xdebug.profiler_enable=1: Enables profiling for all requests.
- xdebug.profiler_output_name: Naming convention for profiling files.
3. Restart DDEV
Restart the DDEV environment:
ddev restart
Google Ad 3
Step 6: Setting Up Visual Studio Code for Profiling
1. Install the PHP Debug Extension
In Visual Studio Code, install the PHP Debug extension by Felix Becker. This extension enables you to debug PHP applications using Xdebug.
2. Configure Debug Settings
Create a debug configuration for Xdebug:
- Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P) and select Debug: Open launch.json.
- If prompted, select PHP. This will create a default launch.json file.
Edit launch.json to match the following configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}/web"
}
}
]
}
Settings Explained:
- port: Ensure this matches the port configured in Xdebug.
- pathMappings: Maps paths in the Docker container to your local filesystem.
3. Start Listening for Debugging
- In VS Code, go to the Debug view (click on the Debug icon on the left sidebar).
- Select Listen for XDebug from the dropdown.
- Click the green play button to start listening for Xdebug connections.
Step 7: Generating and Analyzing Profiling Data
1. Run Your Microservice
Access your microservice as usual by launching it:
ddev launch
2. Generate Profiling Data
As you interact with the microservice (e.g., fetching products, adding new products), Xdebug will generate profiling files in the /var/www/html/xdebug directory.
3. Analyze Profiling Data
To analyze the generated profiling data:
- QCacheGrind: Install it on your machine and open the profiling files generated in the xdebug directory.
- Webgrind: Alternatively, you can set up Webgrind as a web-based tool for analyzing profiling data.
Common Issues and Troubleshooting
1. Profiling Files Not Generated
- Possible Causes:
- Xdebug is not installed or configured correctly.
- xdebug.profiler_enable is not set to 1.
- Solution: Double-check the configuration and ensure that the output directory is writable.
2. High Memory Usage
- Possible Causes:Inefficient code or excessive data being processed.
- Solution: Use profiling data to identify memory-intensive functions and optimize them.
3. Slower Performance Due to Profiling
- Possible Causes: Profiling adds overhead that can slow down your application.
- Solution: Disable profiling in production environments by setting xdebug.profiler_enable=0 in your php.ini.
FAQs
Q1: Can I disable profiling for specific requests?
Yes, you can disable profiling by adding ?XDEBUG_PROFILE=0 to your URL.
Q2: Is it safe to use Xdebug in a production environment?
Xdebug should not be used in production due to performance overhead and potential security risks. Always disable it in production.
Q3: How can I view profiling data without installing QCacheGrind?
You can use Webgrind, which is a web-based interface for Xdebug profiling data. Install it by downloading from Webgrind GitHub.
Q4: What should I focus on when optimizing my application?
When analyzing profiling data, focus on:
- Functions with high execution times.
- Functions that are called frequently (N+1 problems).
- Memory usage hotspots.
Conclusion
In this article, we have set up Xdebug for profiling the product catalog microservice, tailored specifically for use with Visual Studio Code. By following these steps, you can effectively identify and address performance bottlenecks in your application, ensuring optimal performance for your users.
Thanks for reading the article, for more Science & Technology related articles read and subscribe to peoples blog articles.