# Cài đặt Git Flow
apt-get install git-flow
# Khởi tạo Git Flow trong repository
git flow init
# Làm việc với feature branch
git flow feature start new-feature
# Phát triển tính năng...
git flow feature finish new-feature
# Làm việc với release branch
git flow release start 1.0.0
# Chuẩn bị release...
git flow release finish '1.0.0'
# Hotfix cho vấn đề trên production
git flow hotfix start critical-bug
# Sửa lỗi...
git flow hotfix finish critical-bug
<?php
// Ví dụ về code được refactor sau review
// Trước khi review
function getData($id) {
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$stmt = $db->prepare("SELECT * FROM data WHERE id = ?");
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result;
}
// Sau khi review - Áp dụng dependency injection, error handling, và logging
class DataRepository {
private $db;
private $logger;
public function __construct(PDO $db, LoggerInterface $logger) {
$this->db = $db;
$this->logger = $logger;
}
public function getData(int $id): ?array {
try {
$stmt = $this->db->prepare("SELECT * FROM data WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
} catch (PDOException $e) {
$this->logger->error("Database error: " . $e->getMessage(), ['id' => $id]);
throw new RepositoryException("Could not fetch data", 0, $e);
}
}
}
?>
<?php
use PHPUnit\Framework\TestCase;
class UserServiceTest extends TestCase
{
private $userRepository;
private $userService;
protected function setUp(): void
{
$this->userRepository = $this->createMock(UserRepository::class);
$this->userService = new UserService($this->userRepository);
}
public function testRegisterUserWithValidData()
{
// Arrange
$userData = [
'name' => 'John Doe',
'email' => '[email protected]',
'password' => 'SecurePassword123'
];
$this->userRepository->expects($this->once())
->method('create')
->with(
$this->equalTo($userData['name']),
$this->equalTo($userData['email']),
$this->callback(function($password) use ($userData) {
return password_verify($userData['password'], $password);
})
)
->willReturn(1);
// Act
$result = $this->userService->register($userData);
// Assert
$this->assertEquals(1, $result);
}
public function testRegisterUserWithInvalidEmail()
{
// Arrange
$userData = [
'name' => 'John Doe',
'email' => 'invalid-email',
'password' => 'SecurePassword123'
];
// Assert exception
$this->expectException(ValidationException::class);
// Act
$this->userService->register($userData);
}
}
?>
<?php
// Domain Layer: Entities & Value Objects
class User {
private UserId $id;
private string $name;
private Email $email;
private HashedPassword $password;
public function __construct(UserId $id, string $name, Email $email, HashedPassword $password) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
$this->password = $password;
}
public function changePassword(string $newPassword): void {
$this->password = new HashedPassword($newPassword);
}
// Getters...
}
// Value Objects
class Email {
private string $value;
public function __construct(string $email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email address");
}
$this->value = $email;
}
public function __toString(): string {
return $this->value;
}
}
// Application Layer: Services
class UserService {
private UserRepository $repository;
public function __construct(UserRepository $repository) {
$this->repository = $repository;
}
public function registerUser(string $name, string $email, string $password): UserId {
$user = new User(
UserId::generate(),
$name,
new Email($email),
new HashedPassword($password)
);
return $this->repository->save($user);
}
}
// Infrastructure Layer: Repositories
interface UserRepository {
public function save(User $user): UserId;
public function findById(UserId $id): ?User;
}
class MySqlUserRepository implements UserRepository {
private PDO $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
public function save(User $user): UserId {
// Implementation...
}
public function findById(UserId $id): ?User {
// Implementation...
}
}
?>
Backlog:
Sprint Planning:
Daily Standup:
Sprint Review:
Sprint Retrospective:
# .github/workflows/php-ci.yml
name: PHP CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
extensions: mbstring, intl, pdo_mysql
coverage: xdebug
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
uses: actions/cache@v3
with:
path: vendor
key: $-php-$
restore-keys: $-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run code style check
run: vendor/bin/phpcs
- name: Run static analysis
run: vendor/bin/phpstan analyse src tests
- name: Run test suite
run: vendor/bin/phpunit --coverage-clover coverage.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
<?php
// phpunit.xml.dist
?>
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
colors="true"
bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Unit Tests">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration Tests">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="Feature Tests">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
<report>
<clover outputFile="coverage.xml"/>
<html outputDirectory="coverage-report"/>
</report>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>
<?php
// phpcs.xml
?>
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="PHP_CodeSniffer">
<description>PHP Coding Standards</description>
<!-- Scan these files -->
<file>src</file>
<file>tests</file>
<!-- Show progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- Use PSR-12 -->
<rule ref="PSR12"/>
<!-- Específic rules -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="120"/>
</properties>
</rule>
</ruleset>
<?php
// phpstan.neon
?>
parameters:
level: 8
paths:
- src
- tests
excludePaths:
- vendor/*
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
Quy trình triển khai của Laravel Forge
Hoặc cấu hình deploy script tùy chỉnh
composer install --no-dev
php artisan migrate --force
php artisan optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache
Quy trình Zero-Downtime Deployment:
Chuẩn bị:
Cài đặt dependencies:
Cấu hình:
Tối ưu:
Migrations:
Cập nhật symlink:
Restart các services:
Dọn dẹp:
<?php
// Cấu hình logging với Monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackHandler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;
// Tạo logger chính
$logger = new Logger('app');
// Thêm file handler
$fileHandler = new StreamHandler('logs/app.log', Logger::DEBUG);
$fileHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($fileHandler);
// Thêm Slack handler cho lỗi nghiêm trọng
$slackHandler = new SlackHandler(
'slack-token',
'#errors',
'ErrorBot',
true,
null,
Logger::ERROR
);
$logger->pushHandler($slackHandler);
// Thêm processors để ghi lại thêm thông tin
$logger->pushProcessor(new IntrospectionProcessor());
$logger->pushProcessor(new WebProcessor());
// Sử dụng logger
$logger->info('User logged in', ['user_id' => 123]);
$logger->error('Payment failed', [
'user_id' => 123,
'amount' => 99.95,
'error_code' => 'CARD_DECLINED'
]);
?>
# docker-compose.yml cho ELK stack
version: "3"
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:7.17.0
depends_on:
- elasticsearch
volumes:
- ./logstash-config:/usr/share/logstash/pipeline
ports:
- "5000:5000"
kibana:
image: docker.elastic.co/kibana/kibana:7.17.0
depends_on:
- elasticsearch
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
volumes:
elasticsearch-data:
# logstash-config/logstash.conf
input {
file {
type => "php-logs"
path => "/var/log/php/app.log"
codec => "json"
}
tcp {
port => 5000
codec => "json"
}
}
filter {
if [type] == "php-logs" {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "php-logs-%{+YYYY.MM.dd}"
}
}
<?php
// Sử dụng Prometheus PHP Client
// composer require promphp/prometheus_client_php
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
// Kết nối với Redis instance
$adapter = new Redis(['host' => 'redis', 'port' => 6379]);
$registry = new CollectorRegistry($adapter);
// Đếm số request
$counter = $registry->getOrRegisterCounter('app', 'requests_total', 'Total request count', ['endpoint']);
$counter->incBy(1, [$_SERVER['REQUEST_URI']]);
// Đo thời gian thực thi
$histogram = $registry->getOrRegisterHistogram(
'app',
'request_duration_seconds',
'Request duration in seconds',
['endpoint'],
[0.01, 0.05, 0.1, 0.5, 1, 2, 5]
);
$start = microtime(true);
// Xử lý request...
$duration = microtime(true) - $start;
$histogram->observe($duration, [$_SERVER['REQUEST_URI']]);
// Đặt gauge cho số kết nối database
$gauge = $registry->getOrRegisterGauge('app', 'db_connections', 'Current database connections');
$gauge->set(DB::getConnectionCount());
?>
<?php
// metrics.php - Endpoint để Prometheus scrape metrics
// Header
header('Content-Type: text/plain; version=0.0.4');
// Lấy registry
$adapter = new Prometheus\Storage\Redis(['host' => 'redis', 'port' => 6379]);
$registry = new Prometheus\CollectorRegistry($adapter);
// Output metrics
$renderer = new Prometheus\RenderTextFormat();
echo $renderer->render($registry->getMetricFamilySamples());
?>
<?php
// Thiết lập error handler tùy chỉnh
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// Error không nằm trong error_reporting
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
// Thiết lập exception handler
set_exception_handler(function(Throwable $exception) {
global $logger;
// Log exception
$logger->error('Uncaught exception: ' . $exception->getMessage(), [
'exception' => [
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
]
]);
// Thông báo cho Slack về lỗi nghiêm trọng
if ($exception instanceof FatalErrorException) {
notifySlack($exception);
}
// Hiển thị lỗi thân thiện với người dùng trong production
if (getenv('APP_ENV') === 'production') {
http_response_code(500);
echo json_encode([
'error' => 'Server Error',
'message' => 'Something went wrong. Our team has been notified.'
]);
} else {
// Hiển thị chi tiết lỗi trong development
http_response_code(500);
echo '<h1>Error</h1>';
echo '<p>' . htmlspecialchars($exception->getMessage()) . '</p>';
echo '<pre>' . htmlspecialchars($exception->getTraceAsString()) . '</pre>';
}
});
// Tích hợp với dịch vụ Error Tracking như Sentry
// composer require sentry/sdk
Sentry\init([
'dsn' => 'https://[email protected]/0',
'environment' => getenv('APP_ENV'),
'release' => '1.0.0',
]);
try {
// Code có thể gây exception
processOrder($orderId);
} catch (Throwable $exception) {
Sentry\captureException($exception);
// Handle exception
}
?>
<?php
// Sử dụng Xdebug profiling
// php.ini configuration
/*
[xdebug]
xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug
xdebug.profiler_output_name=cachegrind.out.%p
*/
// Sử dụng Tideways XHProf
// composer require tideways/php-xhprof-extension
// Bắt đầu profiling
tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_CPU | TIDEWAYS_XHPROF_FLAGS_MEMORY);
// Code cần profile
$result = complexCalculation();
// Dừng profiling và lưu kết quả
$profile_data = tideways_xhprof_disable();
file_put_contents(
'/tmp/profile_data_' . uniqid() . '.json',
json_encode($profile_data)
);
?>
Tích hợp Blackfire.io
Cài đặt Blackfire Agent và PHP Probe theo hướng dẫn
Sử dụng Blackfire SDK để profile code cụ thể
composer require blackfire/php-sdk
<?php
\BlackfireProbe::getMainInstance()->enable();
// Code cần profile
$result = complexCalculation();
\BlackfireProbe::getMainInstance()->disable();
// 3. Web UI cho tương tác với dashboard
// Sử dụng Chrome extension để trigger profile
?>
<?php
// Phân tích và tối ưu query
// 1. Enable MySQL Slow Query Log
// my.cnf configuration
/*
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
*/
// 2. Phân tích query với EXPLAIN
$stmt = $pdo->prepare("EXPLAIN SELECT * FROM users JOIN orders ON users.id = orders.user_id WHERE users.status = ?");
$stmt->execute(['active']);
$explainResults = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($explainResults);
// 3. Tối ưu queries
// Bad query
$stmt = $pdo->prepare("SELECT * FROM products WHERE category_id = ? ORDER BY price DESC");
// Optimized query - chỉ lấy dữ liệu cần thiết
$stmt = $pdo->prepare("SELECT id, name, price FROM products WHERE category_id = ? ORDER BY price DESC");
// 4. Sử dụng indexes
// Tạo index
$pdo->exec("CREATE INDEX idx_products_category_price ON products (category_id, price)");
// 5. Tránh N+1 query problem
// Bad approach - N+1 queries
$users = $pdo->query("SELECT * FROM users LIMIT 100")->fetchAll();
foreach ($users as $user) {
$orders = $pdo->prepare("SELECT * FROM orders WHERE user_id = ?");
$orders->execute([$user['id']]);
// Process orders...
}
// Good approach - 1 query with JOIN
$stmt = $pdo->query(
"SELECT users.*, orders.*
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE users.id IN (SELECT id FROM users LIMIT 100)"
);
?>
<?php
// 1. Cài đặt và kết nối Redis
$redis = new Redis();
$redis->connect('redis', 6379);
// 2. Caching data
function getUserData($userId) {
global $redis, $pdo;
// Cache key
$cacheKey = "user:{$userId}:data";
// Try to get from cache
$cachedData = $redis->get($cacheKey);
if ($cachedData) {
return json_decode($cachedData, true);
}
// Cache miss - fetch from database
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
$userData = $stmt->fetch(PDO::FETCH_ASSOC);
// Store in cache with TTL (1 hour)
$redis->setex($cacheKey, 3600, json_encode($userData));
return $userData;
}
// 3. Caching query results
function getActiveProducts($categoryId) {
global $redis, $pdo;
// Cache key
$cacheKey = "category:{$categoryId}:active_products";
// Try to get from cache
$cachedData = $redis->get($cacheKey);
if ($cachedData) {
return json_decode($cachedData, true);
}
// Cache miss - fetch from database
$stmt = $pdo->prepare(
"SELECT id, name, price, stock
FROM products
WHERE category_id = ? AND active = 1"
);
$stmt->execute([$categoryId]);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Store in cache with TTL (5 minutes)
$redis->setex($cacheKey, 300, json_encode($products));
return $products;
}
// 4. Cache invalidation when data changes
function updateProduct($productId, $data) {
global $redis, $pdo;
// Update in database
$stmt = $pdo->prepare(
"UPDATE products SET name = ?, price = ?, stock = ? WHERE id = ?"
);
$stmt->execute([$data['name'], $data['price'], $data['stock'], $productId]);
// Get category for cache invalidation
$stmt = $pdo->prepare("SELECT category_id FROM products WHERE id = ?");
$stmt->execute([$productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
// Invalidate cache
$redis->del("category:{$product['category_id']}:active_products");
$redis->del("product:{$productId}:data");
}
?>
// load-test.js - k6 script
import http from "k6/http";
import { sleep, check } from "k6";
export const options = {
stages: [
{ duration: "30s", target: 50 }, // Ramp-up to 50 users over 30s
{ duration: "1m", target: 50 }, // Stay at 50 users for 1 minute
{ duration: "30s", target: 100 }, // Ramp-up to 100 users
{ duration: "1m", target: 100 }, // Stay at 100 users for 1 minute
{ duration: "30s", target: 0 }, // Ramp-down to 0 users
],
thresholds: {
http_req_duration: ["p(95)<500"], // 95% of requests should finish within 500ms
"http_req_duration{status:200}": ["max<600"], // Maximum duration of 200 responses should be below 600ms
},
};
export default function () {
// Test homepage
const homeRes = http.get("https://example.com/");
check(homeRes, {
"homepage status is 200": (r) => r.status === 200,
"homepage load time < 500ms": (r) => r.timings.duration < 500,
});
sleep(1);
// Test API endpoint
const apiRes = http.get("https://example.com/api/products");
check(apiRes, {
"api status is 200": (r) => r.status === 200,
"api response is JSON": (r) =>
r.headers["Content-Type"].includes("application/json"),
});
sleep(2);
}
Sử dụng Opcode Cache (OPcache)
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 # trong production
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.enable_file_override=1
opcache.jit=1255
opcache.jit_buffer_size=100M
Tránh eager loading của các đoạn mã không cần thiết
composer dump-autoload --optimize
Sử dụng connection pooling cho database
[databases]
mydb = host=localhost dbname=mydb user=myuser password=mypassword
Sử dụng queues cho heavy processing
// Publish job to queue
function queueEmailJob($emailData) {
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('email_queue', false, true, false, false);
$msg = new AMQPMessage(
json_encode($emailData),
['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
);
$channel->basic_publish($msg, '', 'email_queue');
$channel->close();
$connection->close();
}
// Worker để xử lý queue
function startEmailWorker() {
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('email_queue', false, true, false, false);
$callback = function($msg) {
$emailData = json_decode($msg->body, true);
try {
// Process email
sendEmail($emailData);
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
} catch (Exception $e) {
// Log error và reject message
$msg->delivery_info['channel']->basic_reject($msg->delivery_info['delivery_tag'], false);
}
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('email_queue', '', false, false, false, false, $callback);
while(count($channel->callbacks)) {
$channel->wait();
}
}
?>
PHP Cơ bản:
OOP trong PHP:
Design Patterns:
Database và SQL:
Security:
Modern PHP:
Testing:
Performance:
Xây dựng Portfolio Developer PHP:
Các dự án nên có trong portfolio:
Đóng góp open source:
Blog kỹ thuật:
Junior PHP Developer (0-2 năm):
Mid-level PHP Developer (2-5 năm):
Senior PHP Developer (5+ năm):
Tech Lead / Architect:
Specialized paths:
<?php
// Ví dụ: Bài tập Coding Challenge thường gặp
function isPalindrome(string $str): bool {
$str = preg_replace('/[^a-z0-9]/i', '', strtolower($str));
return $str === strrev($str);
}
function fizzbuzz(int $n): void {
for ($i = 1; $i <= $n; $i++) {
if ($i % 3 === 0 && $i % 5 === 0) {
echo "FizzBuzz\n";
} elseif ($i % 3 === 0) {
echo "Fizz\n";
} elseif ($i % 5 === 0) {
echo "Buzz\n";
} else {
echo $i . "\n";
}
}
}
?>
Keeping Up with PHP Ecosystem:
<?php
/*
Theo dõi và cập nhật kiến thức PHP:
1. PHP RFC và GitHub:
- https://wiki.php.net/rfc
- https://github.com/php/php-src
2. Blogs và newsletters:
- PHP Weekly
- PHP Annotated Monthly
- Laravel News
- Symfony Blog
3. Podcasts:
- PHP Roundtable
- Laravel Podcast
- Full Stack Radio
- PHP Architects Radio
4. Conferences:
- PHP[tek]
- Laracon
- Symfony Con
- PHP UK Conference
5. Online courses và platforms:
- Laracasts
- Symfonycasts
- PHP The Right Way
6. Twitter accounts và social media:
- @official_php
- @laravelphp
- @symfony
- @nikita_ppv
*/
?>
Kiến trúc hệ thống:
Core Modules:
Yêu cầu kỹ thuật:
Tính năng:
Advanced Features:
e-learning-platform/
├── docker-compose.yml
├── api-gateway/
│ ├── Dockerfile
│ └── src/
├── auth-service/
│ ├── Dockerfile
│ ├── src/
│ └── database/
├── course-service/
│ ├── Dockerfile
│ ├── src/
│ └── database/
├── enrollment-service/
│ ├── Dockerfile
│ ├── src/
│ └── database/
├── media-service/
│ ├── Dockerfile
│ ├── src/
│ └── storage/
├── analytics-service/
│ ├── Dockerfile
│ ├── src/
│ └── database/
├── payment-service/
│ ├── Dockerfile
│ ├── src/
│ └── database/
├── frontend/
│ ├── Dockerfile
│ └── src/
├── admin-panel/
│ ├── Dockerfile
│ └── src/
├── message-broker/
│ └── rabbitmq/
├── cache/
│ └── redis/
├── monitoring/
│ ├── prometheus/
│ └── grafana/
└── logging/
├── elasticsearch/
├── logstash/
└── kibana/
Code Quality:
Architecture:
Testing:
Performance:
Security:
CI/CD:
Documentation:
⬅️ Trở lại: PHP/Part5.md | 🏠 Home | ➡️ Tiếp theo: DEVOPS/Docker1.md