Bash script setup basically VM

No password required with sudo

joos ALL=(ALL) NOPASSWD:ALL

Extend LVM

sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

Setup timezone

sudo timedatectl set-timezone Asia/Ho_Chi_Minh
sudo timedatectl set-ntp on

Docker

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

Setup PHP

#!/bin/bash

echo 'Install requirements'

sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update && sudo apt upgrade -y

phpVersions=('8.3')
phpExtensions=('dev' 'cli' 'mbstring' 'curl' 'intl' 'mbstring' 'xml' 'xmlrpc' 'xsl' 'yaml' 'zip' 'imagick' 'gd' 'opcache' 'memcache' 'memcached' 'mysql' 'sqlite3' 'ldap' 'bcmath' 'fpm')
phpPecls=('mongodb' 'redis' 'pcov' 'apcu')

for phpVersion in "${phpVersions[@]}"
do
  echo "Install PHP ${phpVersion} extensions"
  extensions=$(printf "php${phpVersion}-%s " "${phpExtensions[@]}")
  sudo apt install -y $extensions

  echo "Install PHP ${phpVersion} pecl extensions"
  for phpPecl in "${phpPecls[@]}"
  do
    sudo pecl -d php_suffix="${phpVersion}" install "${phpPecl}"
    sudo pecl uninstall -r "${phpPecl}"    
    sudo bash -c 'echo "extension='${phpPecl}'.so" >> /etc/php/'${phpVersion}'/cli/php.ini'
    sudo bash -c 'echo "extension='${phpPecl}'.so" >> /etc/php/'${phpVersion}'/fpm/php.ini'
  done
done

Github action – Reusable

# https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions
name: XCrawler - Build & Tests

# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
on:
  # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
  pull_request:
    branches: [ develop ]
    types: [ opened, synchronize ]

jobs:

  sonar_cloud:
    name: SonarCloud
    uses: ./.github/workflows/sonar_cloud.yml
    secrets: inherit

  security_check:
    name: Security check
    uses: ./.github/workflows/security_check.yml
    secrets: inherit

  code_standards:
    name: Code standards check
    needs: [ sonar_cloud, security_check ]
    strategy:
      matrix:
        lint: [ phpstan, phpmd, phpcs ]
    uses: ./.github/workflows/code_standards.yml
    with:
      lint: ${{ matrix.lint }}
    secrets: inherit

  tests:
    name: Execute UnitTest
    needs: [ code_standards ]
    strategy:
      matrix:
        test: [ Client, JAV, Flickr, Core ]
    uses: ./.github/workflows/unittest.yml
    with:
      test: ${{ matrix.test }}
    secrets: inherit

  finalizing_build:
    name: "All Parallel Tests passed"
    needs: [ tests ]
    uses: ./.github/workflows/codecov.yml
    secrets: inherit
Continue reading Github action – Reusable

Laravel – Code structure with UnitTest

Giả sử ta có một code structure như sau

Controller

  • Gọi Services để thực thi và respond

Services

  • Gọi Repository khi cần tương tác database
  • Trigger Event
  • Gọi các Services khác hoặc các code logic khác.

Vậy UnitTest sẽ cần những gì. Trong quan điểm mình là đi từ thấp nhất lên cao nhất

  • Repositories : Đảm bảo database được thực thi, assert các data như mong đợi.
    • Assert events đã dispatched.
    • Events : Dispatch events và assert data như mong đợi
  • Services
    • Nếu services có các events ẢNH HƯỞNG TRỰC TIẾP đến kết quả thì mình sẽ không fake event mà để event thực thi và assert kết quả
    • Nếu các events đó dùng cho 3rd thì mình assert để make sure events dispatched như mong đợi

Tất nhiên nếu service đó thuần túy gọi tới 3rd party thì mình chỉ việc mock và test respond từ 3rd party thôi.

Và sau cùng là assert controller trả về data như mong đợi

Microservice – XClient

https://github.com/jooservices/XClient

