顯示具有 PHP 標籤的文章。 顯示所有文章
顯示具有 PHP 標籤的文章。 顯示所有文章

2025/03/03

Docker PHP Extension Installer

我是一位 PHP 開發者,目前許多的 production 專案也會用到 docker 了,所以套件安裝經常會在 Dockerfile 出現,在 php - Official Image | Docker Hub 上你要安裝 extension,他的範例是:

FROM php:8.2-fpm
RUN apt-get update && apt-get install -y \
		libfreetype-dev \
		libjpeg62-turbo-dev \
		libpng-dev \
	&& docker-php-ext-configure gd --with-freetype --with-jpeg \
	&& docker-php-ext-install -j$(nproc) gd

這樣寫蠻醜的也不好維護,後來找到了好用的 mlocati/docker-php-extension-installer: Easily install PHP extensions in Docker containers,安裝方式就變得很乾淨。

FROM php:7.2-cli

RUN curl -sSLf \
        -o /usr/local/bin/install-php-extensions \
        https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
    chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions gd xdebug

install-php-extensions 一直增加你要的東西就可以,下方還有各 PHP 有哪些支援的 extension 表格對應,可以安裝 composer 版本也很貼心。

# Install the latest version
install-php-extensions @composer
# Install the latest 1.x version
install-php-extensions @composer-1
# Install a specific version
install-php-extensions @composer-2.0.2

2024/10/15

Asynchronous Crawler In PHP, Node.js And Python

久久就要複習一下爬蟲寫法,不然不常用就生疏了,針對三種語言寫個 async 的版本吧。

node.js

const axios = require('axios');  
const urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
];  
  
(async () => {  
    try {  
        const requests = urls.map(url => axios.get(url));  
        const responses = await Promise.all(requests);  
  
        responses.forEach(response => {  
            console.log(response.data.id);  
        });  
    } catch (error) {  
        console.error(error.message);  
    }  
})();

Python

import aiohttp  
import asyncio  
  
urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
]  
  
  
async def fetch_url(session, url):  
    async with session.get(url) as response:  
        return await response.json()  
  
  
async def main():  
    async with aiohttp.ClientSession() as session:  
        tasks = [fetch_url(session, url) for url in urls]  
        responses = await asyncio.gather(*tasks)  
  
        for responses in responses:  
            print(responses.get('id'))  
  
  
if __name__ == '__main__':  
    asyncio.run(main())

PHP

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

include "vendor/autoload.php";

$urls = [
    'https://jsonplaceholder.typicode.com/todos/1',
    'https://jsonplaceholder.typicode.com/todos/2',
    'https://jsonplaceholder.typicode.com/todos/3',
];

$client = new Client([
    'verify' => false,
]);
$promises = [];

foreach ($urls as $url) {
    $promises[] = $client->getAsync($url);
}

$results = Utils::all($promises)->wait();

foreach ($results as $result) {
    echo json_decode($result->getBody()->getContents(), true)['id'], PHP_EOL;
}

2023/11/26

PHP Quality

要兼顧 PHP 開發品質,在 commit 之前可以用一些套件檢測,不管是否有符合 coding convention 或是有沒有語法上的錯誤,都可以預防性的監測。

composer require --dev phpstan/phpstan squizlabs/php_codesniffer friendsofphp/php-cs-fixer phpmd/phpmd phpunit/phpunit brianium/paratest

搭配 Makefile 可以減少每次執行要打的指令,此配置是針對 Laravel,可依照不同環境內容變動。

Makefile
fix-code-format:
	vendor/bin/php-cs-fixer fix --verbose

code-style-check:
	vendor/bin/phpstan analyse
	vendor/bin/phpcs --standard=PSR12 app tests
	vendor/bin/php-cs-fixer fix --dry-run
	vendor/bin/phpmd app,config,database,routes,tests text phpmd.xml

test: code-style-check
	vendor/bin/paratest --colors --processes 4 --runner=WrapperRunner
phpstan.neon
parameters:
  paths:
    - app
  excludePaths:
    - app/Http/Middleware/Authenticate.php
    - app/Providers/RouteServiceProvider.php

  level: 5

  parallel:
    processTimeout: 60.0
    maximumNumberOfProcesses: 32
    minimumNumberOfJobsPerProcess: 2

  checkMissingIterableValueType: false
