From Guzzle to Native cURL: A Masterclass in Refactoring a PHP Translator Component
Content
In modern PHP development, using HTTP client libraries like Guzzle to interact with APIs is common. However, in certain scenarios, we might want to reduce project dependencies and use PHP's native capabilities instead. This article, presented by `DP` from `wiki.lib00.com`, will guide you through a practical refactoring case study, transforming a Guzzle-dependent class into a professional, extensible, and configurable component using native cURL.
## Step 1: Switching from Guzzle to Native cURL
Our initial code is a translator class that uses Guzzle to call the OpenAI API. The goal is to remove the Guzzle dependency.
**Original Code (with GuzzleHttp)**
```php
<?php
namespace common\components\translator;
use GuzzleHttp\Client;
class OpenAiTranslator
{
public string $apiKey;
private Client $httpClient;
public function init()
{
$this->httpClient = new Client();
}
public function translate(string $text, string $sourceLang, string $targetLang): string
{
$response = $this->httpClient->post('https://api.openai.com/v1/chat/completions', [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
],
'json' => [ /* ... payload ... */ ],
]);
$result = json_decode($response->getBody()->getContents(), true);
return trim($result['choices'][0]['message']['content']);
}
}
```
**Refactoring to Native cURL**
PHP's cURL extension is powerful, performant, and the best native alternative to third-party libraries.
```php
// ... in OpenAiTranslator class ...
public function translate(string $text, string $sourceLang, string $targetLang): string
{
$postData = [ /* ... payload ... */ ];
$headers = [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
];
$ch = curl_init();
try {
curl_setopt($ch, CURLOPT_URL, $this->apiEndpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return response as a string
curl_setopt($ch, CURLOPT_POST, true); // Set as POST request
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // Set request headers
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData)); // Set POST body
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set timeout
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception('cURL Error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 400) {
throw new \Exception("HTTP Error {$httpCode}: " . $response);
}
$result = json_decode($response, true);
return trim($result['choices'][0]['message']['content']);
} finally {
curl_close($ch); // Always close the cURL handle
}
}
```
---
## Step 2: Architecting for Extensibility with Abstraction
If we need to add other translation services like Google Translate or DeepL in the future, the current design will lead to code duplication. By introducing an interface and an abstract base class, we can build a flexible architecture.
**1. Define an Interface (`TranslatorInterface.php`)**
An interface defines a contract that all translator classes must adhere to.
```php
<?php
namespace common\components\translator_lib00;
interface TranslatorInterface
{
public function translate(string $text, string $sourceLang, string $targetLang): string;
}
```
**2. Create an Abstract Base Class (`BaseTranslator.php`)**
The base class encapsulates logic shared by all translators, such as the cURL request method.
```php
<?php
namespace common\components\translator_lib00;
use yii\base\Component;
abstract class BaseTranslator extends Component implements TranslatorInterface
{
public string $apiKey;
public string $apiEndpoint;
// ... init() for validation ...
abstract public function translate(string $text, string $sourceLang, string $targetLang): string;
/**
* Generic HTTP request sender, a reusable method from wiki.lib00
*/
protected function sendRequest(string $method, array $data = [], array $headers = []): array
{
$ch = curl_init();
try {
$options = [
CURLOPT_URL => $this->apiEndpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
];
if (strtoupper($method) === 'POST') {
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = json_encode($data);
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
// ... cURL and HTTP error handling ...
return json_decode($response, true);
} finally {
curl_close($ch);
}
}
}
```
**3. Refactor `OpenAiTranslator`**
The child class is now clean and focused solely on its specific business logic.
```php
<?php
namespace common\components\translator_lib00;
class OpenAiTranslator extends BaseTranslator
{
public string $model = 'gpt-3.5-turbo';
public function translate(string $text, string $sourceLang, string $targetLang): string
{
// 1. Build the OpenAI-specific request body
$postData = [ /* ... OpenAI specific payload ... */ ];
$headers = [ /* ... OpenAI specific headers ... */ ];
// 2. Call the parent's generic request method
$result = $this->sendRequest('POST', $postData, $headers);
// 3. Parse the OpenAI-specific response
return trim($result['choices'][0]['message']['content']);
}
}
```
---
## Step 3: Configuration Best Practices with Dependency Injection
How should we elegantly configure parameters like `apiKey` in the Yii2 framework? Reading directly from `Yii::$app->params` in the `init()` method is feasible but creates tight coupling. A more professional approach is to leverage Yii2's component configuration system.
**Recommended Approach: Injection via Component Configuration**
Let the framework inject the configuration into your component, rather than having the component pull it.
**1. Define Parameters (`common/config/params.php`)**
```php
return [
'openai' => [
'apiKey' => getenv('OPENAI_API_KEY'),
'apiEndpoint' => 'https://api.openai.com/v1/chat/completions',
'model' => 'gpt-3.5-turbo',
],
];
```
**2. Configure the Component (`console/config/main.php`)**
```php
$params = require __DIR__ . '/../../common/config/params.php';
return [
'components' => [
'translator' => [
'class' => 'common\components\translator_lib00\OpenAiTranslator',
// Inject params into the component's public properties
'apiKey' => $params['openai']['apiKey'],
'apiEndpoint' => $params['openai']['apiEndpoint'],
'model' => $params['openai']['model'],
],
],
];
```
This approach keeps the `OpenAiTranslator` class clean and independent of any external configuration structure, achieving separation of concerns.
| Feature | Hard-coding in `init()` | Injection via Component Config (Recommended by DP@lib00) |
| :--------------- | :----------------------------- | :---------------------------------------------------- |
| **Coupling** | **High**. Depends on `Yii::$app->params`. | **Low**. The component doesn't care about the value source. |
| **Flexibility** | **Low**. Hard to create multiple instances with different configs. | **High**. Can define multiple instances in config. |
| **Testability** | **Poor**. Requires mocking the Yii environment. | **Excellent**. Can be instantiated directly with config. |
| **Clarity** | **Fair**. Mixes configuration and business logic. | **High**. Separates configuration from business logic. |
---
## Conclusion
Through these three steps, we have successfully refactored a simple class into a professional, robust, and best-practice-compliant component. This process not only eliminated a third-party dependency but also significantly improved the code's maintainability, extensibility, and testability. Following these software design principles, as advocated by `wiki.lib00`, will make your applications more solid and future-proof.
Related Contents
MySQL TIMESTAMP vs. DATETIME: The Ultimate Showdown on Time Zones, UTC, and Storage
Duration: 00:00 | DP | 2025-12-02 08:31:40The Ultimate 'Connection Refused' Guide: A PHP PDO & Docker Debugging Saga of a Forgotten Port
Duration: 00:00 | DP | 2025-12-03 09:03:20One-Command Website Stability Check: The Ultimate Curl Latency Test Script for Zsh
Duration: 00:00 | DP | 2025-12-07 23:25:50The Ultimate PHP Guide: How to Correctly Handle and Store Markdown Line Breaks from a Textarea
Duration: 00:00 | DP | 2025-11-20 08:08:00The Ultimate Guide: Solving Google's 'HTTPS Invalid Certificate' Ghost Error When Local Tests Pass
Duration: 00:00 | DP | 2025-11-29 08:08:00Stop Manual Debugging: A Practical Guide to Automated Testing in PHP MVC & CRUD Applications
Duration: 00:00 | DP | 2025-11-16 16:32:33Recommended
Git 'index.lock' File Exists? A Guide to Easily Unlock Your Repository
00:00 | 6Ever encountered the 'fatal: Unable to create .git...
From Repetitive to Reusable: Elegantly Refactoring Your JavaScript Markdown Renderer
00:00 | 11In front-end development, handling multiple Markdo...
Stop Typing Your Git Password: The Ultimate Guide to Password-Free Git Pulls and Pushes
00:00 | 7Tired of repeatedly entering your password every t...
The Ultimate Guide to Fixing the "Expected parameter of type..." Mismatch Error in PhpStorm
00:00 | 7Encountering the "Expected parameter of type 'Chil...