As a part of refactoring XCrawler. XClient under development

  • Receive crawl request from another service and process request to 3rd party
  • Receive queue request from another service. Queue will be scheduling process later and process callback to another service

Caching also provided

By this way, we don’t need care anything about Curl / Guzzle in another project / service.

Laravel – Different between Model trait boot & initialization

Trace back to Model we have

    protected static function bootTraits()
    {
        $class = static::class;

        $booted = [];

        static::$traitInitializers[$class] = [];

        foreach (class_uses_recursive($class) as $trait) {
            $method = 'boot'.class_basename($trait);

            if (method_exists($class, $method) && ! in_array($method, $booted)) {
                forward_static_call([$class, $method]);

                $booted[] = $method;
            }

            if (method_exists($class, $method = 'initialize'.class_basename($trait))) {
                static::$traitInitializers[$class][] = $method;

                static::$traitInitializers[$class] = array_unique(
                    static::$traitInitializers[$class]
                );
            }
        }
    }

Theo như code trên có thể thấy

  • boot sẽ call đến static method. Hay nói cách khác sẽ tác động tới mọi instances của Model này
  • trong khi đó `initialize thì ngược lại. Chỉ tác động tới instance hiện tại.

1 ví dụ thực tế

trait HasSlug
{
    public function initializeHasSystem()
    {
        $this->mergeCasts([
            'slug' => 'string'
        ]);

        $this->mergeFillable([
            'slug'
        ]);
    }

    public static function bootHasSlug()
    {
        static::creating(function ($model) {
            $model->{$model->getSlugName()} = Str::slug($model->name);
        });

        static::updating(function ($model) {
            $model->{$model->getSlugName()} = Str::slug($model->name);
        });
    }

    public function scopeSlug($query, $uuid)
    {
        return $query->where($this->getSlugName(), $uuid);
    }

    public function getSlugName()
    {
        return property_exists($this, 'slug') ? $this->slug : 'slug';
    }
}

Laravel – Stable Diffusion

Sau một thời gian nghịch Stable Diffusion thì giờ cũng chút hứng thú với … code Laravel cho em nó.

https://github.com/jooservices/Stable-Diffusion

Về cơ bản

  • Tạo ra queues để liên tục generate images
  • Tạo ra các “base prompt” để có thể tái` sử dụng ( ví dụ có thể tạo ``$prompt->portraits('Irene Aronson') thì sẽ tự add prompt là portraits của nghệ sĩ Irene Aronson

Alrite. Tuy nhiên trước đó thì cần chuẩn bị 1 số thứ

  • Stable Diffusion ( webUI ) chạy service
Description=systemd service start stable-diffusion

[Service]
Type=simple
Restart=always
RestartSec=1
ExecStart=/bin/bash /home/joos/stable-diffusion-webui/webui.sh
User=joos
WorkingDirectory=/home/joos/stable-diffusion-webui/
StandardOutput=append:/var/log/sdwebui.log
StandardError=append:/var/log/sdwebui.log

[Install]
WantedBy=multi-user.target
  • 1 em cron để chạy schedule:run của Laravel

Xong ! Giờ thì ta có 2 cách

  • Generate ra “queues” qua command
php artisan stable-diffusion:generate --prompt="a beautiful girl at 20 years old. she has colorful hair"

Command này sẽ sinh ra 1.001 queues ứng với prompt trên và … toàn bộ models mình đang có.

  • Code thêm theo ý
        $response = $service
            ->txt2img()
            ->generate($payload)
            ->save($queue->uuid)
            ->getResponse();

JomSocial – Troubleshooter

Feature này mình nghĩ ra và develop cho JomSocial … và cũng là feature cuối cùng mình làm cho JomSocial.

Khi làm supporter hầu hết các tình huống gặp phải là end users sử dụng VÔ TỘI VẠ các 3rd parties mà không bao giờ quan tâm đến compatibility của chúng. Song song đó họ cũng TÙY TIỆN modify vào core của JomSocial gây ra conflict, và sau đó request support !!!

Vậy nên Troubleshooter được develop để xử lý việc này

  • Check các 3rd parties và cũng cảnh báo các issues liên quan đến compatibility
  • Đảm bảo server đúng như requirements
  • Đảm bảo core files không bị modified
  • Có thể automatically restore các files nếu thấy có modified

Cũng thú vị chứ nhỉ ? 🙂

Laravel – Queue’ parameter

Redis

In your config/queue.php configuration file, each queue connection defines a retry_after option. This option specifies how many seconds the queue connection should wait before retrying a job that is being processed. For example, if the value of retry_after is set to 90, the job will be released back onto the queue if it has been processing for 90 seconds without being released or deleted. Typically, you should set the retry_after value to the maximum number of seconds your jobs should reasonably take to complete processing.

retry_after

Template Method

Template Method là một design pattern trong đó một khuôn mẫu được định nghĩa, chứa một số bước cơ bản của một thuật toán, trong khi các bước cụ thể được định nghĩa bởi các lớp con. Khuôn mẫu này đảm bảo rằng các bước cơ bản của thuật toán được thực hiện theo cùng một cách, nhưng cách thức cụ thể của từng bước có thể được thay đổi bởi các lớp con.

abstract class Report {
    public function generate() {
        $this->setData();
        $this->formatData();
        $this->generateOutput();
    }
    
    abstract protected function setData();
    
    protected function formatData() {
        // default implementation
    }
    
    abstract protected function generateOutput();
}

class PdfReport extends Report {
    protected function setData() {
        // set data for pdf report
    }
    
    protected function generateOutput() {
        // generate pdf output
    }
}

class ExcelReport extends Report {
    protected function setData() {
        // set data for excel report
    }
    
    protected function generateOutput() {
        // generate excel output
    }
}

Trong đoạn code này, Report là một abstract class định nghĩa khuôn mẫu cho việc tạo ra báo cáo. Phương thức generate() định nghĩa các bước cơ bản trong việc tạo ra báo cáo, bao gồm setData(), formatData()generateOutput(). Trong đó, setData()generateOutput() là các phương thức trừu tượng và phải được định nghĩa bởi các lớp con. Trong khi đó, formatData() có một implementation mặc định, nhưng có thể được ghi đè bởi các lớp con nếu cần thiết.

Các lớp con, như PdfReportExcelReport, kế thừa từ Report và định nghĩa các phương thức trừu tượng để thực hiện các bước cụ thể trong việc tạo ra báo cáo. Ví dụ, PdfReport định nghĩa setData() để thiết lập dữ liệu cho báo cáo PDF, và generateOutput() để tạo ra đầu ra PDF. Tương tự, ExcelReport định nghĩa setData()generateOutput() để tạo ra báo cáo Excel.

Khi chúng ta muốn tạo ra một báo cáo, chúng ta có thể tạo một đối tượng của lớp con và gọi phương thức generate(). Phương thức này sẽ thực hiện các bước cơ bản của thuật toán và gọi các phương thức cụ thể của lớp con để thực hiện các bước cụ

SOLID

SOLID là một nguyên tắc lập trình hướng đối tượng được đặt tên theo chữ cái đầu của các từ trong các nguyên tắc này: Single Responsibility Principle (SRP), Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), và Dependency Inversion Principle (DIP).