.php-cs-fixer.php
<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = [
    'array_syntax' => ['syntax' => 'short'],
    'binary_operator_spaces' => ['default' => 'single_space'],
    'blank_line_after_namespace' => true,
    'blank_line_after_opening_tag' => true,
    'blank_line_before_statement' => true,
    'cast_spaces' => true,
    'class_definition' => true,
    'declare_equal_normalize' => true,
    'elseif' => true,
    'encoding' => true,
    'full_opening_tag' => true,
    'function_declaration' => true,
    'type_declaration_spaces' => true,
    'lowercase_cast' => true,
    'heredoc_to_nowdoc' => true,
    'include' => true,
    'indentation_type' => true,
    'lowercase_keywords' => true,
    'magic_constant_casing' => true,
    'method_argument_space' => true,
    'phpdoc_separation' => false,
    'native_function_casing' => true,
    'no_alias_functions' => true,
    'no_blank_lines_after_class_opening' => true,
    'no_blank_lines_after_phpdoc' => true,
    'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
    'no_closing_tag' => true,
    'no_empty_phpdoc' => true,
    'no_empty_statement' => true,
    'no_leading_import_slash' => true,
    'no_leading_namespace_whitespace' => true,
    'no_multiline_whitespace_around_double_arrow' => true,
    'no_short_bool_cast' => true,
    'no_singleline_whitespace_before_semicolons' => true,
    'no_spaces_after_function_name' => true,
    'no_spaces_around_offset' => ['positions' => ['inside']],
    'spaces_inside_parentheses' => true,
    'no_trailing_comma_in_singleline' => true,
    'no_trailing_whitespace' => true,
    'no_trailing_whitespace_in_comment' => true,
    'no_unneeded_control_parentheses' => true,
    'no_unreachable_default_argument_value' => true,
    'no_unused_imports' => true,
    'no_useless_return' => true,
    'no_whitespace_before_comma_in_array' => true,
    'no_whitespace_in_blank_line' => true,
    'normalize_index_brace' => true,
    'not_operator_with_successor_space' => true,
    'object_operator_without_whitespace' => true,
    'ordered_class_elements' => true,
    'ordered_imports' => ['sort_algorithm' => 'alpha'],
    'phpdoc_indent' => true,
    'phpdoc_line_span' => ['const' => 'multi', 'method' => 'multi', 'property' => 'single'],
    'phpdoc_no_access' => true,
    'phpdoc_no_useless_inheritdoc' => true,
    'phpdoc_order' => true,
    'phpdoc_scalar' => true,
    'phpdoc_single_line_var_spacing' => true,
    'phpdoc_to_comment' => true,
    'phpdoc_trim' => true,
    'phpdoc_no_alias_tag' => ['replacements' => ['type' => 'var']],
    'phpdoc_types' => true,
    'phpdoc_var_without_name' => true,
    'no_mixed_echo_print' => ['use' => 'echo'],
    'self_accessor' => true,
    'short_scalar_cast' => true,
    'simplified_null_return' => true,
    'single_blank_line_at_eof' => true,
    'blank_lines_before_namespace' => true,
    'single_class_element_per_statement' => true,
    'single_import_per_statement' => true,
    'single_line_after_imports' => true,
    'single_quote' => true,
    'space_after_semicolon' => true,
    'standardize_not_equals' => true,
    'switch_case_semicolon_to_colon' => true,
    'switch_case_space' => true,
    'ternary_operator_spaces' => true,
    'trailing_comma_in_multiline' => ['elements' => ['arrays']],
    'trim_array_spaces' => true,
    'unary_operator_spaces' => true,
    'visibility_required' => true,
    'whitespace_after_comma_in_array' => true,
    'concat_space' => ['spacing' => 'one'],
    'single_space_around_construct' => true,
    'control_structure_braces' => true,
    'braces_position' => true,
    'control_structure_continuation_position' => true,
    'declare_parentheses' => true,
    'statement_indentation' => true,
];

$excludes = [
    'bootstrap/cache',
    'storage',
    'vendor',
    'node_modules',
];

$finder = PhpCsFixer\Finder::create()
                           ->in(__DIR__)
                           ->exclude($excludes)
                           ->notName('*.xml')
                           ->notName('*.yml');

$config = new PhpCsFixer\Config();
$config->setRiskyAllowed(true)
       ->setIndent('    ')
       ->setLineEnding("\n")
       ->setRules($rules)
       ->setUsingCache(true)
       ->setFinder($finder);

return $config;
phpmd.xml
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Netask rule set"
    xmlns="http://pmd.sf.net/ruleset/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
    xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Netask code style rule set
</description>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
<rule ref="rulesets/codesize.xml/ExcessivePublicCount">
    <properties>
        <property name="minimum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyFields">
    <properties>
        <property name="maxfields" value="20"/>
    </properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyMethods"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity">
    <properties>
        <property name="maximum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/controversial.xml"/>
<rule ref="rulesets/design.xml"/>
<rule ref="rulesets/naming.xml/ShortVariable"/>
<rule ref="rulesets/naming.xml/LongVariable">
    <properties>
        <property name="maximum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/naming.xml/ShortMethodName">
    <properties>
        <property name="minimum" value="2"/>
    </properties>
</rule>
<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
<rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
<rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
<rule ref="rulesets/unusedcode.xml/UnusedPrivateField" />
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable" />

<exclude-pattern>app/Console/Kernel.php</exclude-pattern>
<exclude-pattern>app/Services/Service.php</exclude-pattern>
<exclude-pattern>tests/TestCase.php</exclude-pattern>
</ruleset>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <server name="DB_DATABASE" value=":memory:"/> -->
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

2021/12/03

Imitate Laravel Collection Sum Funtion in PHP, Python, Node.js

Laravel 包含了許多好用的套件,Collection 應該是最好用的,將陣列導進去以後可以做各種花式操作,自己嘗試在 PHP、Python、Node.js 上模擬一下作法。