Các nguyên tắc SOLID đề cao việc thiết kế các lớp và đối tượng trong hệ thống phần mềm, nhằm tăng tính linh hoạt, dễ bảo trì, mở rộng và tái sử dụng của hệ thống.

Dưới đây là một số định nghĩa cho từng nguyên tắc SOLID:

  1. Single Responsibility Principle (SRP): Một lớp nên chỉ có một trách nhiệm duy nhất và không nên bị phân tán với nhiều trách nhiệm khác.
  2. Open/Closed Principle (OCP): Một lớp nên được thiết kế để dễ dàng mở rộng mà không cần sửa đổi mã nguồn hiện tại.
  3. Liskov Substitution Principle (LSP): Các lớp con phải có thể thay thế được cho các lớp cha của chúng mà không làm thay đổi tính năng của hệ thống.
  4. Interface Segregation Principle (ISP): Các giao diện nên được thiết kế theo những chức năng cụ thể và không nên yêu cầu các phương thức không cần thiết.
  5. Dependency Inversion Principle (DIP): Mã nguồn nên phụ thuộc vào các giao diện và không phải các lớp cụ thể, để tạo tính linh hoạt và dễ bảo trì.

1 chút mở rộng với Interface Segregation Principle

interface PaymentGatewayInterface {
    public function processPayment(float $amount, string $paymentMethod): bool;
    public function refundPayment(float $amount, string $paymentMethod): bool;
    public function savePaymentInfo(array $paymentInfo): bool;
}

Trong ví dụ này, PaymentGatewayInterface định nghĩa các phương thức không cần thiết như refundPayment() và savePaymentInfo(). Nếu PaymentGateway không sử dụng chúng, nó vẫn phải triển khai các phương thức này và sẽ phụ thuộc vào chúng. Việc này có thể dẫn đến các vấn đề về độ phức tạp và hiệu suất của ứng dụng, đồng thời cũng làm cho việc thay đổi PaymentGateway khó khăn hơn nếu chúng ta muốn loại bỏ các phương thức không cần thi

Còn đối với Dependency Inversion Principle (DIP) là một trong những nguyên tắc trong SOLID và nhấn mạnh rằng “Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstraction”. Nguyên tắc này khuyến khích chúng ta thiết kế các module để chúng phụ thuộc vào abstraction chung hơn là phụ thuộc vào các chi tiết cụ thể.

DIP có thể được áp dụng bằng cách sử dụng các design pattern như Dependency Injection (DI), Inversion of Control (IoC) và Service Locator.

Ví dụ, giả sử bạn đang phát triển một ứng dụng quản lý đơn hàng. Trong ứng dụng của bạn, bạn có một lớp OrderService để xử lý các thao tác liên quan đến đơn hàng, ví dụ như tạo đơn hàng, cập nhật đơn hàng, hoàn tất đơn hàng, v.v.

Trong trường hợp này, nếu OrderService phụ thuộc vào các lớp cấp thấp như OrderRepository, ProductRepository và CustomerRepository, nó sẽ phải biết chi tiết cụ thể của các lớp này, gây ra sự phụ thuộc chặt chẽ và làm cho việc bảo trì, mở rộng và thay đổi khó khăn hơn.

Thay vào đó, nếu chúng ta áp dụng DIP, chúng ta sẽ tạo ra các abstraction (interface hoặc abstract class) để mô tả các chức năng của các lớp cấp thấp như OrderRepository, ProductRepository và CustomerRepository. Sau đó, chúng ta sẽ thiết kế OrderService để phụ thuộc vào các abstraction này thay vì các lớp cụ thể. Ví dụ:

interface OrderRepositoryInterface {
    public function createOrder(array $orderData): int;
    public function updateOrder(int $orderId, array $orderData): bool;
    public function completeOrder(int $orderId): bool;
}

interface ProductRepositoryInterface {
    public function getProductById(int $productId): array;
}

interface CustomerRepositoryInterface {
    public function getCustomerById(int $customerId): array;
}

class OrderService {
    private $orderRepository;
    private $productRepository;
    private $customerRepository;

    public function __construct(OrderRepositoryInterface $orderRepository, ProductRepositoryInterface $productRepository, CustomerRepositoryInterface $customerRepository) {
        $this->orderRepository = $orderRepository;
        $this->productRepository = $productRepository;
        $this->customerRepository = $customerRepository;
    }

    public function createOrder(array $orderData): int {
        // Use the injected order repository to create the order
        $orderId = $this->orderRepository->createOrder($orderData);

        // Use the injected product repository to get the product information
        $product = $this->productRepository->getProductById($orderData['product_id']);

        // Use the injected customer repository to get the customer information
        $customer = $this->customerRepository->getCustomer