PHP

<?php

class Collection
{
    private array $collections;

    public function __construct(array $collections)
    {
        $this->collections = $collections;
    }

    public function sum(Closure $callable = null): int
    {
        $callbackIn = ($callable === null) ? $this->isNotCallback() : $this->isCallback($callable);

        return array_reduce($this->collections, fn($carry, $item) => $carry + $callbackIn($item), 0);
    }

    private function isNotCallback(): Closure
    {
        return fn(int $value): int => $value;
    }

    private function isCallback(Closure $callable): Closure
    {
        return fn(int $value): int => $callable($value);
    }
}

$collection = new Collection([1, 2, 3]);
$cal = fn($item): int => $item + 5;

echo $collection->sum(), PHP_EOL;
echo $collection->sum($cal);

Python

from functools import reduce
from typing import Callable


class Collection(object):
    collections: list

    def __init__(self, collections: list):
        self.collections = collections

    def sum(self, callback=None) -> int:
        callback_in = self.is_not_callable() if callback is None else self.is_callable(callback)

        return reduce(lambda carry, item: carry + callback_in(item), self.collections, 0)

    @staticmethod
    def is_not_callable() -> Callable[[int], int]:
        return lambda x: x

    @staticmethod
    def is_callable(callback) -> Callable[[int], Callable[[int], int]]:
        return lambda x: callback(x)


def call_me() -> Callable[[int], any]:
    return lambda x: x + 5


collection = Collection([1, 2, 3])
print(collection.sum())
print(collection.sum(call_me()))

Node.js

class Collection {
    private collections: any[];

    constructor(collections: any[]) {
        this.collections = collections;
    }

    public sum(callback: Function = null) {
        const callbackIn = (callback === null) ? this.isNotCallable() : this.isCallable(callback);

        return this.collections.reduce((carry, item) => carry + callbackIn(item), 0);
    }

    private isNotCallable(): Function {
        return (value: any) => value;
    }

    private isCallable(callback): Function {
        return (value: any) => callback(value);
    }
}

const collections = new Collection([1, 2, 3]);
const cal = (value) => value + 5;

console.log(collections.sum());
console.log(collections.sum(cal));

2021/12/02

Laravel Connet To MSSQL Server On CentOS

PHP 安裝

# Install PHP 7.4
sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum -y install yum-utils
sudo yum-config-manager --enable remi-php74
sudo yum install -y php php-pdo php-odbc php-mbstring php-zip php-xml

MSSQL Driver 安裝,擇一安裝

1. MSSQL Driver(首選,支援版本 SQL Server 2000 以上)

sudo yum install php-sqlsrv
curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo

sudo yum remove unixODBC-utf16 unixODBC-utf16-devel #to avoid conflicts
sudo ACCEPT_EULA=Y yum install -y msodbcsql17

2. FreeTDS(Sql Server 2000)

sudo yum -y install php-mssql
sudo yum -y install unixODBC-*
sudo yum -y install freetds

cd /usr/lib64
sudo mv libtdsodbc.so.0.0.0 libtdsodbc.so
sudo vi /etc/odbcinst.ini

在最後增加:

[FreeTDS]
Description = ODBC for FreeTDS
Driver64    = /usr/lib64/libtdsodbc.so
Setup64     = /usr/lib64/libtdsodbc.so
FileUsage   = 1
sudo vi /etc/freetds.conf

在最後增加:

[SqlServer2000]
    host = {IP}
    port = 1433
    tds version = 8.0
    client charset = UTF-8

在 Laravel 的配置一切照舊,除了 MSSQL 的 host 要指到 SqlServer2000,若遇到時間格式的問題可以參考此篇,使用 FreeTDS 可以通吃,但怕會有無法預期的問題,不建議同一台 Server 同時使用兩種方式。

參考網站

2020/12/01

php-fpm config

apache 跟 nginx 都有接觸,nginx 上本就是用 php-fpm 跟 php 溝通,現在 apache 上我也捨棄內建的 php module,改用 fcgi 去溝通了,有幾個好處:

  1. apache 內建 php module 必須綁死 php 版本
  2. 因為綁死 php 版本如果有使用 virtual host 的話要做到每個網站跑自己的 php 版本很麻煩
  3. php-fpm 效能以及穩定性綜合值應該是優於 apache php module

在 Ubuntu 上我選擇了 ondrej/php 當作 repo

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
apt install php7.3 php7.3-fpm

CentOS 上使用 remi

dnf install dnf-utils http://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf module reset php
dnf module enable php:remi-7.4
dnf install php

Config

Apache

Through socket file
<VirtualHost>
    # someting config
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php7.2-fpm.sock|fcgi://localhost/"
    </FilesMatch>    
</VirtualHost>
Through port
<VirtualHost>
    # someting config
    <FilesMatch \.php$>
        setHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>  
</VirtualHost>

Apache 使用上必須確定 proxy_module 以及 proxy_fcgi_module 這兩個 module 有開啟,前者必須在後者上方先載入。

Nginx

location ~* \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_split_path_info ^(.+\.php)(.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Reference