├── src ├── configs │ ├── windows_extensions │ ├── ini │ │ ├── xdebug.ini │ │ ├── php.ini │ │ └── jit.ini │ ├── composer.env │ ├── php_debug_packages │ ├── php_packages │ ├── php-versions.json │ ├── os_releases.csv │ ├── mod_priority │ ├── pm │ │ ├── phpunit.json │ │ └── php.json │ ├── brew_extensions │ └── tools_schema.json ├── scripts │ ├── extensions │ │ ├── patches │ │ │ ├── protobuf.sh │ │ │ ├── pdo_oci.sh │ │ │ ├── firebird.sh │ │ │ ├── geos.sh │ │ │ ├── common.sh │ │ │ ├── http.sh │ │ │ └── phpize.sh │ │ ├── geos.sh │ │ ├── sqlsrv.sh │ │ ├── gearman.sh │ │ ├── firebird.ps1 │ │ ├── intl.sh │ │ ├── ioncube.ps1 │ │ ├── blackfire.ps1 │ │ ├── blackfire.sh │ │ ├── ioncube.sh │ │ ├── event.sh │ │ ├── zephir_parser.sh │ │ ├── http.ps1 │ │ ├── firebird.sh │ │ ├── cubrid.sh │ │ ├── sqlsrv.ps1 │ │ ├── oci.ps1 │ │ ├── zephir_parser.ps1 │ │ ├── phalcon.sh │ │ ├── couchbase.sh │ │ ├── oci.sh │ │ ├── http.sh │ │ ├── extension_map.php │ │ ├── phalcon.ps1 │ │ ├── relay.sh │ │ └── source.sh │ └── tools │ │ ├── retry.sh │ │ ├── symfony.ps1 │ │ ├── blackfire.ps1 │ │ ├── grpc_php_plugin.ps1 │ │ ├── symfony.sh │ │ ├── blackfire.sh │ │ ├── protoc.sh │ │ ├── protoc.ps1 │ │ ├── grpc_php_plugin.sh │ │ └── brew.sh ├── packagist.ts ├── fetch.ts ├── config.ts ├── install.ts └── coverage.ts ├── .github ├── FUNDING.yml ├── codeql │ └── codeql-configuration.yml ├── dependabot.yml ├── SECURITY.md ├── workflows │ ├── codeql.yml │ ├── node.yml │ ├── publish.yml │ └── php.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature.yml │ └── bug.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── .prettierrc.json ├── jest.config.js ├── tsconfig.json ├── __tests__ ├── config.test.ts ├── fetch.test.ts ├── packagist.test.ts ├── coverage.test.ts └── install.test.ts ├── action.yml ├── LICENSE ├── examples ├── bedrock.yml ├── blackfire-player.yml ├── blackfire.yml ├── laminas-mvc.yml ├── codeigniter.yml ├── slim-framework.yml ├── lumen.yml ├── symfony.yml ├── laravel.yml ├── sage.yml ├── symfony-mysql.yml ├── symfony-postgres.yml ├── laravel-mysql.yml ├── yii2-mysql.yml ├── phalcon-mysql.yml ├── laravel-postgres.yml ├── phalcon-postgres.yml ├── lumen-mysql.yml ├── yii2-postgres.yml ├── lumen-postgres.yml ├── cakephp.yml ├── cakephp-postgres.yml └── cakephp-mysql.yml ├── eslint.config.mjs ├── .gitignore └── package.json /src/configs/windows_extensions: -------------------------------------------------------------------------------- 1 | xdebug 2 | pcov -------------------------------------------------------------------------------- /src/configs/ini/xdebug.ini: -------------------------------------------------------------------------------- 1 | xdebug.mode=coverage 2 | -------------------------------------------------------------------------------- /src/configs/ini/php.ini: -------------------------------------------------------------------------------- 1 | date.timezone=UTC 2 | memory_limit=-1 3 | -------------------------------------------------------------------------------- /src/configs/ini/jit.ini: -------------------------------------------------------------------------------- 1 | opcache.enable=1 2 | opcache.jit_buffer_size=256M 3 | opcache.jit=1235 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: shivammathur 4 | -------------------------------------------------------------------------------- /.github/codeql/codeql-configuration.yml: -------------------------------------------------------------------------------- 1 | name : CodeQL Configuration 2 | 3 | paths: 4 | - './src' 5 | -------------------------------------------------------------------------------- /src/configs/composer.env: -------------------------------------------------------------------------------- 1 | COMPOSER_PROCESS_TIMEOUT=0 2 | COMPOSER_NO_INTERACTION=1 3 | COMPOSER_NO_AUDIT=1 4 | -------------------------------------------------------------------------------- /src/configs/php_debug_packages: -------------------------------------------------------------------------------- 1 | cgi 2 | cli 3 | curl 4 | fpm 5 | intl 6 | mbstring 7 | mysql 8 | opcache 9 | pgsql 10 | xml 11 | zip 12 | -------------------------------------------------------------------------------- /src/configs/php_packages: -------------------------------------------------------------------------------- 1 | cgi 2 | cli 3 | curl 4 | dev 5 | fpm 6 | intl 7 | mbstring 8 | mysql 9 | opcache 10 | pgsql 11 | xml 12 | zip 13 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/protobuf.sh: -------------------------------------------------------------------------------- 1 | patch_protobuf() { 2 | mkdir -p third_party/wyhash 3 | cp ../../../../third_party/wyhash/* third_party/wyhash 4 | } 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | target-branch: "develop" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /src/configs/php-versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "lowest": "8.1", 3 | "highest": "8.4", 4 | "latest": "8.4", 5 | "nightly": "8.5", 6 | "5.x": "5.6", 7 | "7.x": "7.4", 8 | "8.x": "8.4" 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": false, 4 | "endOfLine": "auto", 5 | "parser": "typescript", 6 | "printWidth": 80, 7 | "semi": true, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "none", 11 | "useTabs": false 12 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true, 11 | collectCoverage: true 12 | }; 13 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/pdo_oci.sh: -------------------------------------------------------------------------------- 1 | patch_pdo_oci() { 2 | get -q -n config.m4 https://raw.githubusercontent.com/php/php-src/PHP-8.0/ext/pdo_oci/config.m4 3 | if [[ ${version:?} =~ 5.[3-6] ]]; then 4 | sudo sed -i '' "/PHP_CHECK_PDO_INCLUDES/d" config.m4 2>/dev/null || sudo sed -i "/PHP_CHECK_PDO_INCLUDES/d" config.m4 5 | fi 6 | } 7 | -------------------------------------------------------------------------------- /src/scripts/tools/retry.sh: -------------------------------------------------------------------------------- 1 | function retry { 2 | local try=0 3 | 4 | until "$@"; do 5 | exit_code="$?" 6 | try=$((try + 1)) 7 | 8 | if [ $try -lt 10 ]; then 9 | sleep "$((2 ** try))" 10 | else 11 | return $exit_code 12 | fi 13 | done 14 | 15 | return 0 16 | } 17 | 18 | function git_retry { 19 | retry git "$@" 20 | } -------------------------------------------------------------------------------- /src/configs/os_releases.csv: -------------------------------------------------------------------------------- 1 | 8,jessie 2 | 9,stretch 3 | 10,buster 4 | 11,bullseye 5 | 12,bookworm 6 | 13,trixie 7 | 16.04 LTS,xenial 8 | 16.10,yakkety 9 | 17.04,zesty 10 | 17.10,artful 11 | 18.04 LTS,bionic 12 | 18.10,cosmic 13 | 19.04,disco 14 | 19.10,eoan 15 | 20.04 LTS,focal 16 | 20.10,groovy 17 | 21.04,hirsute 18 | 21.10,impish 19 | 22.04,jammy 20 | 23.04,lunar 21 | 23.10,mantic 22 | 24.04,noble 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "lib": [ 6 | "ES2021" 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "outDir": "./lib", 12 | "removeComments": true, 13 | "rootDir": "./src", 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "ES2021" 17 | }, 18 | "exclude": ["__tests__", "lib", "node_modules"] 19 | } -------------------------------------------------------------------------------- /src/configs/mod_priority: -------------------------------------------------------------------------------- 1 | apc=25 2 | apcu_bc=25 3 | apcu-bc=25 4 | blackfire=30 5 | couchbase=30 6 | decimal=30 7 | ds=30 8 | event=30 9 | ev=30 10 | grpc=30 11 | http=25 12 | pecl_http=25 13 | pecl-http=25 14 | inotify=30 15 | libvirt-php=40 16 | mailparse=25 17 | maxminddb=30 18 | memcached=25 19 | mysqlnd=10 20 | mysqlnd_ms=30 21 | opcache=10 22 | openswoole=25 23 | pdo=10 24 | phalcon=35 25 | protobuf=30 26 | psr=15 27 | rdkafka=30 28 | swoole=25 29 | vips=30 30 | xml=15 31 | zstd=30 32 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/firebird.sh: -------------------------------------------------------------------------------- 1 | patch_firebird() { 2 | if [[ "${version:?}" =~ ${old_versions:?} ]]; then 3 | sudo sed -i '' '/PHP_CHECK_PDO_INCLUDES/d' config.m4 2>/dev/null || sudo sed -i '/PHP_CHECK_PDO_INCLUDES/d' config.m4 4 | fi 5 | lib_arch=$(gcc -dumpmachine) 6 | lib_dir=/usr/lib/"$lib_arch" 7 | if [ -d "$lib_dir" ]; then 8 | sudo ln -sf "$lib_dir"/libfbclient.so.2 /usr/lib/libfbclient.so 9 | sudo ln -sf "$lib_dir"/libib_util.so /usr/lib/ 10 | fi 11 | } 12 | -------------------------------------------------------------------------------- /src/configs/pm/phpunit.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "phpunit", 5 | "pattern": [ 6 | { 7 | "regexp": "^\\d+\\)\\s.*$" 8 | }, 9 | { 10 | "regexp": "^(.*Failed\\sasserting\\sthat.*)$", 11 | "message": 1 12 | }, 13 | { 14 | "regexp": "^\\s*$" 15 | }, 16 | { 17 | "regexp": "^(.*):(\\d+)$", 18 | "file": 1, 19 | "line": 2 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/extensions/geos.sh: -------------------------------------------------------------------------------- 1 | # Helper function to compile and install geos 2 | add_geos_helper() { 3 | export GEOS_LINUX_LIBS='libgeos-dev' 4 | export GEOS_DARWIN_LIBS='geos' 5 | add_extension_from_source geos https://github.com libgeos php-geos 1.0.0 extension get 6 | } 7 | 8 | # Function to add geos 9 | add_geos() { 10 | enable_extension "geos" "extension" 11 | if check_extension "geos"; then 12 | add_log "${tick:?}" "geos" "Enabled" 13 | else 14 | add_geos_helper >/dev/null 2>&1 15 | add_extension_log "geos" "Installed and enabled" 16 | fi 17 | } 18 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/geos.sh: -------------------------------------------------------------------------------- 1 | patch_geos() { 2 | if [ "$(php -r "echo PHP_VERSION_ID;")" -ge 70000 ]; then 3 | sed -i~ -e "s/, ce->name/, ZSTR_VAL(ce->name)/; s/ulong /zend_ulong /" geos.c 4 | fi 5 | get -q -n /tmp/php8.patch https://git.remirepo.net/cgit/rpms/php/php-geos.git/plain/0003-add-all-arginfo-and-fix-build-with-PHP-8.patch 6 | get -q -n /tmp/toString.patch https://git.remirepo.net/cgit/rpms/php/php-geos.git/plain/0006-fix-__toString-with-8.2.patch 7 | patch -p1 < /tmp/php8.patch 2>/dev/null || true 8 | patch -p1 < /tmp/toString.patch 2>/dev/null || true 9 | } 10 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/common.sh: -------------------------------------------------------------------------------- 1 | process_file() { 2 | local file=$1 3 | sed -i'' -e '0,/#include.*\(php_lcg.h\|php_mt_rand.h\|php_rand.h\|standard\/php_random\.h\).*/s//\#include /' "$file" 4 | sed -i'' -e '/#include.*\(php_lcg.h\|php_mt_rand.h\|php_rand.h\|standard\/php_random\.h\)/d' "$file" 5 | } 6 | 7 | export -f process_file 8 | 9 | # Compare with 8.3 so it runs only on 8.4 and above 10 | if [[ $(printf "%s\n%s" "${version:?}" "8.3" | sort -V | head -n1) != "$version" ]]; then 11 | find . -type f \( -name "*.c" -o -name "*.h" \) -exec bash -c 'process_file "$0"' {} \; 12 | fi 13 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/http.sh: -------------------------------------------------------------------------------- 1 | patch_pecl_http() { 2 | if [ "$(uname -s)" = 'Darwin' ] && ! [[ ${version:?} =~ ${old_versions:?} ]]; then 3 | if [[ ${version:?} =~ 5.6|7.[0-4] ]]; then 4 | sed -i '' -e "s|ext/propro|$(brew --prefix propro@"${version:?}")/include/php/ext/propro@${version:?}|" "./src/php_http_api.h" 5 | fi 6 | sed -i '' -e "s|ext/raphf|$(brew --prefix raphf@"${version:?}")/include/php/ext/raphf@${version:?}|" "./src/php_http_api.h" 7 | if [ "${version:?}" = "5.6" ]; then 8 | sed -i '' -e "s|\$abs_srcdir|\$abs_srcdir ${brew_prefix:?}/include|" -e "s|/ext/propro|/php/ext/propro@5.6|" -e "s|/ext/raphf|/php/ext/raphf@5.6|" "./config9.m4" 9 | fi 10 | fi 11 | } 12 | -------------------------------------------------------------------------------- /src/configs/pm/php.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "php_native_error", 5 | "severity": "error", 6 | "pattern": [ 7 | { 8 | "regexp": "^(.*error):\\s+\\s+(.+) in (.+) on line (\\d+)$", 9 | "code": 1, 10 | "message": 2, 11 | "file": 3, 12 | "line": 4 13 | } 14 | ] 15 | }, { 16 | "owner": "php_native_warning", 17 | "severity": "warning", 18 | "pattern": [ 19 | { 20 | "regexp": "^(.*Warning|.*Deprecated|.*Notice):\\s+\\s+(.+) in (.+) on line (\\d+)$", 21 | "code": 1, 22 | "message": 2, 23 | "file": 3, 24 | "line": 4 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/scripts/extensions/sqlsrv.sh: -------------------------------------------------------------------------------- 1 | # Function to get sqlsrv and pdo_sqlsrv version. 2 | get_sqlsrv_version() { 3 | if [[ "${version:?}" =~ 7.[0-3] ]]; then 4 | echo '5.9.0' 5 | elif [[ "${version:?}" =~ 7.4 ]]; then 6 | echo '5.10.1' 7 | elif [[ "${version:?}" =~ 8.0 ]]; then 8 | echo '5.11.1' 9 | else 10 | # Return an empty string so that pecl will install the latest version. 11 | echo '' 12 | fi 13 | } 14 | 15 | # Function to install sqlsrv and pdo_sqlsrv. 16 | add_sqlsrv() { 17 | ext=$1 18 | ext_version=$(get_sqlsrv_version) 19 | if [ "$(uname -s)" = 'Linux' ]; then 20 | install_packages unixodbc-dev 21 | add_pecl_extension "$ext" "$ext_version" extension 22 | else 23 | add_brew_extension "$ext" extension 24 | fi 25 | } 26 | -------------------------------------------------------------------------------- /src/scripts/extensions/patches/phpize.sh: -------------------------------------------------------------------------------- 1 | # Function to get phpize location on darwin. 2 | get_phpize() { 3 | if [[ "${version:?}" =~ 5.[3-5] ]]; then 4 | echo '/opt/local/bin/phpize' 5 | else 6 | echo "/usr/local/bin/$(readlink /usr/local/bin/phpize)" 7 | fi 8 | } 9 | 10 | # Function to patch phpize to link to php headers on darwin. 11 | patch_phpize() { 12 | if [ "$(uname -s)" = "Darwin" ]; then 13 | sudo cp "$phpize_orig" "$phpize_orig.bck" 14 | sudo sed -i '' 's~includedir=.*~includedir="$(xcrun --show-sdk-path)/usr/include/php"~g' "$phpize_orig" 15 | fi 16 | } 17 | 18 | # Function to restore phpize. 19 | restore_phpize() { 20 | if [ "$os" = "Darwin" ]; then 21 | sudo mv "$phpize_orig.bck" "$phpize_orig" || true 22 | fi 23 | } 24 | 25 | os="$(uname -s)" 26 | phpize_orig="$(get_phpize)" 27 | -------------------------------------------------------------------------------- /src/configs/brew_extensions: -------------------------------------------------------------------------------- 1 | amqp=amqp 2 | apcu=apcu 3 | ast=ast 4 | couchbase=couchbase 5 | ds=ds 6 | event=event 7 | expect=expect 8 | gearman=gearman 9 | gnupg=gnupg 10 | grpc=grpc 11 | igbinary=igbinary 12 | imagick=imagick 13 | imap=imap 14 | lua=lua 15 | mailparse=mailparse 16 | mcrypt=mcrypt 17 | memcache=memcache 18 | memcached=memcached 19 | mongodb=mongodb 20 | msgpack=msgpack 21 | pcov=pcov 22 | pdo_sqlsrv=pdo_sqlsrv 23 | pecl_http=http 24 | phalcon3=phalcon 25 | phalcon4=phalcon 26 | phalcon5=phalcon 27 | propro=propro 28 | protobuf=protobuf 29 | psr=psr 30 | raphf=raphf 31 | rdkafka=rdkafka 32 | redis=redis 33 | snmp=snmp 34 | sqlsrv=sqlsrv 35 | ssh2=ssh2 36 | swoole=swoole 37 | uuid=uuid 38 | v8js=v8js 39 | vips=vips 40 | vld=vld 41 | xdebug=xdebug 42 | xdebug2=xdebug 43 | xlswriter=xlswriter 44 | yaml=yaml 45 | zmq=zmq 46 | -------------------------------------------------------------------------------- /src/scripts/extensions/gearman.sh: -------------------------------------------------------------------------------- 1 | # Helper function to add gearman extension. 2 | add_gearman_helper() { 3 | add_ppa ondrej/pkg-gearman 4 | install_packages libgearman-dev 5 | enable_extension gearman extension 6 | if ! check_extension gearman; then 7 | status="Installed and enabled" 8 | if [[ "${version:?}" =~ 5.[3-5] ]]; then 9 | pecl_install gearman-1.1.2 10 | else 11 | install_packages php"${version:?}"-gearman || pecl_install gearman 12 | fi 13 | enable_extension gearman extension 14 | fi 15 | } 16 | 17 | # Function to add gearman extension. 18 | add_gearman() { 19 | status="Enabled" 20 | if [ "$(uname -s)" = 'Linux' ]; then 21 | add_gearman_helper >/dev/null 2>&1 22 | add_extension_log "gearman" "$status" 23 | else 24 | add_brew_extension gearman extension 25 | fi 26 | } 27 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The latest patch versions of `v1` and `v2` releases of this project are supported for security updates. 6 | 7 | ## Supported PHP Versions 8 | 9 | This security policy only applies to the latest patches of the following PHP versions. 10 | 11 | | Version | Supported | 12 | |---------|--------------------| 13 | | 8.1 | :white_check_mark: | 14 | | 8.2 | :white_check_mark: | 15 | | 8.3 | :white_check_mark: | 16 | 17 | ## Reporting a Vulnerability 18 | 19 | If you have found any issues that might have security implications in the versions supported, please send a report privately to [contact@shivammathur.com](mailto:contact@shivammathur.com). 20 | Do not report security reports publicly. 21 | 22 | ## Tidelift 23 | 24 | If you use this GitHub Action through a Tidelift subscription, please refer to [https://tidelift.com/security](https://tidelift.com/security). -------------------------------------------------------------------------------- /src/scripts/extensions/firebird.ps1: -------------------------------------------------------------------------------- 1 | Function Add-Choco() { 2 | try { 3 | if($null -eq (Get-Command -Name choco.exe -ErrorAction SilentlyContinue)) { 4 | # Source: https://docs.chocolatey.org/en-us/choco/setup 5 | Set-ExecutionPolicy Bypass -Scope Process -Force 6 | [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 7 | Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) 8 | } 9 | } catch { } 10 | } 11 | 12 | Function Add-Firebird() { 13 | Add-Choco > $null 2>&1 14 | choco install firebird -params '/ClientAndDevTools' -y --force > $null 2>&1 15 | if((Get-ChildItem $env:ProgramFiles\**\**\fbclient.dll | Measure-Object).Count -eq 1) { 16 | Add-Extension pdo_firebird 17 | } else { 18 | Add-Log $cross pdo_firebird "Could not install pdo_firebird on PHP $( $installed.FullVersion )" 19 | } 20 | } -------------------------------------------------------------------------------- /__tests__/config.test.ts: -------------------------------------------------------------------------------- 1 | import * as config from '../src/config'; 2 | 3 | describe('Config tests', () => { 4 | it.each` 5 | ini_values | os | output 6 | ${'a=b, c=d'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b\nc=d"'} 7 | ${'a=b, c=d'} | ${'linux'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} 8 | ${'a=b, c=d'} | ${'darwin'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} 9 | ${'a=b & ~c'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'b & ~c\'"'} 10 | ${'a="~(b)"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'~(b)\'"'} 11 | ${'a="b, c"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b, c"'} 12 | ${'a=b, c=d'} | ${'openbsd'} | ${'Platform openbsd is not supported'} 13 | `('checking addINIValues on $os', async ({ini_values, os, output}) => { 14 | expect(await config.addINIValues(ini_values, os)).toContain(output); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup PHP Action' 2 | author: shivammathur 3 | description: 'GitHub Action for PHP' 4 | branding: 5 | color: 'purple' 6 | icon: 'play-circle' 7 | inputs: 8 | php-version: 9 | description: 'Setup PHP version.' 10 | required: false 11 | php-version-file: 12 | description: 'Setup PHP version from a file.' 13 | required: false 14 | extensions: 15 | description: 'Setup PHP extensions.' 16 | required: false 17 | ini-file: 18 | description: 'Set base ini file.' 19 | required: false 20 | default: 'production' 21 | ini-values: 22 | description: 'Add values to php.ini.' 23 | required: false 24 | coverage: 25 | description: 'Setup code coverage driver.' 26 | required: false 27 | tools: 28 | description: 'Setup popular tools globally.' 29 | required: false 30 | outputs: 31 | php-version: 32 | description: 'PHP version in semver format' 33 | runs: 34 | using: 'node20' 35 | main: 'dist/index.js' 36 | -------------------------------------------------------------------------------- /src/scripts/tools/symfony.ps1: -------------------------------------------------------------------------------- 1 | Function Add-Symfony() { 2 | $arch_name ='amd64' 3 | if(-not([Environment]::Is64BitOperatingSystem) -or $version -lt '7.0') { 4 | $arch_name = '386' 5 | } 6 | $url = "https://github.com/symfony-cli/symfony-cli/releases/latest/download/symfony-cli_windows_${arch_name}.zip" 7 | Get-File -Url $url -OutFile $bin_dir\symfony.zip >$null 2>&1 8 | Expand-Archive -Path $bin_dir\symfony.zip -DestinationPath $bin_dir -Force >$null 2>&1 9 | if(Test-Path $bin_dir\symfony.exe) { 10 | Copy-Item -Path $bin_dir\symfony.exe -Destination $bin_dir\symfony-cli.exe > $null 2>&1 11 | Add-ToProfile $current_profile 'symfony' "New-Alias symfony $bin_dir\symfony.exe" 12 | Add-ToProfile $current_profile 'symfony_cli' "New-Alias symfony-cli $bin_dir\symfony-cli.exe" 13 | $tool_version = Get-ToolVersion symfony "-V" 14 | Add-Log $tick "symfony-cli" "Added symfony-cli $tool_version" 15 | } else { 16 | Add-Log $cross "symfony-cli" "Could not setup symfony-cli" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/packagist.ts: -------------------------------------------------------------------------------- 1 | import * as cv from 'compare-versions'; 2 | import * as fetch from './fetch'; 3 | 4 | type RS = Record; 5 | type RSRS = Record; 6 | 7 | export async function search( 8 | package_name: string, 9 | php_version: string 10 | ): Promise { 11 | const response = await fetch.fetch( 12 | `https://repo.packagist.org/p2/${package_name}.json` 13 | ); 14 | if (response.error || response.data === '[]') { 15 | return null; 16 | } 17 | 18 | const data = JSON.parse(response['data']); 19 | if (data && data.packages) { 20 | const versions = data.packages[package_name]; 21 | versions.sort((a: RS, b: RS) => cv.compareVersions(b.version, a.version)); 22 | 23 | const result = versions.find((versionData: RSRS) => { 24 | if (versionData?.require?.php) { 25 | return versionData?.require?.php 26 | .split('|') 27 | .some( 28 | require => require && cv.satisfies(php_version + '.0', require) 29 | ); 30 | } 31 | return false; 32 | }); 33 | 34 | return result ? result.version : null; 35 | } 36 | return null; 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) shivammathur and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL Workflow 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 15 * * 6' 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | codeql: 11 | permissions: 12 | actions: read # for github/codeql-action/init to get workflow details 13 | contents: read # for actions/checkout to fetch code 14 | security-events: write # for github/codeql-action/autobuild to send a status report 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | with: 20 | fetch-depth: 2 21 | 22 | - name: Initialize CodeQL 23 | uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13 24 | with: 25 | config-file: ./.github/codeql/codeql-configuration.yml 26 | languages: javascript 27 | 28 | - name: Autobuild 29 | uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13 30 | 31 | - name: Perform CodeQL Analysis 32 | uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13 33 | -------------------------------------------------------------------------------- /src/scripts/tools/blackfire.ps1: -------------------------------------------------------------------------------- 1 | # Function to add blackfire cli. 2 | Function Add-Blackfire() { 3 | $arch_name ='amd64' 4 | if(-not([Environment]::Is64BitOperatingSystem) -or $version -lt '7.0') { 5 | $arch_name = '386' 6 | } 7 | $cli_version = (Invoke-RestMethod https://blackfire.io/api/v1/releases).cli 8 | $url = "https://packages.blackfire.io/binaries/blackfire/${cli_version}/blackfire-windows_${arch_name}.zip" 9 | Get-File -Url $url -OutFile $bin_dir\blackfire.zip >$null 2>&1 10 | Expand-Archive -Path $bin_dir\blackfire.zip -DestinationPath $bin_dir -Force >$null 2>&1 11 | Add-ToProfile $current_profile 'blackfire' "New-Alias blackfire $bin_dir\blackfire.exe" 12 | if ((Test-Path env:BLACKFIRE_SERVER_ID) -and (Test-Path env:BLACKFIRE_SERVER_TOKEN)) { 13 | blackfire agent:config --server-id=$env:BLACKFIRE_SERVER_ID --server-token=$env:BLACKFIRE_SERVER_TOKEN >$null 2>&1 14 | } 15 | if ((Test-Path env:BLACKFIRE_CLIENT_ID) -and (Test-Path env:BLACKFIRE_CLIENT_TOKEN)) { 16 | blackfire client:config --client-id=$env:BLACKFIRE_CLIENT_ID --client-token=$env:BLACKFIRE_CLIENT_TOKEN --ca-cert=$php_dir\ssl\cacert.pem >$null 2>&1 17 | } 18 | Add-Log $tick "blackfire" "Added blackfire $cli_version" 19 | } 20 | -------------------------------------------------------------------------------- /src/scripts/extensions/intl.sh: -------------------------------------------------------------------------------- 1 | # Function to install ICU 2 | install_icu() { 3 | icu=$1 4 | if [ "$(php -i | grep "ICU version =>" | sed -e "s|.*=> s*||")" != "$icu" ]; then 5 | get -q -n /tmp/icu.tar.zst "https://github.com/shivammathur/icu-intl/releases/download/icu4c/icu4c-$icu$arch_suffix.tar.zst" 6 | sudo tar -I zstd -xf /tmp/icu.tar.zst -C /usr/local 7 | sudo cp -r /usr/local/icu/lib/* /usr/lib/"$(uname -m)"-linux-gnu/ 8 | fi 9 | } 10 | 11 | # Function to add ext-intl with the given version of ICU 12 | add_intl() { 13 | icu=$(echo "$1" | cut -d'-' -f 2) 14 | supported_version=$(get -s -n "" https://api.github.com/repos/shivammathur/icu-intl/releases | grep -Po "${icu//./\\.}" | head -n 1) 15 | if [ "$icu" != "$supported_version" ]; then 16 | add_log "${cross:?}" "intl" "ICU $icu is not supported" 17 | else 18 | [ "${ts:?}" = 'zts' ] && suffix='-zts' 19 | install_icu "$icu" >/dev/null 2>&1 20 | get -q -n "${ext_dir:?}/intl.so" "https://github.com/shivammathur/icu-intl/releases/download/intl/php${version:?}-intl-$icu$suffix$arch_suffix.so" 21 | enable_extension intl extension 22 | add_extension_log intl "Installed and enabled with ICU $icu" 23 | fi 24 | } 25 | 26 | arch="$(uname -m)" 27 | [[ "$arch" = 'arm64' || "$arch" = 'aarch64' ]] && arch_suffix='-arm64' || arch_suffix='' 28 | -------------------------------------------------------------------------------- /src/scripts/tools/grpc_php_plugin.ps1: -------------------------------------------------------------------------------- 1 | Function Add-Msys2() { 2 | $msys_location = 'C:\msys64' 3 | if (-not(Test-Path $msys_location)) { 4 | choco install msys2 -y >$null 2>&1 5 | $msys_location = 'C:\tools\msys64' 6 | } 7 | return $msys_location 8 | } 9 | 10 | Function Add-GrpcPhpPlugin() { 11 | $msys_location = Add-Msys2 12 | $arch = arch 13 | if($arch -eq 'arm64' -or $arch -eq 'aarch64') { 14 | $grpc_package = 'mingw-w64-clang-aarch64-grpc' 15 | } else { 16 | $grpc_package = 'mingw-w64-x86_64-grpc' 17 | } 18 | $logs = . $msys_location\usr\bin\bash -l -c "pacman -S --noconfirm $grpc_package" >$null 2>&1 19 | $grpc_version = Get-ToolVersion 'Write-Output' "$logs" 20 | Add-Path $msys_location\mingw64\bin 21 | Set-Output grpc_php_plugin_path "$msys_location\mingw64\bin\grpc_php_plugin.exe" 22 | Add-ToProfile $current_profile 'grpc_php_plugin' "New-Alias grpc_php_plugin $msys_location\mingw64\bin\grpc_php_plugin.exe" 23 | Add-Log $tick "grpc_php_plugin" "Added grpc_php_plugin $grpc_version" 24 | printf "$env:GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "grpc_php_plugin" "Click to read the grpc_php_plugin related license information" 25 | Write-Output (Invoke-WebRequest https://raw.githubusercontent.com/grpc/grpc/master/LICENSE).Content 26 | Write-Output "$env:END_GROUP" 27 | } 28 | -------------------------------------------------------------------------------- /__tests__/fetch.test.ts: -------------------------------------------------------------------------------- 1 | import * as fetch from '../src/fetch'; 2 | import nock from 'nock'; 3 | 4 | it('checking fetch', async () => { 5 | const host_url = 'https://example.com'; 6 | const manifest_url = host_url + '/manifest'; 7 | const ping_url = host_url + '/ping'; 8 | 9 | nock(host_url) 10 | .get('/manifest') 11 | .reply(200, {latest: 'latest'}) 12 | .get('/manifest', '', { 13 | reqheaders: {authorization: 'Bearer invalid_token'} 14 | }) 15 | .reply(401, {error: '401: Unauthorized'}) 16 | .get('/ping') 17 | .twice() 18 | .reply(301, undefined, { 19 | Location: host_url + '/pong' 20 | }) 21 | .get('/pong') 22 | .reply(200, 'pong'); 23 | 24 | let response: Record = await fetch.fetch(manifest_url); 25 | expect(response.error).toBe(undefined); 26 | expect(response.data).toContain('latest'); 27 | 28 | response = await fetch.fetch(ping_url, '', 1); 29 | expect(response.error).toBe(undefined); 30 | expect(response.data).toContain('pong'); 31 | 32 | response = await fetch.fetch(ping_url, '', 0); 33 | expect(response.error).toBe('301: Redirect error'); 34 | expect(response.data).toBe(undefined); 35 | 36 | response = await fetch.fetch(manifest_url, 'invalid_token'); 37 | expect(response.error).not.toBe(undefined); 38 | expect(response.data).toBe(undefined); 39 | }); 40 | -------------------------------------------------------------------------------- /examples/bedrock.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for roots/bedrock 2 | name: Testing Bedrock 3 | on: [push, pull_request] 4 | jobs: 5 | bedrock: 6 | name: Bedrock (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | php-versions: ['7.4', '8.0', '8.1'] 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | # Docs: https://github.com/shivammathur/setup-php 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php-versions }} 21 | 22 | - name: Get composer cache directory 23 | id: composer-cache 24 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 25 | 26 | - uses: actions/cache@v3 27 | with: 28 | path: ${{ steps.composer-cache.outputs.dir }} 29 | # Use composer.json for key, if composer.lock is not committed. 30 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 32 | restore-keys: ${{ runner.os }}-composer- 33 | 34 | - name: Install Composer dependencies 35 | run: composer install --no-progress --prefer-dist --optimize-autoloader 36 | 37 | - name: PHP test 38 | run: composer test 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug Fix, ⚙ Improvement or 🎉 New Feature 3 | about: You found a bug, want to improve something or add a new feature 4 | labels: bug or enhancement 5 | 6 | --- 7 | 8 | ## A Pull Request should be associated with a Discussion. 9 | 10 | > If you're fixing a bug, adding a new feature or improving something please provide the details in discussions, 11 | > so that the development can be pointed in the intended direction. 12 | 13 | Related discussion: 14 | 15 | > Further notes in [Contribution Guidelines](.github/CONTRIBUTING.md) 16 | > Thank you for your contribution. 17 | 18 | ### Description 19 | 20 | This PR [briefly explain what it does] 21 | 22 | > In case this PR introduced TypeScript/JavaScript code changes: 23 | 24 | - [ ] I have written test cases for the changes in this pull request 25 | - [ ] I have run `npm run format` before the commit. 26 | - [ ] I have run `npm run lint` before the commit. 27 | - [ ] I have run `npm run release` before the commit. 28 | - [ ] `npm test` returns with no unit test errors and all code covered. 29 | 30 | > In case this PR edits any scripts: 31 | 32 | - [ ] I have checked the edited scripts for syntax. 33 | - [ ] I have tested the changes in an integration test (If yes, provide workflow YAML and link). 34 | 35 | -------------------------------------------------------------------------------- /src/scripts/extensions/ioncube.ps1: -------------------------------------------------------------------------------- 1 | # Function to log result of a operation. 2 | Function Add-LicenseLog() { 3 | printf "$env:GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "ioncube" "Click to read the ioncube loader license information" 4 | Get-Content $ext_dir\ioncube\LICENSE.txt 5 | Write-Output "$env:END_GROUP" 6 | } 7 | 8 | # Function to add ioncube extension. 9 | Function Add-Ioncube() { 10 | try { 11 | $status = 'Enabled' 12 | if (-not(Test-Path $ext_dir\php_ioncube.dll)) { 13 | $status = 'Installed and enabled' 14 | $arch_part = $arch 15 | if ($arch -eq 'x64') { 16 | $arch_part = 'x86-64' 17 | } 18 | $vc = $installed.VCVersion 19 | $ts_part = "" 20 | if (-not($installed.ThreadSafe)) { 21 | $ts_part = "_nonts" 22 | } 23 | Get-File -Url "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win$ts_part`_vc$vc`_$arch_part.zip" -OutFile $ext_dir\ioncube.zip 24 | Expand-Archive -Path $ext_dir\ioncube.zip -DestinationPath $ext_dir -Force 25 | Copy-Item $ext_dir\ioncube\ioncube_loader_win_$version.dll $ext_dir\php_ioncube.dll 26 | } 27 | "zend_extension=$ext_dir\php_ioncube.dll`r`n" + (Get-Content $php_dir\php.ini -Raw) | Set-Content $php_dir\php.ini 28 | Add-Log $tick "ioncube" $status 29 | Add-LicenseLog 30 | } catch { 31 | Add-Log $cross "ioncube" "Could not install ioncube on PHP $($installed.FullVersion)" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/blackfire-player.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Blackfire Player 2 | name: Play a Blackfire Scenario 3 | on: [push, pull_request] 4 | jobs: 5 | blackfire-player: 6 | name: Blackfire (PHP ${{ matrix.php-versions }}) 7 | # Add your Blackfire credentials securely using GitHub Secrets 8 | env: 9 | BLACKFIRE_SERVER_ID: ${{ secrets.BLACKFIRE_SERVER_ID }} 10 | BLACKFIRE_SERVER_TOKEN: ${{ secrets.BLACKFIRE_SERVER_TOKEN }} 11 | BLACKFIRE_CLIENT_ID: ${{ secrets.BLACKFIRE_CLIENT_ID }} 12 | BLACKFIRE_CLIENT_TOKEN: ${{ secrets.BLACKFIRE_CLIENT_TOKEN }} 13 | runs-on: ${{ matrix.operating-system }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 18 | php-versions: ['7.4', '8.0', '8.1'] 19 | # blackfire-player supports PHP >= 5.5 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | # Docs: https://github.com/shivammathur/setup-php 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-versions }} 29 | extensions: blackfire 30 | # Setup Blackfire CLI and player 31 | tools: blackfire, blackfire-player 32 | coverage: none 33 | 34 | # Refer to https://blackfire.io/docs/player/index#usage 35 | - name: Play the scenario 36 | run: blackfire-player run scenario.bkf 37 | -------------------------------------------------------------------------------- /src/scripts/extensions/blackfire.ps1: -------------------------------------------------------------------------------- 1 | # Function to install blackfire extension. 2 | Function Add-Blackfire() { 3 | Param ( 4 | [Parameter(Position = 0, Mandatory = $true)] 5 | [ValidateNotNull()] 6 | [string] 7 | $extension 8 | ) 9 | try { 10 | $no_dot_version = $version.replace('.', '') 11 | $extension_version = $extension.split('-')[1] 12 | if ($extension_version -notmatch "\S") { 13 | if($version -lt '7.0') { 14 | $extension_version = '1.50.0' 15 | } else { 16 | $extension_version = (Invoke-RestMethod https://blackfire.io/api/v1/releases).probe.php 17 | } 18 | } 19 | if (Test-Path $ext_dir\blackfire.dll) { 20 | Enable-PhpExtension -Extension blackfire -Path $php_dir 21 | $status="Enabled" 22 | } else { 23 | $nts = if (!$installed.ThreadSafe) { "_nts" } else { "" } 24 | Get-File -Url "https://packages.blackfire.io/binaries/blackfire-php/${extension_version}/blackfire-php-windows_${arch}-php-${no_dot_version}${nts}.dll" -OutFile $ext_dir\blackfire.dll > $null 2>&1 25 | Disable-Extension xdebug > $null 2>&1 26 | Disable-Extension pcov > $null 2>&1 27 | Enable-PhpExtension -Extension blackfire -Path $php_dir 28 | $status="Installed and enabled" 29 | } 30 | Add-Log $tick $extension $status 31 | } catch { 32 | Add-Log $cross $extension "Could not install $extension on PHP $($installed.FullVersion)" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/extensions/blackfire.sh: -------------------------------------------------------------------------------- 1 | # Function to install blackfire extension. 2 | add_blackfire() { 3 | local extension=$1 4 | version=${version:?} 5 | no_dot_version=${version/./} 6 | platform=$(uname -s | tr '[:upper:]' '[:lower:]') 7 | extension_version=$(echo "$extension" | cut -d '-' -f 2) 8 | status='Enabled' 9 | if ! shared_extension blackfire; then 10 | status='Installed and enabled' 11 | arch="$(uname -m)" 12 | arch_name="amd64" 13 | [[ "$arch" = "aarch64" || "$arch" = "arm64" ]] && arch_name="arm64" 14 | [ "${ts:?}" = 'zts' ] && no_dot_version="${no_dot_version}-zts" 15 | if [ "$extension_version" = "blackfire" ]; then 16 | if [[ ${version:?} =~ 5.[3-6] ]]; then 17 | extension_version='1.50.0' 18 | else 19 | extension_version=$(get -s -n "" https://blackfire.io/api/v1/releases | grep -Eo 'php":"([0-9]+.[0-9]+.[0-9]+)' | cut -d '"' -f 3) 20 | fi 21 | fi 22 | get -q -n "${ext_dir:?}/blackfire.so" https://packages.blackfire.io/binaries/blackfire-php/"$extension_version"/blackfire-php-"$platform"_"$arch_name"-php-"$no_dot_version".so >/dev/null 2>&1 23 | fi 24 | if [ -e "${ext_dir:?}/blackfire.so" ]; then 25 | disable_extension xdebug >/dev/null 2>&1 26 | disable_extension pcov >/dev/null 2>&1 27 | enable_extension blackfire extension 28 | add_extension_log blackfire "$status" 29 | else 30 | add_extension_log blackfire "Could not install blackfire on PHP ${semver:?}" 31 | fi 32 | } 33 | -------------------------------------------------------------------------------- /examples/blackfire.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Blackfire 2 | name: Profiling with blackfire 3 | on: [push, pull_request] 4 | jobs: 5 | blackfire: 6 | name: Blackfire (PHP ${{ matrix.php-versions }}) 7 | # Add your Blackfire credentials securely using GitHub Secrets 8 | env: 9 | BLACKFIRE_SERVER_ID: ${{ secrets.BLACKFIRE_SERVER_ID }} 10 | BLACKFIRE_SERVER_TOKEN: ${{ secrets.BLACKFIRE_SERVER_TOKEN }} 11 | BLACKFIRE_CLIENT_ID: ${{ secrets.BLACKFIRE_CLIENT_ID }} 12 | BLACKFIRE_CLIENT_TOKEN: ${{ secrets.BLACKFIRE_CLIENT_TOKEN }} 13 | runs-on: ${{ matrix.operating-system }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 18 | php-versions: ['7.4', '8.0', '8.1'] 19 | # Blackfire supports PHP >= 5.3 on Ubuntu and macOS, and PHP >= 5.4 on Windows 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | # Docs: https://github.com/shivammathur/setup-php 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-versions }} 29 | # Setup Blackfire extension and CLI 30 | extensions: blackfire 31 | tools: blackfire 32 | # Disable Xdebug and PCOV coverage drivers 33 | coverage: none 34 | 35 | # Refer to https://blackfire.io/docs/cookbooks/profiling-cli 36 | - name: Profile 37 | run: blackfire run php my-script.php 38 | -------------------------------------------------------------------------------- /examples/laminas-mvc.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Laminas framework MVC projects 2 | name: Testing Zend Framework 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 9 | php-versions: ['7.4', '8.0', '8.1'] 10 | runs-on: ${{ matrix.operating-system }} 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | # Docs: https://github.com/shivammathur/setup-php 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: ${{ matrix.php-versions }} 20 | coverage: xdebug 21 | 22 | - name: Get composer cache directory 23 | id: composer-cache 24 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 25 | 26 | - name: Cache composer dependencies 27 | uses: actions/cache@v3 28 | with: 29 | path: ${{ steps.composer-cache.outputs.dir }} 30 | # Use composer.json for key, if composer.lock is not committed. 31 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 32 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 33 | restore-keys: ${{ runner.os }}-composer- 34 | 35 | - name: Install dependencies 36 | run: composer install --no-progress --prefer-dist --optimize-autoloader 37 | 38 | - name: Test with phpunit 39 | run: vendor/bin/phpunit --coverage-text 40 | -------------------------------------------------------------------------------- /src/scripts/tools/symfony.sh: -------------------------------------------------------------------------------- 1 | add_symfony_with_brew() { 2 | add_brew_tap symfony-cli/homebrew-tap 3 | brew install symfony-cli/tap/symfony-cli 4 | } 5 | 6 | get_symfony_artifact_url() { 7 | arch=$(dpkg --print-architecture) 8 | url=$(get -s -n "" https://raw.githubusercontent.com/symfony-cli/homebrew-tap/main/Formula/symfony-cli.rb 2<&1 | grep -m 1 "url.*linux.*${arch}" | cut -d\" -f 2) 9 | if [ -z "$url" ]; then 10 | url=$(get -s -n "" https://api.github.com/repos/symfony-cli/symfony-cli/releases 2<&1 | grep -m 1 "url.*linux.*${arch}.*gz\"" | cut -d\" -f 4) 11 | fi 12 | echo "$url" 13 | } 14 | 15 | add_symfony_helper() { 16 | if [ "$(uname -s)" = "Linux" ]; then 17 | url="$(get_symfony_artifact_url)" 18 | if [ -z "$url" ]; then 19 | . "${0%/*}"/tools/brew.sh 20 | configure_brew 21 | add_symfony_with_brew 22 | else 23 | get -s -n "" "$url" | sudo tar -xz -C "${tool_path_dir:?}" 2>/dev/null 24 | sudo chmod a+x /usr/local/bin/symfony 25 | fi 26 | elif [ "$(uname -s)" = "Darwin" ]; then 27 | add_symfony_with_brew 28 | fi 29 | } 30 | 31 | add_symfony() { 32 | add_symfony_helper >/dev/null 2>&1 33 | symfony_path="$(command -v symfony)" 34 | if [[ -n "$symfony_path" ]]; then 35 | sudo ln -s "$symfony_path" "${tool_path_dir:?}"/symfony-cli 36 | tool_version=$(get_tool_version "symfony" "-V") 37 | add_log "${tick:?}" "symfony-cli" "Added symfony-cli $tool_version" 38 | else 39 | add_log "${cross:?}" "symfony-cli" "Could not setup symfony-cli" 40 | fi 41 | } 42 | -------------------------------------------------------------------------------- /examples/codeigniter.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for CodeIgniter 2 | name: Testing CodeIgniter 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 9 | php-versions: ['7.4', '8.0', '8.1'] 10 | runs-on: ${{ matrix.operating-system }} 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | # Docs: https://github.com/shivammathur/setup-php 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: ${{ matrix.php-versions }} 20 | extensions: mbstring, intl, curl, dom 21 | coverage: xdebug 22 | 23 | - name: Get composer cache directory 24 | id: composer-cache 25 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 26 | 27 | - name: Cache composer dependencies 28 | uses: actions/cache@v3 29 | with: 30 | path: ${{ steps.composer-cache.outputs.dir }} 31 | # Use composer.json for key, if composer.lock is not committed. 32 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 33 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 34 | restore-keys: ${{ runner.os }}-composer- 35 | 36 | - name: Install dependencies 37 | run: composer install --no-progress --prefer-dist --optimize-autoloader 38 | 39 | - name: Test with phpunit 40 | run: vendor/bin/phpunit --coverage-text 41 | -------------------------------------------------------------------------------- /examples/slim-framework.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Slim Framework 2 | name: Testing Slim Framework 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 9 | php-versions: ['7.4', '8.0', '8.1'] 10 | runs-on: ${{ matrix.operating-system }} 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | # Docs: https://github.com/shivammathur/setup-php 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: ${{ matrix.php-versions }} 20 | extensions: mbstring, simplexml, dom 21 | coverage: xdebug 22 | 23 | - name: Get composer cache directory 24 | id: composer-cache 25 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 26 | 27 | - name: Cache composer dependencies 28 | uses: actions/cache@v3 29 | with: 30 | path: ${{ steps.composer-cache.outputs.dir }} 31 | # Use composer.json for key, if composer.lock is not committed. 32 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 33 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 34 | restore-keys: ${{ runner.os }}-composer- 35 | 36 | - name: Install dependencies 37 | run: composer install --no-progress --prefer-dist --optimize-autoloader 38 | 39 | - name: Test with phpunit 40 | run: vendor/bin/phpunit --coverage-text 41 | -------------------------------------------------------------------------------- /src/scripts/tools/blackfire.sh: -------------------------------------------------------------------------------- 1 | add_blackfire_linux() { 2 | sudo mkdir -p /var/run/blackfire /etc/blackfire 3 | add_list debian/blackfire http://packages.blackfire.io/debian https://packages.blackfire.io/gpg.key any main 4 | install_packages blackfire 5 | sudo chmod 777 /etc/blackfire/agent 6 | } 7 | 8 | add_blackfire_darwin() { 9 | sudo mkdir -p /usr/local/var/run 10 | add_brew_tap blackfireio/homebrew-blackfire 11 | brew install blackfire 12 | } 13 | 14 | blackfire_config() { 15 | if [[ -n $BLACKFIRE_SERVER_ID ]] && [[ -n $BLACKFIRE_SERVER_TOKEN ]]; then 16 | blackfire agent:config --server-id="$BLACKFIRE_SERVER_ID" --server-token="$BLACKFIRE_SERVER_TOKEN" 17 | if [ "$os" = "Linux" ]; then 18 | if [ -d /run/systemd/system ]; then 19 | sudo systemctl start blackfire-agent 20 | else 21 | sudo service blackfire-agent start 22 | fi 23 | elif [ "$os" = "Darwin" ]; then 24 | brew services start blackfire 25 | fi 26 | fi 27 | if [[ -n $BLACKFIRE_CLIENT_ID ]] && [[ -n $BLACKFIRE_CLIENT_TOKEN ]]; then 28 | blackfire client:config --client-id="$BLACKFIRE_CLIENT_ID" --client-token="$BLACKFIRE_CLIENT_TOKEN" 29 | fi 30 | } 31 | 32 | # Function to add blackfire cli. 33 | add_blackfire() { 34 | os="$(uname -s)" 35 | [ "$os" = "Linux" ] && add_blackfire_linux >/dev/null 2>&1 36 | [ "$os" = "Darwin" ] && add_blackfire_darwin >/dev/null 2>&1 37 | blackfire_config >/dev/null 2>&1 38 | tool_version=$(get_tool_version "blackfire" "version") 39 | add_log "${tick:?}" "blackfire" "Added blackfire $tool_version" 40 | } 41 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import {fixupConfigRules, fixupPluginRules} from '@eslint/compat'; 2 | // eslint-disable-next-line import/no-unresolved 3 | import typescriptEslint from '@typescript-eslint/eslint-plugin'; 4 | import jest from 'eslint-plugin-jest'; 5 | import globals from 'globals'; 6 | // eslint-disable-next-line import/no-unresolved 7 | import tsParser from '@typescript-eslint/parser'; 8 | import path from 'node:path'; 9 | import {fileURLToPath} from 'node:url'; 10 | import js from '@eslint/js'; 11 | import {FlatCompat} from '@eslint/eslintrc'; 12 | 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = path.dirname(__filename); 15 | const compat = new FlatCompat({ 16 | baseDirectory: __dirname, 17 | recommendedConfig: js.configs.recommended, 18 | allConfig: js.configs.all 19 | }); 20 | 21 | export default [ 22 | ...fixupConfigRules( 23 | compat.extends( 24 | 'eslint:recommended', 25 | 'plugin:@typescript-eslint/eslint-recommended', 26 | 'plugin:@typescript-eslint/recommended', 27 | 'plugin:import/errors', 28 | 'plugin:import/warnings', 29 | 'plugin:import/typescript', 30 | 'plugin:prettier/recommended', 31 | 'prettier' 32 | ) 33 | ), 34 | { 35 | plugins: { 36 | '@typescript-eslint': fixupPluginRules(typescriptEslint), 37 | jest 38 | }, 39 | 40 | languageOptions: { 41 | globals: { 42 | ...globals.node, 43 | ...globals.jest 44 | }, 45 | 46 | parser: tsParser, 47 | ecmaVersion: 2021, 48 | sourceType: 'module' 49 | } 50 | } 51 | ]; 52 | -------------------------------------------------------------------------------- /src/scripts/tools/protoc.sh: -------------------------------------------------------------------------------- 1 | get_protobuf_tag() { 2 | if [ "$protobuf_tag" = "latest" ]; then 3 | protobuf_tag=$(get -s -n "" https://github.com/protocolbuffers/protobuf/releases/latest 2<&1 | grep -m 1 -Eo "tag/(v[0-9]+(\.[0-9]+)?(\.[0-9]+)?)" | head -n 1 | cut -d '/' -f 2) 4 | else 5 | status_code=$(get -v -n /tmp/protobuf.tmp "https://github.com/protocolbuffers/protobuf/releases/tag/v$protobuf_tag") 6 | if [ "$status_code" = "200" ]; then 7 | protobuf_tag="v$protobuf_tag" 8 | else 9 | protobuf_tag=$(get -s -n "" https://github.com/protocolbuffers/protobuf/releases/latest 2<&1 | grep -m 1 -Eo "tag/(v[0-9]+(\.[0-9]+)?(\.[0-9]+)?)" | head -n 1 | cut -d '/' -f 2) 10 | fi 11 | fi 12 | } 13 | 14 | add_protoc() { 15 | protobuf_tag=$1 16 | get_protobuf_tag 17 | ( 18 | platform='linux' 19 | [ "$(uname -s)" = "Darwin" ] && platform='osx' 20 | arch="$(uname -m)" 21 | [[ "$arch" = 'arm64' || "$arch" = 'aarch64' ]] && arch='aarch_64' 22 | get -q -n /tmp/protobuf.zip "https://github.com/protocolbuffers/protobuf/releases/download/$protobuf_tag/protoc-${protobuf_tag:1}-$platform-$arch.zip" 23 | sudo unzip /tmp/protobuf.zip -d /usr/local/ 24 | sudo chmod -R 777 /usr/local/bin/protoc /usr/local/include/google 25 | ) >/dev/null 2>&1 26 | add_log "${tick:?}" "protoc" "Added protoc ${protobuf_tag:1}" 27 | printf "$GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "protoc" "Click to read the protoc related license information" 28 | curl "${curl_opts[@]:?}" https://raw.githubusercontent.com/protocolbuffers/protobuf/master/LICENSE 29 | echo "$END_GROUP" 30 | } 31 | -------------------------------------------------------------------------------- /src/scripts/extensions/ioncube.sh: -------------------------------------------------------------------------------- 1 | # Function to log result of a operation. 2 | add_license_log() { 3 | printf "$GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "ioncube" "Click to read the ioncube loader license information" 4 | cat "${ext_dir:?}"/IONCUBE_LICENSE.txt 5 | echo "$END_GROUP" 6 | } 7 | 8 | # Function to install ioncube. 9 | add_ioncube() { 10 | status='Enabled' 11 | if ! shared_extension ioncube; then 12 | status='Installed and enabled' 13 | arch="$(uname -m)" 14 | if [ "$(uname -s)" = "Darwin" ]; then 15 | [ "$arch" = "arm64" ] && os_suffix="dar_arm64" || os_suffix="mac_x86-64" 16 | else 17 | [[ "$arch" = "i386" || "$arch" = "i686" ]] && arch=x86 18 | [[ "$arch" = "x86_64" ]] && arch=x86-64 19 | os_suffix="lin_$arch" 20 | fi 21 | ts_part="" && [ "${ts:?}" = "zts" ] && ts_part="_ts" 22 | get -s -n "" https://downloads.ioncube.com/loader_downloads/ioncube_loaders_"$os_suffix".tar.gz | tar -xzf - -C /tmp 23 | loader_file=/tmp/ioncube/ioncube_loader_"${os_suffix%%_*}_${version:?}$ts_part".so 24 | if [ -e "$loader_file" ]; then 25 | sudo mv /tmp/ioncube/ioncube_loader_"${os_suffix%%_*}_${version:?}$ts_part".so "${ext_dir:?}/ioncube.so" 26 | sudo cp /tmp/ioncube/LICENSE.txt "$ext_dir"/IONCUBE_LICENSE.txt 27 | echo "zend_extension=$ext_dir/ioncube.so" | sudo tee "${scan_dir:?}/00-ioncube.ini" >/dev/null 2>&1 28 | fi 29 | else 30 | echo "zend_extension=$ext_dir/ioncube.so" | sudo tee "${scan_dir:?}/00-ioncube.ini" >/dev/null 2>&1 31 | fi 32 | add_extension_log "ioncube" "$status" 33 | check_extension "ioncube" && add_license_log 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/extensions/event.sh: -------------------------------------------------------------------------------- 1 | # Function to get event configure options 2 | get_event_configure_opts() { 3 | event_opts=( 4 | --with-event-core 5 | --with-event-extra 6 | --with-event-openssl 7 | --enable-event-sockets 8 | ) 9 | if [ "$os" = 'Linux' ]; then 10 | event_opts+=( 11 | --with-openssl-dir=yes 12 | --with-event-libevent-dir=/usr 13 | ) 14 | else 15 | event_opts+=( 16 | --with-openssl-dir="$(brew --prefix openssl@3)" 17 | --with-event-libevent-dir="$(brew --prefix libevent)" 18 | ) 19 | fi 20 | } 21 | 22 | # Helper function to compile and install event 23 | add_event_helper() { 24 | local ext=$1 25 | [[ "$ext" =~ ^event$ ]] && ext="event-$(get_pecl_version "event" "stable")" 26 | event_opts=() && get_event_configure_opts 27 | export EVENT_LINUX_LIBS='libevent-dev' 28 | export EVENT_DARWIN_LIBS='libevent' 29 | event_configure_opts="--with-php-config=$(command -v php-config) ${event_opts[*]}" 30 | export EVENT_CONFIGURE_OPTS="$event_configure_opts" 31 | add_extension_from_source event https://pecl.php.net event event "${ext##*-}" extension pecl 32 | } 33 | 34 | # Function to add event 35 | add_event() { 36 | local ext=$1 37 | enable_extension "event" "extension" 38 | if check_extension "event"; then 39 | add_log "${tick:?}" "event" "Enabled" 40 | else 41 | if ! [[ "${version:?}" =~ ${old_versions:?} ]] && [ "$os" = "Darwin" ]; then 42 | add_brew_extension event extension >/dev/null 2>&1 43 | else 44 | add_event_helper "$ext" >/dev/null 2>&1 45 | fi 46 | add_extension_log "event" "Installed and enabled" 47 | fi 48 | } 49 | 50 | os="$(uname -s)" 51 | -------------------------------------------------------------------------------- /__tests__/packagist.test.ts: -------------------------------------------------------------------------------- 1 | import * as packagist from '../src/packagist'; 2 | import nock from 'nock'; 3 | 4 | describe('search function', () => { 5 | const mockResponse = { 6 | packages: { 7 | 'test-package': [ 8 | { 9 | require: { 10 | php: '8.0.0' 11 | }, 12 | version: '1.0.0' 13 | }, 14 | { 15 | version: '2.0.0' 16 | } 17 | ] 18 | } 19 | }; 20 | 21 | test('should return the version if matching php version is found', async () => { 22 | nock('https://repo.packagist.org') 23 | .get('/p2/test-package.json') 24 | .reply(200, mockResponse); 25 | const result = await packagist.search('test-package', '8.0'); 26 | expect(result).toBe('1.0.0'); 27 | }); 28 | 29 | test('should return null if no matching php version is found', async () => { 30 | nock('https://repo.packagist.org') 31 | .get('/p2/test-package.json') 32 | .reply(200, mockResponse); 33 | const result = await packagist.search('test-package', '5.6'); 34 | expect(result).toBeNull(); 35 | }); 36 | 37 | test('should return null if fetch fails', async () => { 38 | nock('https://repo.packagist.org').get('/p2/test-package.json').reply(404); 39 | const result = await packagist.search('test-package', '8.0'); 40 | expect(result).toBeNull(); 41 | }); 42 | 43 | test('should return null if the response is empty', async () => { 44 | nock('https://repo.packagist.org') 45 | .get('/p2/test-package.json') 46 | .reply(200, {error: true, data: '[]'}); 47 | const result = await packagist.search('test-package', '8.0'); 48 | expect(result).toBeNull(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node workflow 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - develop 7 | - verbose 8 | paths-ignore: 9 | - '**.md' 10 | - 'examples/**' 11 | push: 12 | branches: 13 | - master 14 | - develop 15 | - verbose 16 | paths-ignore: 17 | - '**.md' 18 | - 'examples/**' 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | run: 24 | name: Run 25 | runs-on: ${{ matrix.operating-system }} 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | with: 34 | fetch-depth: 2 35 | 36 | - name: Setup Node.js 20.x 37 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 38 | with: 39 | node-version: 20.x 40 | 41 | - name: Install dependencies 42 | run: npm install 43 | 44 | - name: Prettier Format Check 45 | run: npm run format-check 46 | 47 | - name: ESLint Check 48 | run: npm run lint 49 | 50 | - name: Run tests 51 | run: npm test 52 | 53 | - name: Run npm audit 54 | run: npm audit 55 | 56 | - name: Send Coverage 57 | uses: codecov/codecov-action@e96185f4044c2f0cedf0f022454acf9811cf8057 # v5.4.0 58 | with: 59 | token: ${{ secrets.CODECOV_TOKEN }} 60 | files: coverage/lcov.info 61 | name: github-actions-codecov-${{ matrix.operating-system }} 62 | fail_ci_if_error: false 63 | verbose: true 64 | -------------------------------------------------------------------------------- /src/scripts/extensions/zephir_parser.sh: -------------------------------------------------------------------------------- 1 | # Get zephir_parser version 2 | get_zephir_parser_version() { 3 | local ext=$1 4 | if [[ "$ext" =~ ^zephir_parser$ ]]; then 5 | get -s -n "" "${zp_releases:?}"/latest 2<&1 | grep -m 1 -Eo "tag/(v?[0-9]+(\.[0-9]+)?(\.[0-9]+)?)" | head -n 1 | cut -d '/' -f 2 6 | else 7 | zp_version="${ext##*-}" 8 | echo "v${zp_version/v//}" 9 | fi 10 | } 11 | 12 | # Add zephir_parser helper 13 | add_zephir_parser_helper() { 14 | local ext=$1 15 | nts="${ts:?}" && nts="${nts/z/}" 16 | ext_version=$(get_zephir_parser_version "$ext") 17 | [ "$(uname -s)" = "Linux" ] && os_suffix=ubuntu || os_suffix=macos 18 | build_name=$(get -s -n "" https://api.github.com/repos/"$repo"/releases/tags/"$ext_version" | grep -Eo "zephir_parser-php-${version:?}-$nts-$os_suffix-.*.zip" | head -n 1) 19 | [ -z "$build_name" ] && build_name=$(get -s -n "" "$zp_releases"/expanded_assets/"$ext_version" | grep -Eo "zephir_parser-php-${version:?}-$nts-$os_suffix-.*.zip" | head -n 1) 20 | if [ -n "$build_name" ]; then 21 | get -q -e "/tmp/zp.zip" "$zp_releases"/download/"$ext_version"/"$build_name" 22 | sudo unzip -o "/tmp/zp.zip" -d "${ext_dir:?}" 23 | enable_extension zephir_parser extension 24 | else 25 | pecl_install zephir_parser 26 | fi 27 | } 28 | 29 | # Add zephir_parser 30 | add_zephir_parser() { 31 | ext=$1 32 | repo=zephir-lang/php-zephir-parser 33 | zp_releases=https://github.com/"$repo"/releases 34 | if ! shared_extension zephir_parser; then 35 | message='Installed and enabled' 36 | add_zephir_parser_helper "$ext" >/dev/null 2>&1 37 | else 38 | message='Enabled' 39 | enable_extension zephir_parser extension 40 | fi 41 | add_extension_log zephir_parser "$message" 42 | } -------------------------------------------------------------------------------- /examples/lumen.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Lumen 2 | name: Unit Testing Lumen 3 | on: [push, pull_request] 4 | jobs: 5 | lumen: 6 | name: Lumen (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}) 7 | runs-on: ${{ matrix.operating-system }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 12 | php-versions: ['7.4', '8.0', '8.1'] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | # Docs: https://github.com/shivammathur/setup-php 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | extensions: mbstring, dom, fileinfo, mysql 23 | coverage: xdebug 24 | 25 | - name: Get composer cache directory 26 | id: composer-cache 27 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 28 | 29 | - name: Cache composer dependencies 30 | uses: actions/cache@v3 31 | with: 32 | path: ${{ steps.composer-cache.outputs.dir }} 33 | # Use composer.json for key, if composer.lock is not committed. 34 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 35 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: ${{ runner.os }}-composer- 37 | 38 | - name: Install Composer dependencies 39 | run: composer install --no-progress --prefer-dist --optimize-autoloader 40 | 41 | - name: Prepare the application 42 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 43 | 44 | - name: Test with phpunit 45 | run: vendor/bin/phpunit --coverage-text 46 | -------------------------------------------------------------------------------- /examples/symfony.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Symfony 2 | name: Testing Symfony 3 | on: [push, pull_request] 4 | jobs: 5 | symfony: 6 | name: Symfony (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}) 7 | runs-on: ${{ matrix.operating-system }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 12 | php-versions: ['7.4', '8.0', '8.1'] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | # Docs: https://github.com/shivammathur/setup-php 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | tools: phpunit-bridge 23 | extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite 24 | coverage: xdebug 25 | 26 | - name: Get composer cache directory 27 | id: composer-cache 28 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 29 | 30 | - name: Cache composer dependencies 31 | uses: actions/cache@v3 32 | with: 33 | path: ${{ steps.composer-cache.outputs.dir }} 34 | # Use composer.json for key, if composer.lock is not committed. 35 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 36 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 37 | restore-keys: ${{ runner.os }}-composer- 38 | 39 | - name: Install Composer dependencies 40 | run: composer install --no-progress --prefer-dist --optimize-autoloader 41 | 42 | - name: Install PHPUnit 43 | run: simple-phpunit install 44 | 45 | - name: Run tests 46 | run: simple-phpunit --coverage-text 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature 3 | title: "Feature: " 4 | labels: ["enhancement"] 5 | assignees: ["shivammathur"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: "Please fill out the sections below to help us understand your new feature proposal." 10 | 11 | - type: textarea 12 | id: feature-description 13 | attributes: 14 | label: "Describe the feature" 15 | placeholder: "A clear and concise description of what you want and why." 16 | validations: 17 | required: true 18 | 19 | - type: checkboxes 20 | id: version-check 21 | attributes: 22 | label: "Please check the latest release" 23 | options: 24 | - label: "I have checked releases, and the feature is missing in the latest patch version of `v2`." 25 | required: true 26 | 27 | - type: textarea 28 | id: underlying-issue 29 | attributes: 30 | label: "Underlying issue" 31 | placeholder: "Please describe the issue this would solve." 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: alternatives 37 | attributes: 38 | label: "Describe alternatives" 39 | placeholder: "Please mention any alternative solutions you've considered." 40 | validations: 41 | required: false 42 | 43 | - type: textarea 44 | id: additional-context 45 | attributes: 46 | label: "Additional context" 47 | placeholder: "Drag and drop images or paste any additional information here..." 48 | 49 | - type: dropdown 50 | id: willing-to-submit-pr 51 | attributes: 52 | label: "Are you willing to submit a PR?" 53 | description: "We accept pull requests targeting the develop branch." 54 | options: 55 | - "Yes" 56 | - "No" 57 | validations: 58 | required: true 59 | -------------------------------------------------------------------------------- /src/scripts/extensions/http.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ICUUrl() { 2 | Param ( 3 | [Parameter(Position = 0, Mandatory = $true)] 4 | [ValidateNotNull()] 5 | $icu_version, 6 | [Parameter(Position = 1, Mandatory = $true)] 7 | [ValidateNotNull()] 8 | $arch, 9 | [Parameter(Position = 2, Mandatory = $true)] 10 | [ValidateNotNull()] 11 | $vs_version 12 | ) 13 | $trunk = "https://windows.php.net" 14 | $urls=@("${trunk}/downloads/php-sdk/deps/${vs_version}/${arch}", "${trunk}/downloads/php-sdk/deps/archives/${vs_version}/${arch}") 15 | foreach ($url in $urls) { 16 | $web_content = Get-File -Url $url 17 | foreach ($link in $web_content.Links) { 18 | if ($link -match "/.*ICU-${icu_version}.*/") { 19 | return $trunk + $link.HREF 20 | } 21 | } 22 | } 23 | } 24 | 25 | Function Repair-ICU() { 26 | $icu = deplister $ext_dir\php_http.dll | Select-String "icu[a-z]+(\d+).dll,([A-Z]+)" | Foreach-Object { $_.Matches } 27 | if($icu -and $icu.Groups[2].Value -ne 'OK') { 28 | $vs = "vs" + $installed.VCVersion 29 | if ($installed.VCVersion -lt 16) { 30 | $vs = "vc" + $installed.VCVersion 31 | } 32 | $zip_url = Get-ICUUrl $icu.Groups[1].Value $installed.Architecture $vs 33 | if ($zip_url -ne '') { 34 | New-Item -Path "$php_dir" -Name "icu" -ItemType "directory" -Force > $null 2>&1 35 | Get-File -Url $zip_url -OutFile "$php_dir\icu\icu.zip" 36 | Expand-Archive -Path $php_dir\icu\icu.zip -DestinationPath $php_dir\icu -Force 37 | Get-ChildItem $php_dir\icu\bin -Filter *.dll | Copy-Item -Destination $php_dir -Force 38 | } 39 | } 40 | } 41 | 42 | Function Add-Http() { 43 | Add-Extension iconv >$null 2>&1 44 | Add-Extension raphf >$null 2>&1 45 | if($version -lt '8.0') { 46 | Add-Extension propro >$null 2>&1 47 | } 48 | Add-Extension pecl_http >$null 2>&1 49 | Repair-ICU 50 | Add-ExtensionLog http "Installed and enabled" 51 | } -------------------------------------------------------------------------------- /examples/laravel.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Laravel 2 | name: Testing Laravel 3 | on: [push, pull_request] 4 | jobs: 5 | laravel: 6 | name: Laravel (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}) 7 | runs-on: ${{ matrix.operating-system }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 12 | php-versions: ['7.4', '8.0', '8.1'] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | # Docs: https://github.com/shivammathur/setup-php 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | extensions: mbstring, dom, fileinfo 23 | coverage: xdebug 24 | 25 | - name: Get composer cache directory 26 | id: composer-cache 27 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 28 | 29 | - name: Cache composer dependencies 30 | uses: actions/cache@v3 31 | with: 32 | path: ${{ steps.composer-cache.outputs.dir }} 33 | # Use composer.json for key, if composer.lock is not committed. 34 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 35 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: ${{ runner.os }}-composer- 37 | 38 | - name: Install Composer dependencies 39 | run: composer install --no-progress --prefer-dist --optimize-autoloader 40 | 41 | - name: Prepare the application 42 | run: | 43 | php -r "file_exists('.env') || copy('.env.example', '.env');" 44 | php artisan key:generate 45 | 46 | - name: Clear Config 47 | run: php artisan config:clear 48 | 49 | - name: Test with phpunit 50 | run: vendor/bin/phpunit --coverage-text 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Explicitly not ignoring node_modules so that they are included in package downloaded by runner 2 | node_modules/ 3 | __tests__/runner/* 4 | lib/ 5 | 6 | # Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # vuepress build output 84 | .vuepress/dist 85 | 86 | # Serverless directories 87 | .serverless/ 88 | 89 | # FuseBox cache 90 | .fusebox/ 91 | 92 | # DynamoDB Local files 93 | .dynamodb/ -------------------------------------------------------------------------------- /src/fetch.ts: -------------------------------------------------------------------------------- 1 | import {IncomingMessage, OutgoingHttpHeaders} from 'http'; 2 | import * as https from 'https'; 3 | import * as url from 'url'; 4 | 5 | /** 6 | * Function to fetch a URL 7 | * 8 | * @param input_url 9 | * @param auth_token 10 | * @param redirect_count 11 | */ 12 | export async function fetch( 13 | input_url: string, 14 | auth_token?: string, 15 | redirect_count = 5 16 | ): Promise> { 17 | const fetch_promise: Promise> = new Promise( 18 | resolve => { 19 | const url_object: url.UrlObject = new url.URL(input_url); 20 | const headers: OutgoingHttpHeaders = { 21 | 'User-Agent': `Mozilla/5.0 (${process.platform} ${process.arch}) setup-php` 22 | }; 23 | if (auth_token) { 24 | headers.authorization = 'Bearer ' + auth_token; 25 | } 26 | const options: https.RequestOptions = { 27 | hostname: url_object.hostname, 28 | path: url_object.pathname, 29 | headers: headers, 30 | agent: new https.Agent({keepAlive: false}) 31 | }; 32 | const req = https.get(options, (res: IncomingMessage) => { 33 | if (res.statusCode === 200) { 34 | let body = ''; 35 | res.setEncoding('utf8'); 36 | res.on('data', chunk => (body += chunk)); 37 | res.on('end', () => resolve({data: `${body}`})); 38 | } else if ( 39 | [301, 302, 303, 307, 308].includes(res.statusCode as number) 40 | ) { 41 | if (redirect_count > 0 && res.headers.location) { 42 | fetch(res.headers.location, auth_token, redirect_count--).then( 43 | resolve 44 | ); 45 | } else { 46 | resolve({error: `${res.statusCode}: Redirect error`}); 47 | } 48 | } else { 49 | resolve({error: `${res.statusCode}: ${res.statusMessage}`}); 50 | } 51 | }); 52 | req.end(); 53 | } 54 | ); 55 | return await fetch_promise; 56 | } 57 | -------------------------------------------------------------------------------- /src/scripts/tools/protoc.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ProtobufTag() { 2 | $releases = 'https://github.com/protocolbuffers/protobuf/releases' 3 | if("$protobuf_tag" -eq "latest") { 4 | $protobuf_tag = (Get-File -Url $releases/latest).BaseResponse.RequestMessage.RequestUri.Segments[-1] 5 | } else { 6 | try { 7 | $protobuf_tag = $protobuf_tag -replace '^v', '' 8 | [net.httpWebRequest] $request = [net.webRequest]::create("$releases/tag/v$protobuf_tag") 9 | $request.Method = "HEAD" 10 | [net.httpWebResponse] $response = $request.getResponse() 11 | $response.Close() 12 | $protobuf_tag = "v$protobuf_tag" 13 | } catch { 14 | $protobuf_tag = (Get-File -Url $releases/latest).BaseResponse.RequestMessage.RequestUri.Segments[-1] 15 | } 16 | } 17 | return $protobuf_tag 18 | } 19 | 20 | Function Add-Protoc() { 21 | param( 22 | [Parameter(Mandatory = $true, Position = 0, HelpMessage = 'The PHP version to be installed')] 23 | [ValidatePattern('^latest$|^(v?)\d+\.\d+(\.\d+)?$')] 24 | [string] $protobuf_tag 25 | ) 26 | $protobuf_tag = Get-ProtobufTag 27 | $arch_num = '64' 28 | if(-not([Environment]::Is64BitOperatingSystem)) { 29 | $arch_num = '32' 30 | } 31 | $url = "https://github.com/protocolbuffers/protobuf/releases/download/$protobuf_tag/protoc-$($protobuf_tag -replace 'v', '')-win$arch_num.zip" 32 | Get-File -Url $url -OutFile $bin_dir\protoc.zip >$null 2>&1 33 | Expand-Archive -Path $bin_dir\protoc.zip -DestinationPath $bin_dir\protoc -Force >$null 2>&1 34 | Move-Item -Path $bin_dir\protoc\bin\protoc.exe -Destination $bin_dir\protoc.exe 35 | Add-ToProfile $current_profile 'protoc' "New-Alias protoc $bin_dir\protoc.exe" 36 | Add-Log $tick "protoc" "Added protoc $($protobuf_tag -replace 'v', '')" 37 | printf "$env:GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "protoc" "Click to read the protoc related license information" 38 | Write-Output (Invoke-WebRequest https://raw.githubusercontent.com/protocolbuffers/protobuf/master/LICENSE).Content 39 | Write-Output "$env:END_GROUP" 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | on: 3 | release: 4 | types: [created] 5 | workflow_dispatch: 6 | inputs: 7 | skip: 8 | description: Skip release to repository 9 | required: false 10 | tag: 11 | description: Tag name 12 | required: true 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | steps: 20 | - name: Checkout release 21 | if: github.event_name != 'workflow_dispatch' 22 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | 24 | - name: Checkout tag 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | if: github.event_name == 'workflow_dispatch' 27 | with: 28 | ref: ${{ github.event.inputs.tag }} 29 | 30 | - name: Setup Node.js 31 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 32 | with: 33 | node-version: '20.x' 34 | registry-url: https://registry.npmjs.org 35 | 36 | - name: Install dependencies and add lib 37 | run: | 38 | npm install 39 | npm run build 40 | sed -i -e '/lib\//d' .gitignore 41 | 42 | - name: Publish to NPM 43 | if: "!contains(github.event.inputs.skip, 'skip-npm')" 44 | run: npm publish --access public 45 | env: 46 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | 48 | - name: Change to GitHub Packages registry 49 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 50 | with: 51 | registry-url: https://npm.pkg.github.com 52 | scope: '@shivammathur' 53 | 54 | - name: Patch package.json 55 | run: | 56 | sed -i 's#"name": "#"name": "@shivammathur/#' package.json 57 | 58 | - name: Publish to GitHub Packages 59 | if: "!contains(github.event.inputs.skip, 'skip-github-packages')" 60 | run: npm publish 61 | env: 62 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /src/scripts/extensions/firebird.sh: -------------------------------------------------------------------------------- 1 | add_firebird_client_darwin() { 2 | firebird_tag='v5.0.0' 3 | arch_name='x64' 4 | arch="$(uname -m)" 5 | [[ "$arch" = "arm64" || "$arch" = "aarch64" ]] && arch_name='arm64' 6 | pkg_name=$(get -s -n "" https://api.github.com/repos/FirebirdSQL/firebird/releases/tags/"$firebird_tag" | grep -Eo "Firebird-.*.-$arch_name.pkg" | head -n 1) 7 | [ -z "$pkg_name" ] && pkg_name=$(get -s -n "" https://github.com/FirebirdSQL/firebird/releases/expanded_assets/"$firebird_tag" | grep -Eo "Firebird-.*.-$arch_name.pkg" | head -n 1) 8 | get -q -e "/tmp/firebird.pkg" https://github.com/FirebirdSQL/firebird/releases/download/"$firebird_tag"/"$pkg_name" 9 | sudo installer -pkg /tmp/firebird.pkg -target / 10 | sudo mkdir -p /opt/firebird/include /opt/firebird/lib 11 | sudo cp -a /Library/Frameworks/Firebird.framework/Headers/* /opt/firebird/include/ 12 | sudo find /Library/Frameworks/Firebird.framework -name '*.dylib' -exec cp "{}" /opt/firebird/lib \; 13 | } 14 | 15 | add_firebird_helper() { 16 | firebird_dir=$1 17 | tag="$(php_src_tag)" 18 | export PDO_FIREBIRD_CONFIGURE_PREFIX_OPTS="CFLAGS=-Wno-incompatible-function-pointer-types EXTRA_CFLAGS=-Wno-int-conversion" 19 | export PDO_FIREBIRD_CONFIGURE_OPTS="--with-pdo-firebird=$firebird_dir" 20 | export PDO_FIREBIRD_LINUX_LIBS="firebird-dev" 21 | export PDO_FIREBIRD_PATH="ext/pdo_firebird" 22 | add_extension_from_source pdo_firebird https://github.com php php-src "$tag" extension get 23 | } 24 | 25 | add_firebird() { 26 | if [ "$(uname -s )" = "Darwin" ]; then 27 | add_firebird_client_darwin >/dev/null 2>&1 28 | fi 29 | enable_extension pdo_firebird extension 30 | status="Enabled" 31 | if ! check_extension pdo_firebird; then 32 | status="Installed and enabled" 33 | if [ "$(uname -s)" = "Linux" ]; then 34 | if [[ "${version:?}" =~ 5.3|${nightly_versions:?} ]]; then 35 | add_firebird_helper /usr >/dev/null 2>&1 36 | else 37 | add_pdo_extension firebird >/dev/null 2>&1 38 | fi 39 | else 40 | add_firebird_helper /opt/firebird >/dev/null 2>&1 41 | fi 42 | fi 43 | add_extension_log pdo_firebird "$status" 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-php", 3 | "version": "2.32.0", 4 | "private": false, 5 | "description": "Setup PHP for use with GitHub Actions", 6 | "main": "lib/install.js", 7 | "types": "lib/install.d.ts", 8 | "directories": { 9 | "lib": "lib", 10 | "test": "__tests__", 11 | "src": "src" 12 | }, 13 | "files": [ 14 | "lib", 15 | "src" 16 | ], 17 | "scripts": { 18 | "build": "tsc", 19 | "lint": "eslint **/src/*.ts **/__tests__/*.ts --cache --fix", 20 | "format": "prettier --write **/src/*.ts **/__tests__/*.ts && git add -f __tests__/ ", 21 | "format-check": "prettier --check **/src/*.ts **/__tests__/*.ts", 22 | "release": "ncc build -m -o dist && git add -f dist/", 23 | "test": "jest" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/shivammathur/setup-php.git" 28 | }, 29 | "keywords": [ 30 | "actions", 31 | "php", 32 | "setup" 33 | ], 34 | "author": "shivammathur", 35 | "license": "MIT", 36 | "dependencies": { 37 | "@actions/core": "^1.11.1", 38 | "@actions/exec": "^1.1.1", 39 | "@actions/io": "^1.1.3", 40 | "compare-versions": "^6.1.1" 41 | }, 42 | "devDependencies": { 43 | "@eslint/compat": "^1.2.7", 44 | "@eslint/js": "9.22.0", 45 | "@types/jest": "^29.5.14", 46 | "@types/node": "^22.13.10", 47 | "@typescript-eslint/eslint-plugin": "^8.26.1", 48 | "@typescript-eslint/parser": "^8.26.1", 49 | "@vercel/ncc": "^0.38.3", 50 | "eslint": "9.22.0", 51 | "eslint-config-prettier": "^10.1.1", 52 | "eslint-plugin-import": "^2.31.0", 53 | "eslint-plugin-jest": "^28.11.0", 54 | "eslint-plugin-prettier": "^5.2.3", 55 | "globals": "^16.0.0", 56 | "jest": "^29.7.0", 57 | "jest-circus": "^29.7.0", 58 | "nock": "^14.0.1", 59 | "prettier": "^3.5.3", 60 | "simple-git-hooks": "^2.11.1", 61 | "ts-jest": "^29.2.6", 62 | "typescript": "^5.8.2" 63 | }, 64 | "bugs": { 65 | "url": "https://github.com/shivammathur/setup-php/issues" 66 | }, 67 | "simple-git-hooks": { 68 | "pre-commit": "npm run format && npm run lint && npm run test && npm run build && npm run release" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/scripts/extensions/cubrid.sh: -------------------------------------------------------------------------------- 1 | # Function to log license details. 2 | add_license_log() { 3 | printf "$GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "$ext" "Click to read the $ext related license information" 4 | printf "Cubrid CCI package is required for %s extension.\n" "$ext" 5 | printf "The extension %s and Cubrid CCI are provided under the license linked below.\n" "$ext" 6 | printf "Refer to: \033[35;1m%s \033[0m\n" "https://github.com/CUBRID/cubrid-cci/blob/develop/COPYING" 7 | echo "$END_GROUP" 8 | } 9 | 10 | # Function to setup gcc-7 and g++-7 11 | setup_compiler() { 12 | if ! command -v gcc-7 >/dev/null || ! command -v g++-7 >/dev/null; then 13 | add_ppa ubuntu-toolchain-r/test 14 | add_packages gcc-7 g++-7 -y 15 | fi 16 | printf "gcc g++" | xargs -d ' ' -I {} sudo update-alternatives --install /usr/bin/{} {} /usr/bin/{}-7 7 17 | } 18 | 19 | # Function to set cubrid repo for the extension. 20 | set_cubrid_repo() { 21 | case "${ext:?}" in 22 | "cubrid") cubrid_repo="cubrid-php";; 23 | "pdo_cubrid") cubrid_repo="cubrid-pdo";; 24 | esac 25 | } 26 | 27 | # Function to set cubrid branch for a PHP version. 28 | set_cubrid_branch() { 29 | case "${version:?}" in 30 | 5.[3-6]) cubrid_branch="RB-9.3.0";; 31 | *) cubrid_branch="develop";; 32 | esac 33 | } 34 | 35 | add_cubrid_helper() { 36 | ext=$1 37 | enable_extension "$ext" extension 38 | if ! check_extension "$ext"; then 39 | status='Installed and enabled' 40 | set_cubrid_repo 41 | set_cubrid_branch 42 | patch_phpize 43 | read -r "${ext}_PREFIX_CONFIGURE_OPTS" <<< "CFLAGS=-Wno-implicit-function-declaration" 44 | read -r "${ext}_CONFIGURE_OPTS" <<< "--with-php-config=$(command -v php-config)" 45 | add_extension_from_source "$ext" https://github.com CUBRID "$cubrid_repo" "$cubrid_branch" extension 46 | restore_phpize 47 | fi 48 | } 49 | 50 | # Function to add cubrid and pdo_cubrid. 51 | add_cubrid() { 52 | ext=$1 53 | status='Enabled' 54 | add_cubrid_helper "$ext" >/dev/null 2>&1 55 | add_extension_log "$ext" "$status" 56 | check_extension "$ext" && add_license_log 57 | } 58 | 59 | # shellcheck source=. 60 | . "${scripts:?}"/extensions/patches/phpize.sh 61 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './utils'; 2 | 3 | /** 4 | * Add script to set custom ini values for unix 5 | * 6 | * @param ini_values_csv 7 | */ 8 | export async function addINIValuesUnix( 9 | ini_values_csv: string 10 | ): Promise { 11 | const ini_values: Array = await utils.CSVArray(ini_values_csv); 12 | let script = ''; 13 | await utils.asyncForEach(ini_values, async function (line: string) { 14 | script += 15 | '\n' + (await utils.addLog('$tick', line, 'Added to php.ini', 'linux')); 16 | }); 17 | return ( 18 | 'echo "' + 19 | ini_values.join('\n') + 20 | '" | sudo tee -a "${pecl_file:-${ini_file[@]}}" >/dev/null 2>&1' + 21 | script 22 | ); 23 | } 24 | 25 | /** 26 | * Add script to set custom ini values for windows 27 | * 28 | * @param ini_values_csv 29 | */ 30 | export async function addINIValuesWindows( 31 | ini_values_csv: string 32 | ): Promise { 33 | const ini_values: Array = await utils.CSVArray(ini_values_csv); 34 | let script = '\n'; 35 | await utils.asyncForEach(ini_values, async function (line: string) { 36 | script += 37 | (await utils.addLog('$tick', line, 'Added to php.ini', 'win32')) + '\n'; 38 | }); 39 | return ( 40 | 'Add-Content "$php_dir\\php.ini" "' + ini_values.join('\n') + '"' + script 41 | ); 42 | } 43 | 44 | /** 45 | * Function to add custom ini values 46 | * 47 | * @param ini_values_csv 48 | * @param os 49 | * @param no_step 50 | */ 51 | export async function addINIValues( 52 | ini_values_csv: string, 53 | os: string, 54 | no_step = false 55 | ): Promise { 56 | let script = '\n'; 57 | switch (no_step) { 58 | case true: 59 | script += 60 | (await utils.stepLog('Add php.ini values', os)) + 61 | (await utils.suppressOutput(os)) + 62 | '\n'; 63 | break; 64 | case false: 65 | default: 66 | script += (await utils.stepLog('Add php.ini values', os)) + '\n'; 67 | break; 68 | } 69 | switch (os) { 70 | case 'win32': 71 | return script + (await addINIValuesWindows(ini_values_csv)); 72 | case 'darwin': 73 | case 'linux': 74 | return script + (await addINIValuesUnix(ini_values_csv)); 75 | default: 76 | return await utils.log( 77 | 'Platform ' + os + ' is not supported', 78 | os, 79 | 'error' 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/sage.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for roots/sage 2 | name: Testing Sage 3 | on: [push, pull_request] 4 | jobs: 5 | sage: 6 | name: Sage (PHP ${{ matrix.php-versions }} & Node ${{ matrix.node-versions }}) 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | php-versions: ['7.4', '8.0', '8.1'] 12 | node-versions: ['16'] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-versions }} 21 | 22 | # Docs: https://github.com/shivammathur/setup-php 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php-versions }} 27 | extensions: mbstring 28 | 29 | - name: Check node versions 30 | run: node -v 31 | 32 | - name: Get yarn cache 33 | id: yarn-cache 34 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 35 | 36 | - uses: actions/cache@v3 37 | with: 38 | path: ${{ steps.yarn-cache.outputs.dir }} 39 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 40 | restore-keys: ${{ runner.os }}-yarn- 41 | 42 | - name: Get composer cache directory 43 | id: composer-cache 44 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 45 | 46 | - name: Cache composer dependencies 47 | uses: actions/cache@v3 48 | with: 49 | path: ${{ steps.composer-cache.outputs.dir }} 50 | # Use composer.json for key, if composer.lock is not committed. 51 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 52 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 53 | restore-keys: ${{ runner.os }}-composer- 54 | 55 | - name: Install yarn dependencies 56 | run: yarn -V 57 | 58 | - name: Install Composer dependencies 59 | run: composer install --no-progress --prefer-dist --optimize-autoloader 60 | 61 | - name: Yarn test and build 62 | run: | 63 | yarn run test 64 | yarn run build 65 | yarn run rmdist 66 | yarn run "build:production" 67 | 68 | - name: PHP test 69 | run: composer test 70 | -------------------------------------------------------------------------------- /src/install.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import {exec} from '@actions/exec'; 4 | import * as core from '@actions/core'; 5 | import * as config from './config'; 6 | import * as coverage from './coverage'; 7 | import * as extensions from './extensions'; 8 | import * as tools from './tools'; 9 | import * as utils from './utils'; 10 | 11 | /** 12 | * Build the script 13 | * 14 | * @param os 15 | */ 16 | export async function getScript(os: string): Promise { 17 | const url = 'https://setup-php.com/sponsor'; 18 | const filename = os + (await utils.scriptExtension(os)); 19 | const script_path = path.join(__dirname, '../src/scripts', filename); 20 | const run_path = script_path.replace(os, 'run'); 21 | process.env['fail_fast'] = await utils.getInput('fail-fast', false); 22 | const extension_csv: string = await utils.getInput('extensions', false); 23 | const ini_values_csv: string = await utils.getInput('ini-values', false); 24 | const coverage_driver: string = await utils.getInput('coverage', false); 25 | const tools_csv: string = await utils.getInput('tools', false); 26 | const version: string = await utils.parseVersion( 27 | await utils.readPHPVersion() 28 | ); 29 | const ini_file: string = await utils.parseIniFile( 30 | await utils.getInput('ini-file', false) 31 | ); 32 | let script = await utils.joins('.', script_path, version, ini_file); 33 | if (extension_csv) { 34 | script += await extensions.addExtension(extension_csv, version, os); 35 | } 36 | script += await tools.addTools(tools_csv, version, os); 37 | if (coverage_driver) { 38 | script += await coverage.addCoverage(coverage_driver, version, os); 39 | } 40 | if (ini_values_csv) { 41 | script += await config.addINIValues(ini_values_csv, os); 42 | } 43 | script += '\n' + (await utils.stepLog(`Sponsor setup-php`, os)); 44 | script += '\n' + (await utils.addLog('$tick', 'setup-php', url, os)); 45 | 46 | fs.writeFileSync(run_path, script, {mode: 0o755}); 47 | 48 | return run_path; 49 | } 50 | 51 | /** 52 | * Run the script 53 | */ 54 | export async function run(): Promise { 55 | const os: string = process.platform; 56 | const tool = await utils.scriptTool(os); 57 | const run_path = await getScript(os); 58 | await exec(tool + run_path); 59 | } 60 | 61 | // call the run function 62 | (async () => { 63 | await run(); 64 | })().catch(error => { 65 | core.setFailed(error.message); 66 | }); 67 | -------------------------------------------------------------------------------- /src/scripts/tools/grpc_php_plugin.sh: -------------------------------------------------------------------------------- 1 | add_bazel() { 2 | if ! command -v bazel; then 3 | if [ "$(uname -s)" = "Linux" ]; then 4 | add_list bazel/apt https://storage.googleapis.com/bazel-apt https://bazel.build/bazel-release.pub.gpg stable jdk1.8 5 | install_packages bazel 6 | else 7 | brew install bazel 8 | fi 9 | fi 10 | } 11 | 12 | get_grpc_tag() { 13 | if [ "$grpc_tag" = "latest" ]; then 14 | grpc_tag=$(get -s -n "" https://github.com/grpc/grpc/releases/latest | grep -Eo -m 1 "v[0-9]+\.[0-9]+\.[0-9]+" | head -n 1) 15 | else 16 | if [[ ${grpc_tag:0:1} != "v" ]] ; then grpc_tag="v$grpc_tag"; fi 17 | status_code=$(get -v -n /tmp/grpc.tmp "https://github.com/grpc/grpc/releases/tag/$grpc_tag") 18 | if [ "$status_code" != "200" ]; then 19 | grpc_tag=$(get -s -n "" https://github.com/grpc/grpc/releases/latest | grep -Eo -m 1 "v[0-9]+\.[0-9]+\.[0-9]+" | head -n 1) 20 | fi 21 | fi 22 | } 23 | 24 | add_grpc_php_plugin_brew() { 25 | . "${0%/*}"/tools/brew.sh 26 | configure_brew 27 | [ -e /usr/local/bin/protoc ] && sudo mv /usr/local/bin/protoc /tmp/protoc && sudo mv /usr/local/include/google /tmp 28 | brew install grpc 29 | brew link --force --overwrite grpc >/dev/null 2>&1 30 | [ -e /tmp/protoc ] && sudo mv /tmp/protoc /usr/local/bin/protoc && sudo mv /tmp/google /usr/local/include/ 31 | grpc_tag="v$(brew info grpc | grep "grpc:" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" 32 | license_path="$(brew --prefix grpc)/LICENSE" 33 | } 34 | 35 | add_grpc_php_plugin_compile() { 36 | get_grpc_tag 37 | get -s -n "" "https://github.com/grpc/grpc/archive/$grpc_tag.tar.gz" | tar -xz -C /tmp 38 | export DISABLE_BAZEL_WRAPPER=1 39 | ( 40 | cd "/tmp/grpc-${grpc_tag:1}" || exit 41 | add_bazel 42 | ./tools/bazel build src/compiler:grpc_php_plugin 43 | sudo mv ./bazel-bin/src/compiler/grpc_php_plugin /usr/local/bin/grpc_php_plugin 44 | sudo chmod a+x /usr/local/bin/grpc_php_plugin 45 | license_path="/tmp/grpc-${grpc_tag:1}/LICENSE" 46 | ) 47 | } 48 | 49 | add_grpc_php_plugin() { 50 | grpc_tag=$1 51 | license_path="" 52 | if [ "$grpc_tag" = "latest" ]; then 53 | add_grpc_php_plugin_brew >/dev/null 2>&1 54 | else 55 | add_grpc_php_plugin_compile >/dev/null 2>&1 56 | fi 57 | set_output grpc_php_plugin_path "$(command -v grpc_php_plugin)" 58 | add_log "${tick:?}" "grpc_php_plugin" "Added grpc_php_plugin ${grpc_tag:1}" 59 | printf "$GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "grpc_php_plugin" "Click to read the grpc_php_plugin related license information" 60 | cat "$license_path" 61 | echo "$END_GROUP" 62 | } 63 | -------------------------------------------------------------------------------- /src/scripts/extensions/sqlsrv.ps1: -------------------------------------------------------------------------------- 1 | # Function to get sqlsrv extension version. 2 | Function Get-SqlsrvReleaseVersion() { 3 | if ($version -le '7.2') { 4 | # Use the releases from PECL for these versions 5 | return null; 6 | } elseif($version -eq '7.3') { 7 | return '5.9.0' 8 | } elseif ($version -eq '7.4') { 9 | return '5.10.1' 10 | } elseif ($version -eq '8.0') { 11 | return '5.11.1' 12 | } else { 13 | return 'latest' 14 | } 15 | } 16 | 17 | # Function to get sqlsrv extension release URL. 18 | Function Get-SqlsrvReleaseUrl() 19 | { 20 | $extensionVersion = Get-SqlsrvReleaseVersion 21 | if($extensionVersion) { 22 | $repo = "$github/microsoft/msphpsql" 23 | if($extensionVersion -eq 'latest') { 24 | return "$repo/releases/latest/download/Windows-$version.zip" 25 | } else { 26 | return "$repo/releases/download/v$extensionVersion/Windows-$version.zip" 27 | } 28 | } 29 | } 30 | 31 | # Function to add sqlsrv extension from GitHub. 32 | Function Add-SqlsrvFromMSGithub() 33 | { 34 | Param ( 35 | [Parameter(Position = 0, Mandatory = $true)] 36 | [ValidateNotNull()] 37 | [string] 38 | $extension 39 | ) 40 | try { 41 | $zipUrl = SqlsrvReleaseUrl 42 | if($zipUrl) { 43 | $nts = if (!$installed.ThreadSafe) { "nts" } else { "ts" } 44 | $noDotVersion = $version.replace('.', '') 45 | $extensionFilePath = "Windows-$version\$arch\php_${extension}_${noDotVersion}_${nts}.dll" 46 | Get-File -Url $zipUrl -OutFile $ENV:RUNNER_TOOL_CACHE\sqlsrv.zip > $null 2>&1 47 | Expand-Archive -Path $ENV:RUNNER_TOOL_CACHE\sqlsrv.zip -DestinationPath $ENV:RUNNER_TOOL_CACHE\sqlsrv -Force > $null 2>&1 48 | Copy-Item -Path "$ENV:RUNNER_TOOL_CACHE\sqlsrv\$extensionFilePath" -Destination "$ext_dir\php_$extension.dll" 49 | Enable-PhpExtension -Extension $extension -Path $php_dir 50 | } 51 | } catch { } 52 | } 53 | 54 | # Function to add sqlsrv extension. 55 | Function Add-Sqlsrv() { 56 | Param ( 57 | [Parameter(Position = 0, Mandatory = $true)] 58 | [ValidateNotNull()] 59 | [string] 60 | $extension 61 | ) 62 | $status = 'Enabled' 63 | if (Test-Path $ext_dir\php_$extension.dll) { 64 | Enable-PhpExtension -Extension $extension -Path $php_dir 65 | } else { 66 | try { 67 | Add-ExtensionFromGithub $extension > $null 2>&1 68 | } catch {} 69 | if (-not(Test-Extension $extension)) { 70 | Add-SqlsrvFromMSGithub $extension >$null 2>&1 71 | } 72 | if (-not(Test-Extension $extension)) { 73 | Add-Extension $extension >$null 2>&1 74 | } 75 | $status = 'Installed and enabled' 76 | } 77 | Add-ExtensionLog $extension $status 78 | } 79 | -------------------------------------------------------------------------------- /src/scripts/tools/brew.sh: -------------------------------------------------------------------------------- 1 | # Function to fetch a brew tap. 2 | fetch_brew_tap() { 3 | tap=$1 4 | tap_user=$(dirname "$tap") 5 | tap_name=$(basename "$tap") 6 | mkdir -p "$tap_dir/$tap_user" 7 | branch="$(git ls-remote --symref "https://github.com/$tap" HEAD | grep -Eo 'refs/heads/.*' | tr '\t' '\n' | head -1 | cut -d '/' -f 3)" 8 | get -s -n "" "https://github.com/$tap/archive/$branch.tar.gz" | sudo tar -xzf - -C "$tap_dir/$tap_user" 9 | sudo mv "$tap_dir/$tap_user/$tap_name-$branch" "$tap_dir/$tap_user/$tap_name" 10 | } 11 | 12 | # Function to add a brew tap. 13 | add_brew_tap() { 14 | tap=$1 15 | if ! [ -d "$tap_dir/$tap" ]; then 16 | if [ "${runner:?}" = "self-hosted" ]; then 17 | brew tap "$tap" >/dev/null 2>&1 18 | else 19 | fetch_brew_tap "$tap" >/dev/null 2>&1 20 | if ! [ -d "$tap_dir/$tap" ]; then 21 | brew tap "$tap" >/dev/null 2>&1 22 | fi 23 | fi 24 | fi 25 | } 26 | 27 | # Function to get brew prefix. 28 | get_brew_prefix() { 29 | if [ "$(uname -s)" = "Linux" ]; then 30 | echo /home/linuxbrew/.linuxbrew 31 | else 32 | if [ "$(uname -m)" = "arm64" ]; then 33 | echo /opt/homebrew 34 | else 35 | echo /usr/local 36 | fi 37 | fi 38 | } 39 | 40 | # Function to add brew's bin directories to the PATH. 41 | add_brew_bins_to_path() { 42 | local brew_prefix=${1:-$(get_brew_prefix)} 43 | add_path "$brew_prefix"/bin 44 | add_path "$brew_prefix"/sbin 45 | } 46 | 47 | # Function to add brew. 48 | add_brew() { 49 | brew_prefix="$(get_brew_prefix)" 50 | if ! [ -d "$brew_prefix"/bin ]; then 51 | step_log "Setup Brew" 52 | get -s "" "/tmp/install.sh" "https://raw.githubusercontent.com/Homebrew/install/master/install.sh" | bash -s >/dev/null 2>&1 53 | add_log "${tick:?}" "Brew" "Installed Homebrew" 54 | fi 55 | add_brew_bins_to_path "$brew_prefix" 56 | } 57 | 58 | # Function to configure brew constants. 59 | configure_brew() { 60 | brew_path="$(command -v brew)" 61 | if [ -z "$brew_path" ]; then 62 | add_brew 63 | brew_path="$(command -v brew)" 64 | fi 65 | brew_opts=(-f) 66 | brew_path_dir="$(dirname "$brew_path")" 67 | brew_prefix="$brew_path_dir"/.. 68 | brew_repo="$brew_path_dir/$(dirname "$(readlink "$brew_path")")"/.. 69 | tap_dir="$brew_repo"/Library/Taps 70 | core_repo="$tap_dir"/homebrew/homebrew-core 71 | 72 | export HOMEBREW_CHANGE_ARCH_TO_ARM=1 73 | export HOMEBREW_NO_AUTO_UPDATE=1 74 | export HOMEBREW_NO_ENV_HINTS=1 75 | export HOMEBREW_NO_INSTALL_CLEANUP=1 76 | export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 77 | export brew_opts 78 | export brew_path 79 | export brew_path_dir 80 | export brew_prefix 81 | export brew_repo 82 | export tap_dir 83 | export core_repo 84 | } 85 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Nice, you found a bug! 3 | title: "Bug: " 4 | labels: ["bug"] 5 | assignees: ["shivammathur"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: "Please fill out the sections below to help us address your issue." 10 | 11 | - type: textarea 12 | id: bug-description 13 | attributes: 14 | label: "Describe the bug" 15 | placeholder: "Please describe the bug concisely." 16 | validations: 17 | required: true 18 | 19 | - type: checkboxes 20 | id: version 21 | attributes: 22 | label: "Version" 23 | description: "I have checked releases, and the bug exists in the latest patch version of `v1` or `v2`." 24 | options: 25 | - label: "v2" 26 | - label: "v1" 27 | validations: 28 | required: true 29 | 30 | - type: dropdown 31 | id: runners 32 | attributes: 33 | label: "Runners" 34 | description: "Please choose the GitHub Action runner your workflow uses." 35 | options: 36 | - "GitHub Hosted" 37 | - "Self Hosted" 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | id: operating-systems 43 | attributes: 44 | label: "Operating systems" 45 | placeholder: "e.g., Ubuntu 20.04, Windows Server 2019, etc." 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: php-versions 51 | attributes: 52 | label: "PHP versions" 53 | placeholder: "e.g., PHP 7.4, PHP 8.0, etc." 54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | id: steps-to-reproduce 59 | attributes: 60 | label: "To Reproduce" 61 | placeholder: "Please provide the relevant steps of your workflow `.yml` file." 62 | validations: 63 | required: true 64 | 65 | - type: textarea 66 | id: expected-behavior 67 | attributes: 68 | label: "Expected behavior" 69 | placeholder: "A clear and concise description of what you expected to happen." 70 | validations: 71 | required: true 72 | 73 | - type: textarea 74 | id: screenshots-logs 75 | attributes: 76 | label: "Screenshots/Logs" 77 | placeholder: "Drag and drop images or paste logs here..." 78 | 79 | - type: textarea 80 | id: additional-context 81 | attributes: 82 | label: "Additional context" 83 | placeholder: "Add any other context about the problem here." 84 | 85 | - type: dropdown 86 | id: willing-to-submit-pr 87 | attributes: 88 | label: "Are you willing to submit a PR?" 89 | description: "We accept pull requests targeting the develop branch." 90 | options: 91 | - "Yes" 92 | - "No" 93 | validations: 94 | required: true 95 | -------------------------------------------------------------------------------- /__tests__/coverage.test.ts: -------------------------------------------------------------------------------- 1 | import * as coverage from '../src/coverage'; 2 | 3 | describe('Config tests', () => { 4 | it.each` 5 | driver | php | os | output 6 | ${'PCOV'} | ${'7.4'} | ${'win32'} | ${'Add-Extension pcov,Disable-Extension xdebug false'} 7 | ${'pcov'} | ${'7.4'} | ${'win32'} | ${'$pcov_version = php -r "echo phpversion(\'pcov\');"'} 8 | ${'pcov'} | ${'7.4'} | ${'win32'} | ${'PCOV $pcov_version enabled as coverage driver'} 9 | ${'pcov'} | ${'7.0'} | ${'win32'} | ${'PHP 7.1 or newer is required'} 10 | ${'pcov'} | ${'5.6'} | ${'win32'} | ${'PHP 7.1 or newer is required'} 11 | ${'pcov'} | ${'7.4'} | ${'win32'} | ${'Add-Extension pcov,Disable-Extension xdebug false'} 12 | ${'pcov'} | ${'7.4'} | ${'linux'} | ${'add_extension pcov,disable_extension xdebug false'} 13 | ${'pcov'} | ${'7.4'} | ${'darwin'} | ${'add_brew_extension pcov,disable_extension xdebug false'} 14 | ${'xdebug'} | ${'7.4'} | ${'win32'} | ${'Add-Extension xdebug'} 15 | ${'xdebug3'} | ${'7.1'} | ${'win32'} | ${'xdebug3 is not supported on PHP 7.1'} 16 | ${'xdebug2'} | ${'7.4'} | ${'win32'} | ${'Add-Extension xdebug stable 2.9.8'} 17 | ${'xdebug'} | ${'8.0'} | ${'linux'} | ${'add_extension xdebug'} 18 | ${'xdebug3'} | ${'8.0'} | ${'linux'} | ${'add_extension xdebug'} 19 | ${'xdebug2'} | ${'7.4'} | ${'linux'} | ${'add_pecl_extension xdebug 2.9.8 zend_extension'} 20 | ${'xdebug'} | ${'7.4'} | ${'linux'} | ${'xdebug_version="$(php -r "echo phpversion(\'xdebug\');")"'} 21 | ${'xdebug'} | ${'7.4'} | ${'linux'} | ${'Xdebug $xdebug_version enabled as coverage driver'} 22 | ${'xdebug'} | ${'7.4'} | ${'darwin'} | ${'add_brew_extension xdebug'} 23 | ${'xdebug3'} | ${'7.1'} | ${'darwin'} | ${'xdebug3 is not supported on PHP 7.1'} 24 | ${'xdebug2'} | ${'7.4'} | ${'darwin'} | ${'add_brew_extension xdebug2'} 25 | ${'xdebug2'} | ${'8.0'} | ${'darwin'} | ${'xdebug2 is not supported on PHP 8.0'} 26 | ${'none'} | ${'7.4'} | ${'win32'} | ${'Disable-Extension xdebug false,Disable-Extension pcov false'} 27 | ${'none'} | ${'7.4'} | ${'linux'} | ${'disable_extension xdebug false,disable_extension pcov false'} 28 | ${'none'} | ${'7.4'} | ${'darwin'} | ${'disable_extension xdebug false,disable_extension pcov false'} 29 | ${'nocov'} | ${'7.x'} | ${'any'} | ${''} 30 | ${''} | ${'7.x'} | ${'any'} | ${''} 31 | `( 32 | 'checking addCoverage with $driver on $os', 33 | async ({driver, php, os, output}) => { 34 | const script: string = await coverage.addCoverage(driver, php, os); 35 | if (output) { 36 | output.split(',').forEach((command: string) => { 37 | expect(script).toContain(command); 38 | }); 39 | } else { 40 | expect(script).toEqual(output); 41 | } 42 | } 43 | ); 44 | }); 45 | -------------------------------------------------------------------------------- /examples/symfony-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Symfony with MySQL 2 | name: Testing Symfony with MySQL 3 | on: [push, pull_request] 4 | jobs: 5 | symfony: 6 | name: Symfony (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | 9 | # Docs: https://docs.github.com/en/actions/using-containerized-services 10 | services: 11 | mysql: 12 | image: mysql:latest 13 | env: 14 | MYSQL_ALLOW_EMPTY_PASSWORD: false 15 | MYSQL_ROOT_PASSWORD: symfony 16 | MYSQL_DATABASE: symfony 17 | ports: 18 | - 3306/tcp 19 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | php-versions: ['7.4', '8.0', '8.1'] 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | # Docs: https://github.com/shivammathur/setup-php 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php-versions }} 33 | tools: phpunit-bridge 34 | extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql 35 | coverage: xdebug 36 | 37 | # Local MySQL service in GitHub hosted environments is disabled by default. 38 | # If you are using it instead of service containers, make sure you start it. 39 | # - name: Start mysql service 40 | # run: sudo systemctl start mysql.service 41 | 42 | - name: Get composer cache directory 43 | id: composer-cache 44 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 45 | 46 | - name: Cache composer dependencies 47 | uses: actions/cache@v3 48 | with: 49 | path: ${{ steps.composer-cache.outputs.dir }} 50 | # Use composer.json for key, if composer.lock is not committed. 51 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 52 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 53 | restore-keys: ${{ runner.os }}-composer- 54 | 55 | - name: Install Composer dependencies 56 | run: composer install --no-progress --prefer-dist --optimize-autoloader 57 | 58 | - name: Run Migration 59 | run: | 60 | composer require --dev symfony/orm-pack 61 | php bin/console doctrine:schema:update --force || echo "No migrations found or schema update failed" 62 | php bin/console doctrine:migrations:migrate || echo "No migrations found or migration failed" 63 | env: 64 | DATABASE_URL: mysql://root:symfony@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/symfony 65 | 66 | - name: Install PHPUnit 67 | run: simple-phpunit install 68 | 69 | - name: Run tests 70 | run: simple-phpunit --coverage-text 71 | -------------------------------------------------------------------------------- /src/scripts/extensions/oci.ps1: -------------------------------------------------------------------------------- 1 | # Function to log license information. 2 | Function Add-LicenseLog() { 3 | printf "$env:GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" $extension "Click to read the $extension related license information" 4 | printf "Oracle Instant Client package is required for %s extension.\n" $extension 5 | printf "It is provided under the Oracle Technology Network Development and Distribution License.\n" 6 | printf "Refer to: \033[35;1m%s \033[0m\n" "https://www.oracle.com/downloads/licenses/instant-client-lic.html" 7 | Write-Output "$env:END_GROUP" 8 | } 9 | 10 | # Function to get instantclinet. 11 | Function Add-InstantClient() { 12 | if (-not(Test-Path $php_dir\oci.dll)) { 13 | $suffix = 'windows' 14 | if ($arch -eq 'x86') { 15 | $suffix = 'nt' 16 | } 17 | Get-File -Url https://download.oracle.com/otn_software/nt/instantclient/instantclient-basiclite-$suffix.zip -OutFile $php_dir\instantclient.zip 18 | Expand-Archive -Path $php_dir\instantclient.zip -DestinationPath $php_dir -Force 19 | Copy-Item $php_dir\instantclient*\* $php_dir 20 | } 21 | } 22 | 23 | # Function to oci8 extension URL. 24 | Function Get-Oci8Url() { 25 | if($version -lt '8.0') { 26 | $ociVersion = '2.2.0' 27 | if ($version -eq '7.0') { 28 | $ociVersion = '2.1.8' 29 | } elseif ($version -lt '7.0') { 30 | $ociVersion = '2.0.12' 31 | } 32 | return Get-PeclArchiveUrl oci8 $ociVersion $installed 33 | } else { 34 | $ociUrl = ''; 35 | Get-PeclPackageVersion oci8 -MinimumStability stable -MaximumStability stable | ForEach-Object { 36 | $ociUrl = Get-PeclArchiveUrl oci8 $_ $installed 37 | if($ociUrl) { 38 | return $ociUrl 39 | } 40 | } 41 | } 42 | } 43 | 44 | # Function to get OCI8 DLL. 45 | Function Get-Oci8DLL() { 46 | Get-ChildItem $ext_dir\php_oci8*.dll | ForEach-Object { 47 | if((Get-PhpExtension -Path $_).PhpVersion -eq $version) { 48 | return $_ 49 | } 50 | } 51 | return $null 52 | } 53 | 54 | # Function to install oci8 and pdo_oci. 55 | Function Add-Oci() { 56 | Param ( 57 | [Parameter(Position = 0, Mandatory = $true)] 58 | [ValidateNotNull()] 59 | [ValidateSet('oci8', 'pdo_oci')] 60 | [string] 61 | $extension 62 | ) 63 | try { 64 | $status = 'Enabled' 65 | Add-InstantClient 66 | if($version -lt '8.4') { 67 | if($version -lt '5.6' -and $extension -eq 'oci8') { 68 | Add-Content -Value "`r`nextension=php_oci8.dll" -Path $php_dir\php.ini 69 | } else { 70 | Enable-PhpExtension $extension -Path $php_dir 71 | } 72 | } else { 73 | $status = 'Installed and enabled' 74 | Add-Extension $extension >$null 2>&1 75 | } 76 | Add-ExtensionLog $extension $status 77 | Add-LicenseLog 78 | } catch { 79 | Add-Log $cross $extension "Could not install $extension on PHP $( $installed.FullVersion )" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/symfony-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Symfony with PostgreSQL 2 | name: Testing Symfony with PostgreSQL 3 | on: [push, pull_request] 4 | jobs: 5 | symfony: 6 | name: Symfony (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | 9 | # Docs: https://docs.github.com/en/actions/using-containerized-services 10 | services: 11 | postgres: 12 | image: postgres:latest 13 | env: 14 | POSTGRES_USER: postgres 15 | POSTGRES_PASSWORD: postgres 16 | POSTGRES_DB: postgres 17 | ports: 18 | - 5432/tcp 19 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | php-versions: ['7.4', '8.0', '8.1'] 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | # Docs: https://github.com/shivammathur/setup-php 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php-versions }} 33 | tools: phpunit-bridge 34 | extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, pgsql 35 | coverage: xdebug 36 | 37 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 38 | # If you are using it instead of service containers, make sure you start it. 39 | # - name: Start postgresql service 40 | # run: sudo systemctl start postgresql.service 41 | 42 | - name: Get composer cache directory 43 | id: composer-cache 44 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 45 | 46 | - name: Cache composer dependencies 47 | uses: actions/cache@v3 48 | with: 49 | path: ${{ steps.composer-cache.outputs.dir }} 50 | # Use composer.json for key, if composer.lock is not committed. 51 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 52 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 53 | restore-keys: ${{ runner.os }}-composer- 54 | 55 | - name: Install Composer dependencies 56 | run: composer install --no-progress --prefer-dist --optimize-autoloader 57 | 58 | - name: Run Migration 59 | run: | 60 | composer require --dev symfony/orm-pack 61 | php bin/console doctrine:schema:update --force || echo "No migrations found or schema update failed" 62 | php bin/console doctrine:migrations:migrate || echo "No migrations found or migration failed" 63 | env: 64 | DATABASE_URL: postgres://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports[5432] }}/postgres?charset=UTF-8 65 | 66 | - name: Install PHPUnit 67 | run: simple-phpunit install 68 | 69 | - name: Run tests 70 | run: simple-phpunit --coverage-text 71 | -------------------------------------------------------------------------------- /src/scripts/extensions/zephir_parser.ps1: -------------------------------------------------------------------------------- 1 | # Function to get the url of the phalcon release asset. 2 | Function Get-ZephirParserReleaseAssetUrl() { 3 | Param ( 4 | [Parameter(Position = 0, Mandatory = $true)] 5 | [ValidateNotNull()] 6 | [string] 7 | $extension_version 8 | ) 9 | $repo = 'zephir-lang/php-zephir-parser' 10 | $zp_releases = "$github/$repo/releases" 11 | $nts = if (!$installed.ThreadSafe) { "nts" } else { "ts" } 12 | try { 13 | $match = (Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/tags/$extension_version").assets | Select-String -Pattern "browser_download_url=.*(zephir_parser-php-${version}-$nts-windows.*.zip)" 14 | } catch { 15 | $match = (Get-File -Url "$zp_releases/expanded_assets/$extension_version").Links.href | Select-String -Pattern "(zephir_parser-php-${version}-$nts-windows.*.zip)" 16 | } 17 | if($NULL -ne $match) { 18 | return "$zp_releases/download/$extension_version/$($match.Matches[0].Groups[1].Value)" 19 | } 20 | return false; 21 | } 22 | 23 | # Function to get zephir parser version using GitHub releases. 24 | Function Get-ZephirParserVersion() { 25 | Param ( 26 | [Parameter(Position = 0, Mandatory = $true)] 27 | [ValidateNotNull()] 28 | [string] 29 | $extension 30 | ) 31 | $repo = 'zephir-lang/php-zephir-parser' 32 | $zp_releases = "$github/$repo/releases" 33 | if($extension -eq 'zephir_parser') { 34 | return (Get-File -Url $zp_releases/latest).BaseResponse.RequestMessage.RequestUri.Segments[-1] 35 | } else { 36 | return 'v' + ($extension.split('-')[1] -replace 'v') 37 | } 38 | } 39 | 40 | # Function to add zephir parser using GitHub releases. 41 | Function Add-ZephirParserFromGitHub() { 42 | Param ( 43 | [Parameter(Position = 0, Mandatory = $true)] 44 | [ValidateNotNull()] 45 | [string] 46 | $extension 47 | ) 48 | $extension_version = Get-ZephirParserVersion $extension 49 | $zip_url = Get-ZephirParserReleaseAssetUrl $extension_version 50 | if($zip_url) { 51 | Get-File -Url $zip_url -OutFile $ENV:RUNNER_TOOL_CACHE\zp.zip > $null 2>&1 52 | Expand-Archive -Path $ENV:RUNNER_TOOL_CACHE\zp.zip -DestinationPath $ENV:RUNNER_TOOL_CACHE\zp -Force > $null 2>&1 53 | Copy-Item -Path "$ENV:RUNNER_TOOL_CACHE\zp\php_zephir_parser.dll" -Destination "$ext_dir\php_zephir_parser.dll" 54 | Enable-PhpExtension -Extension zephir_parser -Path $php_dir 55 | } else { 56 | throw "Unable to get zephir_parser release from the GitHub repo" 57 | } 58 | } 59 | 60 | # Function to add zephir parser. 61 | Function Add-ZephirParser() { 62 | Param ( 63 | [Parameter(Position = 0, Mandatory = $true)] 64 | [ValidateNotNull()] 65 | [string] 66 | $extension 67 | ) 68 | try { 69 | $status = 'Enabled' 70 | if (Test-Path $ext_dir\php_zephir_parser.dll) { 71 | Enable-PhpExtension -Extension zephir_parser -Path $php_dir 72 | } else { 73 | $status = 'Installed and enabled' 74 | try { 75 | Add-ZephirParserFromGitHub $extension 76 | } catch { 77 | Add-Extension $extension >$null 2>&1 78 | } 79 | } 80 | Add-ExtensionLog zephir_parser $status 81 | } catch { 82 | Add-Log $cross $extension "Could not install $extension on PHP $($installed.FullVersion)" 83 | } 84 | } -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to setup-php 2 | 3 | ## Welcome! 4 | 5 | We look forward to your contributions! Here are some examples how you can contribute: 6 | 7 | * [Ask any questions you may have](https://github.com/shivammathur/setup-php/discussions/new?category=Q-A-Help) 8 | * [Report a bug](https://github.com/shivammathur/setup-php/issues/new?labels=type/bug&template=bug.md) 9 | * [Propose a new feature](https://github.com/shivammathur/setup-php/issues/new?labels=enhancement&template=feature.md) 10 | * [Send a pull request](https://github.com/shivammathur/setup-php/pulls) 11 | 12 | ## Contributor Code of Conduct 13 | 14 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 15 | 16 | ## Getting Started 17 | 18 | To get started fork `setup-php` and clone it using git: 19 | 20 | ```bash 21 | git clone https://github.com//setup-php.git 22 | 23 | cd setup-php 24 | ``` 25 | 26 | If you are using `Windows` configure `git` to handle line endings. 27 | 28 | ```cmd 29 | git config --local core.autocrlf true 30 | ``` 31 | 32 | Install `setup-php` dependencies using [npm](https://www.npmjs.com/): 33 | 34 | ```bash 35 | npm install 36 | ``` 37 | 38 | ## Workflow to create Pull Requests 39 | 40 | * Fork the `setup-php` project and clone it. 41 | * Create a new branch from the develop branch. 42 | * Make your bug fix or feature addition. 43 | * Add tests for it, so we don't break it in a future version unintentionally. 44 | * Ensure the test suite passes and the code complies with our coding guidelines (see below). 45 | * Send a pull request to the develop branch with all the details. 46 | * If possible, create a GitHub Actions workflow with an integration test for the change in a demo repository and link it in your pull request. 47 | 48 | Please make sure that you have [set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) for use with Git. Strings such as `silly nick name ` look really stupid in the commit history of a project. 49 | 50 | Due to time constraints, you may not always get a quick response. Please do not take delays personally and feel free to remind. 51 | 52 | ## Coding Guidelines 53 | 54 | This project comes with `.prettierrc.json` and `eslintrc.json` configuration files. Please run the following commands to fix and verify the code quality. 55 | 56 | ```bash 57 | npm run format 58 | npm run lint 59 | ``` 60 | 61 | ### Running the test suite 62 | 63 | After following the steps shown above, The `setup-php` tests in the `__tests__` directory can be run using this command: 64 | 65 | ```bash 66 | npm test 67 | ``` 68 | 69 | ### Creating a release 70 | 71 | Creating a release means compiling all the TypeScript code to a single file which `setup-php` can run. Run this, before you push your changes. 72 | 73 | ```bash 74 | npm run build 75 | npm run release 76 | ``` 77 | 78 | ### Reporting issues and discussions 79 | 80 | For questions or support, we prefer GitHub Discussions. For any bugs or new features you can create an issue using the appropriate template: 81 | 82 | * [Discussions](https://github.com/shivammathur/setup-php/discussions) 83 | * [Issues](https://github.com/shivammathur/setup-php/issues) 84 | -------------------------------------------------------------------------------- /examples/laravel-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Laravel with MySQL and Redis 2 | name: Testing Laravel with MySQL 3 | on: [push, pull_request] 4 | jobs: 5 | laravel: 6 | name: Laravel (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | DB_DATABASE: laravel 10 | DB_USERNAME: root 11 | DB_PASSWORD: password 12 | BROADCAST_DRIVER: log 13 | CACHE_DRIVER: redis 14 | QUEUE_CONNECTION: redis 15 | SESSION_DRIVER: redis 16 | 17 | # Docs: https://docs.github.com/en/actions/using-containerized-services 18 | services: 19 | mysql: 20 | image: mysql:latest 21 | env: 22 | MYSQL_ALLOW_EMPTY_PASSWORD: false 23 | MYSQL_ROOT_PASSWORD: password 24 | MYSQL_DATABASE: laravel 25 | ports: 26 | - 3306/tcp 27 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 28 | 29 | redis: 30 | image: redis 31 | ports: 32 | - 6379/tcp 33 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | php-versions: ['7.4', '8.0', '8.1'] 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | # Docs: https://github.com/shivammathur/setup-php 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: ${{ matrix.php-versions }} 47 | extensions: mbstring, dom, fileinfo, mysql 48 | coverage: xdebug 49 | 50 | # Local MySQL service in GitHub hosted environments is disabled by default. 51 | # If you are using it instead of service containers, make sure you start it. 52 | # - name: Start mysql service 53 | # run: sudo systemctl start mysql.service 54 | 55 | - name: Get composer cache directory 56 | id: composer-cache 57 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 58 | 59 | - name: Cache composer dependencies 60 | uses: actions/cache@v3 61 | with: 62 | path: ${{ steps.composer-cache.outputs.dir }} 63 | # Use composer.json for key, if composer.lock is not committed. 64 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 66 | restore-keys: ${{ runner.os }}-composer- 67 | 68 | - name: Install Composer dependencies 69 | run: composer install --no-progress --prefer-dist --optimize-autoloader 70 | 71 | - name: Prepare the application 72 | run: | 73 | php -r "file_exists('.env') || copy('.env.example', '.env');" 74 | php artisan key:generate 75 | 76 | - name: Clear Config 77 | run: php artisan config:clear 78 | 79 | - name: Run Migration 80 | run: php artisan migrate -v 81 | env: 82 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 83 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 84 | 85 | - name: Test with phpunit 86 | run: vendor/bin/phpunit --coverage-text 87 | env: 88 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 89 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 90 | -------------------------------------------------------------------------------- /examples/yii2-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Yii Framework with MySQL 2 | name: Testing Yii2 with MySQL 3 | on: [push, pull_request] 4 | jobs: 5 | yii: 6 | name: Yii2 (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | DB_USERNAME: root 10 | DB_PASSWORD: yii 11 | TEST_DB_USERNAME: root 12 | TEST_DB_PASSWORD: yii 13 | DB_CHARSET: utf8 14 | 15 | # Docs: https://docs.github.com/en/actions/using-containerized-services 16 | services: 17 | mysql: 18 | image: mysql:latest 19 | env: 20 | MYSQL_ALLOW_EMPTY_PASSWORD: false 21 | MYSQL_ROOT_PASSWORD: yii 22 | MYSQL_DATABASE: yii 23 | ports: 24 | - 3306/tcp 25 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | php-versions: ['7.4', '8.0'] 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Set Node.js 10.x 35 | uses: actions/setup-node@v1 36 | with: 37 | node-version: 10.x 38 | 39 | # Docs: https://github.com/shivammathur/setup-php 40 | - name: Setup PHP 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: ${{ matrix.php-versions }} 44 | extensions: mbstring, intl, gd, imagick, zip, dom, mysql 45 | coverage: xdebug 46 | 47 | # Local MySQL service in GitHub hosted environments is disabled by default. 48 | # If you are using it instead of service containers, make sure you start it. 49 | # - name: Start mysql service 50 | # run: sudo systemctl start mysql.service 51 | 52 | - name: Get composer cache directory 53 | id: composer-cache 54 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 55 | 56 | - name: Cache composer dependencies 57 | uses: actions/cache@v3 58 | with: 59 | path: ${{ steps.composer-cache.outputs.dir }} 60 | # Use composer.json for key, if composer.lock is not committed. 61 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 62 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 63 | restore-keys: ${{ runner.os }}-composer- 64 | 65 | - name: Install Composer dependencies 66 | run: composer install --no-progress --prefer-dist --optimize-autoloader 67 | 68 | - name: Prepare the application 69 | run: | 70 | php -r "file_exists('.env') || copy('.env.dist', '.env');" 71 | php console/yii app/setup 72 | npm install --development 73 | npm run build 74 | env: 75 | DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii 76 | TEST_DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii 77 | 78 | - name: Run Tests 79 | run: | 80 | vendor/bin/codecept build 81 | php tests/bin/yii app/setup --interactive=0 82 | nohup php -S localhost:8080 > yii.log 2>&1 & 83 | vendor/bin/codecept run 84 | env: 85 | DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii 86 | TEST_DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii 87 | -------------------------------------------------------------------------------- /examples/phalcon-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Phalcon with MySQL 2 | ## Notes 3 | ## Make sure you have .env.example or .env file in your project 4 | ## and you have loaded Dotenv (https://github.com/vlucas/phpdotenv) 5 | name: Testing Phalcon with MySQL 6 | on: [push, pull_request] 7 | jobs: 8 | phalcon: 9 | name: Phalcon (PHP ${{ matrix.php-versions }}) 10 | runs-on: ubuntu-latest 11 | env: 12 | DB_ADAPTER: mysql 13 | DB_HOST: 127.0.0.1 14 | DB_NAME: phalcon 15 | DB_USERNAME: root 16 | DB_PASSWORD: password 17 | CODECEPTION_URL: 127.0.0.1 18 | CODECEPTION_PORT: 8888 19 | 20 | # Docs: https://docs.github.com/en/actions/using-containerized-services 21 | services: 22 | mysql: 23 | image: mysql:latest 24 | env: 25 | MYSQL_ALLOW_EMPTY_PASSWORD: false 26 | MYSQL_ROOT_PASSWORD: password 27 | MYSQL_DATABASE: phalcon 28 | ports: 29 | - 3306/tcp 30 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | php-versions: ['7.2', '7.3', '7.4'] 35 | # For phalcon 3.x, use 36 | # php-versions: ['7.0', '7.1', '7.2', '7.3'] 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | # Docs: https://github.com/shivammathur/setup-php 42 | - name: Setup PHP 43 | uses: shivammathur/setup-php@v2 44 | with: 45 | php-version: ${{ matrix.php-versions }} 46 | # Use phalcon3 for the phalcon 3.x. 47 | extensions: mbstring, dom, zip, phalcon4, mysql 48 | coverage: xdebug 49 | 50 | # Local MySQL service in GitHub hosted environments is disabled by default. 51 | # If you are using it instead of service containers, make sure you start it. 52 | # - name: Start mysql service 53 | # run: sudo systemctl start mysql.service 54 | 55 | - name: Get composer cache directory 56 | id: composer-cache 57 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 58 | 59 | - name: Cache composer dependencies 60 | uses: actions/cache@v3 61 | with: 62 | path: ${{ steps.composer-cache.outputs.dir }} 63 | # Use composer.json for key, if composer.lock is not committed. 64 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 66 | restore-keys: ${{ runner.os }}-composer- 67 | 68 | - name: Install Composer dependencies 69 | run: composer install --no-progress --prefer-dist --optimize-autoloader 70 | 71 | - name: Prepare the application 72 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 73 | 74 | - name: Run Migration 75 | run: | 76 | if [ ! -e phinx.yml ]; then vendor/bin/phinx init; fi 77 | vendor/bin/phinx migrate 78 | vendor/bin/phinx seed:run 79 | env: 80 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 81 | 82 | - name: Run Tests 83 | run: | 84 | (cd public && nohup php -S $CODECEPTION_URL:$CODECEPTION_PORT > phalcon.log 2>&1 &) 85 | vendor/bin/codecept build 86 | vendor/bin/codecept run 87 | env: 88 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 89 | -------------------------------------------------------------------------------- /examples/laravel-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Laravel with PostgreSQL and Redis 2 | name: Testing Laravel with PostgreSQL 3 | on: [push, pull_request] 4 | jobs: 5 | laravel: 6 | name: Laravel (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | BROADCAST_DRIVER: log 10 | CACHE_DRIVER: redis 11 | QUEUE_CONNECTION: redis 12 | SESSION_DRIVER: redis 13 | DB_CONNECTION: pgsql 14 | DB_HOST: localhost 15 | DB_PASSWORD: postgres 16 | DB_USERNAME: postgres 17 | DB_DATABASE: postgres 18 | 19 | # Docs: https://docs.github.com/en/actions/using-containerized-services 20 | services: 21 | postgres: 22 | image: postgres:latest 23 | env: 24 | POSTGRES_USER: postgres 25 | POSTGRES_PASSWORD: postgres 26 | POSTGRES_DB: postgres 27 | ports: 28 | - 5432/tcp 29 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 30 | 31 | redis: 32 | image: redis 33 | ports: 34 | - 6379/tcp 35 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | php-versions: ['7.4', '8.0', '8.1'] 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | 44 | # Docs: https://github.com/shivammathur/setup-php 45 | - name: Setup PHP 46 | uses: shivammathur/setup-php@v2 47 | with: 48 | php-version: ${{ matrix.php-versions }} 49 | extensions: mbstring, dom, fileinfo, pgsql 50 | coverage: xdebug 51 | 52 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 53 | # If you are using it instead of service containers, make sure you start it. 54 | # - name: Start postgresql service 55 | # run: sudo systemctl start postgresql.service 56 | 57 | - name: Get composer cache directory 58 | id: composer-cache 59 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 60 | 61 | - name: Cache composer dependencies 62 | uses: actions/cache@v3 63 | with: 64 | path: ${{ steps.composer-cache.outputs.dir }} 65 | # Use composer.json for key, if composer.lock is not committed. 66 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 67 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 68 | restore-keys: ${{ runner.os }}-composer- 69 | 70 | - name: Install Composer dependencies 71 | run: composer install --no-progress --prefer-dist --optimize-autoloader 72 | 73 | - name: Prepare the application 74 | run: | 75 | php -r "file_exists('.env') || copy('.env.example', '.env');" 76 | php artisan key:generate 77 | 78 | - name: Clear Config 79 | run: php artisan config:clear 80 | 81 | - name: Run Migration 82 | run: php artisan migrate -v 83 | env: 84 | DB_PORT: ${{ job.services.postgres.ports[5432] }} 85 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 86 | 87 | - name: Test with phpunit 88 | run: vendor/bin/phpunit --coverage-text 89 | env: 90 | DB_PORT: ${{ job.services.postgres.ports[5432] }} 91 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 92 | -------------------------------------------------------------------------------- /examples/phalcon-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Phalcon with PostgreSQL 2 | ## Notes 3 | ## Make sure you have .env.example or .env file in your project 4 | ## and you have loaded Dotenv (https://github.com/vlucas/phpdotenv) 5 | name: Testing Phalcon with PostgreSQL 6 | on: [push, pull_request] 7 | jobs: 8 | phalcon: 9 | name: Phalcon (PHP ${{ matrix.php-versions }}) 10 | runs-on: ubuntu-latest 11 | env: 12 | DB_ADAPTER: pgsql 13 | DB_HOST: 127.0.0.1 14 | DB_NAME: postgres 15 | DB_USERNAME: postgres 16 | DB_PASSWORD: postgres 17 | CODECEPTION_URL: 127.0.0.1 18 | CODECEPTION_PORT: 8888 19 | DB_CONNECTION: pgsql 20 | 21 | # Docs: https://docs.github.com/en/actions/using-containerized-services 22 | services: 23 | postgres: 24 | image: postgres:latest 25 | env: 26 | POSTGRES_USER: postgres 27 | POSTGRES_PASSWORD: postgres 28 | POSTGRES_DB: postgres 29 | ports: 30 | - 5432/tcp 31 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | php-versions: ['7.2', '7.3', '7.4'] 36 | # For phalcon 3.x, use 37 | # php-versions: ['7.0', '7.1', '7.2', '7.3'] 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | # Docs: https://github.com/shivammathur/setup-php 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: ${{ matrix.php-versions }} 47 | # Use phalcon3 for the phalcon 3.x 48 | extensions: mbstring, dom, zip, phalcon4, pgsql 49 | coverage: xdebug 50 | 51 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 52 | # If you are using it instead of service containers, make sure you start it. 53 | # - name: Start postgresql service 54 | # run: sudo systemctl start postgresql.service 55 | 56 | - name: Get composer cache directory 57 | id: composer-cache 58 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 59 | - name: Cache composer dependencies 60 | uses: actions/cache@v3 61 | with: 62 | path: ${{ steps.composer-cache.outputs.dir }} 63 | # Use composer.json for key, if composer.lock is not committed. 64 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 66 | restore-keys: ${{ runner.os }}-composer- 67 | - name: Install Composer dependencies 68 | run: composer install --no-progress --prefer-dist --optimize-autoloader 69 | - name: Prepare the application 70 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 71 | - name: Run Migration 72 | run: | 73 | if [ ! -e phinx.yml ]; then vendor/bin/phinx init; fi 74 | vendor/bin/phinx migrate 75 | vendor/bin/phinx seed:run 76 | env: 77 | DB_PORT: ${{ job.services.postgres.ports['5432'] }} 78 | - name: Run Tests 79 | run: | 80 | (cd public && nohup php -S $CODECEPTION_URL:$CODECEPTION_PORT > phalcon.log 2>&1 &) 81 | vendor/bin/codecept build 82 | vendor/bin/codecept run 83 | env: 84 | DB_PORT: ${{ job.services.postgres.ports['5432'] }} -------------------------------------------------------------------------------- /src/scripts/extensions/phalcon.sh: -------------------------------------------------------------------------------- 1 | # Helper function to get phalcon version 2 | get_phalcon_version() { 3 | if [ "$extension" = "phalcon5" ]; then 4 | if [ "${version:?}" = "7.4" ]; then 5 | echo '5.4.0' 6 | else 7 | get_pecl_version phalcon stable 5 8 | fi 9 | elif [ "$extension" = "phalcon4" ]; then 10 | echo '4.1.2' 11 | elif [ "$extension" = "phalcon3" ]; then 12 | echo '3.4.5' 13 | fi 14 | } 15 | 16 | # Function to add phalcon from repo. 17 | add_phalcon_from_repo(){ 18 | version=${version:?} 19 | if [ "$extension" = "phalcon5" ]; then 20 | PHALCON_PATH=build/phalcon 21 | else 22 | PHALCON_PATH=build/php"${version%.*}"/64bits 23 | fi 24 | PHALCON_CONFIGURE_OPTS="--enable-phalcon --with-php-config=$(command -v php-config)" 25 | export PHALCON_PATH 26 | export PHALCON_CONFIGURE_OPTS 27 | add_extension_from_source phalcon https://github.com phalcon cphalcon v"$(get_phalcon_version)" extension 28 | } 29 | 30 | # Helper function to add phalcon. 31 | add_phalcon_helper() { 32 | status='Installed and enabled' 33 | if [ "$(uname -s)" = "Darwin" ]; then 34 | add_brew_extension "$extension" extension 35 | else 36 | package="php${version:?}-$extension" 37 | add_ppa ondrej/php >/dev/null 2>&1 || update_ppa ondrej/php 38 | [[ "$extension" =~ phalcon[4|5] ]] && (install_packages "php${version:?}-psr" || pecl_install psr || pecl_install psr-1.1.0) 39 | (check_package "$package" && install_packages "$package") || pecl_install phalcon-"$(get_phalcon_version)" || add_phalcon_from_repo 40 | fi 41 | } 42 | 43 | # Function to add phalcon3. 44 | add_phalcon3() { 45 | if shared_extension phalcon; then 46 | phalcon_version=$(php -d="extension=phalcon.so" -r "echo phpversion('phalcon');" | cut -d'.' -f 1) 47 | if [ "$phalcon_version" != "$extension_major_version" ]; then 48 | add_phalcon_helper 49 | else 50 | enable_extension phalcon extension 51 | fi 52 | else 53 | add_phalcon_helper 54 | fi 55 | } 56 | 57 | # Function to add phalcon4. 58 | add_phalcon4() { 59 | enable_extension psr extension 60 | if shared_extension phalcon; then 61 | if check_extension psr; then 62 | phalcon_version=$(php -d="extension=phalcon" -r "echo phpversion('phalcon');" | cut -d'.' -f 1) 63 | if [ "$phalcon_version" != "$extension_major_version" ]; then 64 | add_phalcon_helper 65 | else 66 | enable_extension phalcon extension 67 | fi 68 | else 69 | add_phalcon_helper 70 | fi 71 | else 72 | add_phalcon_helper 73 | fi 74 | } 75 | 76 | # Function to add phalcon5. 77 | add_phalcon5() { 78 | if shared_extension phalcon; then 79 | phalcon_version=$(php -d="extension=phalcon.so" -r "echo phpversion('phalcon');" | cut -d'.' -f 1) 80 | if [ "$phalcon_version" != "$extension_major_version" ]; then 81 | add_phalcon_helper 82 | else 83 | enable_extension phalcon extension 84 | fi 85 | else 86 | add_phalcon_helper 87 | fi 88 | } 89 | 90 | # Function to add phalcon. 91 | add_phalcon() { 92 | local extension=$1 93 | status='Enabled' 94 | [ "$extension" = "phalcon" ] && extension=phalcon5 95 | extension_major_version=${extension: -1} 96 | if [[ "$extension_major_version" =~ [3-5] ]]; then 97 | add_phalcon"$extension_major_version" >/dev/null 2>&1 98 | fi 99 | add_extension_log "phalcon" "$status" 100 | } 101 | -------------------------------------------------------------------------------- /examples/lumen-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Lumen with MySQL and Redis 2 | name: Testing Lumen with MySQL 3 | on: [push, pull_request] 4 | jobs: 5 | lumen: 6 | name: Lumen (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | DB_DATABASE: lumen 10 | DB_USERNAME: root 11 | DB_PASSWORD: password 12 | BROADCAST_DRIVER: log 13 | CACHE_DRIVER: redis 14 | QUEUE_CONNECTION: redis 15 | SESSION_DRIVER: redis 16 | 17 | # Docs: https://docs.github.com/en/actions/using-containerized-services 18 | services: 19 | mysql: 20 | image: mysql:latest 21 | env: 22 | MYSQL_ALLOW_EMPTY_PASSWORD: false 23 | MYSQL_ROOT_PASSWORD: password 24 | MYSQL_DATABASE: lumen 25 | ports: 26 | - 3306/tcp 27 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 28 | 29 | redis: 30 | image: redis 31 | ports: 32 | - 6379/tcp 33 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | php-versions: ['7.4', '8.0', '8.1'] 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | # Docs: https://github.com/shivammathur/setup-php 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: ${{ matrix.php-versions }} 47 | extensions: mbstring, dom, fileinfo, mysql 48 | coverage: xdebug 49 | 50 | # Local MySQL service in GitHub hosted environments is disabled by default. 51 | # If you are using it instead of service containers, make sure you start it. 52 | # - name: Start mysql service 53 | # run: sudo systemctl start mysql.service 54 | 55 | - name: Get composer cache directory 56 | id: composer-cache 57 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 58 | 59 | - name: Cache composer dependencies 60 | uses: actions/cache@v3 61 | with: 62 | path: ${{ steps.composer-cache.outputs.dir }} 63 | # Use composer.json for key, if composer.lock is not committed. 64 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 66 | restore-keys: ${{ runner.os }}-composer- 67 | 68 | - name: Install Composer dependencies 69 | run: | 70 | composer install --no-progress --prefer-dist --optimize-autoloader 71 | composer require predis/predis illuminate/redis 72 | 73 | - name: Prepare the application 74 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 75 | 76 | - name: Register Redis as service provider 77 | run: sed -i '$i\$app->register(Illuminate\\Redis\\RedisServiceProvider::class);' bootstrap/app.php 78 | 79 | - name: Run Migration 80 | run: php artisan migrate -v 81 | env: 82 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 83 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 84 | 85 | - name: Test with phpunit 86 | run: vendor/bin/phpunit --coverage-text 87 | env: 88 | DB_PORT: ${{ job.services.mysql.ports['3306'] }} 89 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 90 | -------------------------------------------------------------------------------- /examples/yii2-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Yii Framework with PostgreSQL 2 | name: Testing Yii2 with PostgreSQL 3 | on: [push, pull_request] 4 | jobs: 5 | yii: 6 | name: Yii2 (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | DB_USERNAME: postgres 10 | DB_PASSWORD: postgres 11 | TEST_DB_USERNAME: postgres 12 | TEST_DB_PASSWORD: postgres 13 | DB_CHARSET: utf8 14 | 15 | # Docs: https://docs.github.com/en/actions/using-containerized-services 16 | services: 17 | postgres: 18 | image: postgres:latest 19 | env: 20 | POSTGRES_USER: postgres 21 | POSTGRES_PASSWORD: postgres 22 | POSTGRES_DB: postgres 23 | ports: 24 | - 5432/tcp 25 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | php-versions: ['7.4', '8.0'] 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Set Node.js 10.x 35 | uses: actions/setup-node@v1 36 | with: 37 | node-version: 10.x 38 | 39 | # Docs: https://github.com/shivammathur/setup-php 40 | - name: Setup PHP 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: ${{ matrix.php-versions }} 44 | extensions: mbstring, intl, gd, imagick, zip, dom, pgsql 45 | coverage: xdebug 46 | 47 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 48 | # If you are using it instead of service containers, make sure you start it. 49 | # - name: Start postgresql service 50 | # run: sudo systemctl start postgresql.service 51 | 52 | - name: Get composer cache directory 53 | id: composer-cache 54 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 55 | 56 | - name: Cache composer dependencies 57 | uses: actions/cache@v3 58 | with: 59 | path: ${{ steps.composer-cache.outputs.dir }} 60 | # Use composer.json for key, if composer.lock is not committed. 61 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 62 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 63 | restore-keys: ${{ runner.os }}-composer- 64 | 65 | - name: Install Composer dependencies 66 | run: composer install --no-progress --prefer-dist --optimize-autoloader 67 | 68 | - name: Prepare the application 69 | run: | 70 | php -r "file_exists('.env') || copy('.env.dist', '.env');" 71 | php console/yii app/setup 72 | npm install --development 73 | npm run build 74 | env: 75 | DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres 76 | TEST_DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres 77 | 78 | - name: Run Tests 79 | run: | 80 | vendor/bin/codecept build 81 | php tests/bin/yii app/setup --interactive=0 82 | nohup php -S localhost:8080 > yii.log 2>&1 & 83 | vendor/bin/codecept run 84 | env: 85 | DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres 86 | TEST_DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres 87 | -------------------------------------------------------------------------------- /src/scripts/extensions/couchbase.sh: -------------------------------------------------------------------------------- 1 | # Function to install libraries required by couchbase 2 | add_couchbase_clibs() { 3 | ext=$1 4 | trunk="https://github.com/couchbase/libcouchbase/releases" 5 | if [[ "$ext" =~ couchbase-2.+ ]]; then 6 | release="2.10.9" 7 | else 8 | release=$(get -s -n "" "$trunk"/latest | grep -Eo -m 1 "[0-9]+\.[0-9]+\.[0-9]+" | head -n 1) 9 | fi 10 | [ "$VERSION_ID" = "24.04" ] && vid=22.04 || vid="$VERSION_ID" 11 | [ "$VERSION_CODENAME" = "noble" ] && vcn=jammy || vcn="$VERSION_CODENAME" 12 | deb_url="$trunk/download/$release/libcouchbase-${release}_ubuntu${vid/./}_${vcn}_amd64.tar" 13 | get -q -n /tmp/libcouchbase.tar "$deb_url" 14 | if ! [ -e /tmp/libcouchbase.tar ] || ! file /tmp/libcouchbase.tar | grep -q 'tar archive'; then 15 | deb_url="$trunk/download/$release/libcouchbase-${release}_ubuntu2004_focal_amd64.tar" 16 | get -q -n /tmp/libcouchbase.tar "$deb_url" 17 | add_old_libssl 18 | fi 19 | sudo tar -xf /tmp/libcouchbase.tar -C /tmp 20 | install_packages libev4 libevent-dev 21 | sudo dpkg -i /tmp/libcouchbase-*/*.deb 22 | } 23 | 24 | add_old_libssl() { 25 | if [[ "$VERSION_ID" = "24.04" ]]; then 26 | get -q -n /tmp/libssl.deb http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb 27 | [ -e /tmp/libssl.deb ] && sudo dpkg -i /tmp/libssl.deb || add_extension_log "couchbase" "Could not install libssl1.1" 28 | fi 29 | } 30 | 31 | add_couchbase_cxxlibs() { 32 | if [ "${runner:?}" = "self-hosted" ]; then 33 | add_list cmake https://apt.kitware.com/ubuntu/ https://apt.kitware.com/keys/kitware-archive-latest.asc "$VERSION_CODENAME" main 34 | fi 35 | install_packages cmake ccache 36 | } 37 | 38 | get_couchbase_version() { 39 | if [[ "${version:?}" =~ ${old_versions:?} ]]; then 40 | echo couchbase-2.2.3 41 | elif [[ "${version:?}" =~ 5.6|7.[0-1] ]]; then 42 | echo couchbase-2.6.2 43 | elif [ "${version:?}" = '7.2' ]; then 44 | echo couchbase-3.0.4 45 | elif [ "${version:?}" = '7.3' ]; then 46 | echo couchbase-3.2.2 47 | elif [ "${version:?}" = '7.4' ]; then 48 | echo couchbase-4.1.1 49 | else 50 | echo couchbase 51 | fi 52 | } 53 | 54 | # Function to add couchbase. 55 | add_couchbase() { 56 | ext=$1 57 | if [ "$(uname -s)" = "Linux" ]; then 58 | if [ "$ext" = "couchbase" ]; then 59 | ext=$(get_couchbase_version) 60 | fi 61 | if [[ "$ext" =~ couchbase-[2-3].+ ]]; then 62 | add_couchbase_clibs "$ext" >/dev/null 2>&1 63 | else 64 | add_couchbase_cxxlibs >/dev/null 2>&1 65 | fi 66 | enable_extension "couchbase" "extension" 67 | if check_extension "couchbase"; then 68 | add_log "${tick:?}" "couchbase" "Enabled" 69 | else 70 | if [ "$ext" = "couchbase" ]; then 71 | ext="couchbase-$(get_pecl_version "couchbase" "stable")" 72 | n_proc="$(nproc)" 73 | export COUCHBASE_SUFFIX_OPTS="CMAKE_BUILD_TYPE=Release" 74 | export CMAKE_BUILD_PARALLEL_LEVEL="$n_proc" 75 | add_extension_from_source couchbase https://pecl.php.net couchbase couchbase "${ext##*-}" extension pecl >/dev/null 2>&1 76 | else 77 | pecl_install "${ext}" >/dev/null 2>&1 78 | fi 79 | add_extension_log "couchbase" "Installed and enabled" 80 | fi 81 | else 82 | if [ -e "${ext_dir:?}"/libcouchbase_php_core.dylib ]; then 83 | sudo cp "${ext_dir:?}"/libcouchbase_php_core.dylib "${brew_prefix:?}"/lib 84 | fi 85 | add_brew_extension couchbase extension 86 | fi 87 | } 88 | -------------------------------------------------------------------------------- /examples/lumen-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Lumen with PostgreSQL and Redis 2 | name: Testing Lumen with PostgreSQL 3 | on: [push, pull_request] 4 | jobs: 5 | laravel: 6 | name: Lumen (PHP ${{ matrix.php-versions }}) 7 | runs-on: ubuntu-latest 8 | env: 9 | BROADCAST_DRIVER: log 10 | CACHE_DRIVER: redis 11 | QUEUE_CONNECTION: redis 12 | SESSION_DRIVER: redis 13 | DB_CONNECTION: pgsql 14 | DB_HOST: localhost 15 | DB_PASSWORD: postgres 16 | DB_USERNAME: postgres 17 | DB_DATABASE: postgres 18 | 19 | # Docs: https://docs.github.com/en/actions/using-containerized-services 20 | services: 21 | postgres: 22 | image: postgres:latest 23 | env: 24 | POSTGRES_USER: postgres 25 | POSTGRES_PASSWORD: postgres 26 | POSTGRES_DB: postgres 27 | ports: 28 | - 5432/tcp 29 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 30 | 31 | redis: 32 | image: redis 33 | ports: 34 | - 6379/tcp 35 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | php-versions: ['7.4', '8.0', '8.1'] 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | 44 | # Docs: https://github.com/shivammathur/setup-php 45 | - name: Setup PHP 46 | uses: shivammathur/setup-php@v2 47 | with: 48 | php-version: ${{ matrix.php-versions }} 49 | extensions: mbstring, dom, fileinfo, pgsql 50 | coverage: xdebug 51 | 52 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 53 | # If you are using it instead of service containers, make sure you start it. 54 | # - name: Start postgresql service 55 | # run: sudo systemctl start postgresql.service 56 | 57 | - name: Get composer cache directory 58 | id: composer-cache 59 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 60 | 61 | - name: Cache composer dependencies 62 | uses: actions/cache@v3 63 | with: 64 | path: ${{ steps.composer-cache.outputs.dir }} 65 | # Use composer.json for key, if composer.lock is not committed. 66 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 67 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 68 | restore-keys: ${{ runner.os }}-composer- 69 | 70 | - name: Install Composer dependencies 71 | run: | 72 | composer install --no-progress --prefer-dist --optimize-autoloader 73 | composer require predis/predis illuminate/redis 74 | 75 | - name: Prepare the application 76 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 77 | 78 | - name: Register Redis as service provider 79 | run: sed -i '$i\$app->register(Illuminate\\Redis\\RedisServiceProvider::class);' bootstrap/app.php 80 | 81 | - name: Run Migration 82 | run: php artisan migrate -v 83 | env: 84 | DB_PORT: ${{ job.services.postgres.ports[5432] }} 85 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 86 | 87 | - name: Test with phpunit 88 | run: vendor/bin/phpunit --coverage-text 89 | env: 90 | DB_PORT: ${{ job.services.postgres.ports[5432] }} 91 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 92 | -------------------------------------------------------------------------------- /src/scripts/extensions/oci.sh: -------------------------------------------------------------------------------- 1 | # Function to log result of a operation. 2 | add_license_log() { 3 | printf "$GROUP\033[34;1m%s \033[0m\033[90;1m%s \033[0m\n" "$ext" "Click to read the $ext related license information" 4 | printf "Oracle Instant Client package is required for %s extension.\n" "$ext" 5 | printf "It is provided under the Oracle Technology Network Development and Distribution License.\n" 6 | printf "Refer to: \033[35;1m%s \033[0m\n" "https://www.oracle.com/downloads/licenses/instant-client-lic.html" 7 | echo "$END_GROUP" 8 | } 9 | 10 | # Function to install instantclient and SDK. 11 | add_client() { 12 | if [ ! -e "$oracle_client" ]; then 13 | sudo mkdir -p -m 777 "$oracle_home" "$oracle_client" 14 | arch="$(uname -m)" 15 | for package in basiclite sdk; do 16 | if [ "$os" = 'Linux' ]; then 17 | libs='/usr/lib/' 18 | os_name='linux' 19 | [[ "$arch" = 'arm64' || "$arch" = 'aarch64' ]] && arch_suffix='linux-arm64' || arch_suffix='linuxx64' 20 | lib_ext='so' 21 | elif [ "$os" = 'Darwin' ]; then 22 | libs='/usr/local/lib/' 23 | os_name='mac' 24 | arch_suffix='macos' 25 | lib_ext='dylib' 26 | fi 27 | if [[ "$os" = 'Darwin' && ("$arch" = 'arm64' || "$arch" = 'aarch64') ]]; then 28 | get -q -n "/opt/oracle/$package.dmg" "https://download.oracle.com/otn_software/$os_name/instantclient/instantclient-$package-macos-arm64.dmg" 29 | sudo hdiutil attach "/opt/oracle/$package.dmg" 30 | (cd /Volumes/instantclient-"$package"-macos.arm64-* && bash install_ic.sh) 31 | sudo cp -a ~/Downloads/instantclient_* /opt/oracle/ 32 | else 33 | get -q -n "/opt/oracle/$package.zip" "https://download.oracle.com/otn_software/$os_name/instantclient/instantclient-$package-$arch_suffix.zip" 34 | unzip -o "/opt/oracle/$package.zip" -d "$oracle_home" 35 | fi 36 | done 37 | for icdir in /opt/oracle/instantclient_*; do 38 | sudo mv "$icdir"/* "$oracle_client"/ 39 | done 40 | sudo mkdir -p "$libs" 41 | sudo ln -sf /opt/oracle/instantclient/*.$lib_ext* $libs 42 | if [ "$os" = "Linux" ]; then 43 | [ -e "$libs/$arch"-linux-gnu/libaio.so.1 ] || sudo ln -sf "$libs/$arch"-linux-gnu/libaio.so.1t64 "$libs/$arch"-linux-gnu/libaio.so.1 44 | fi 45 | fi 46 | } 47 | 48 | # Function to install oci8 and pdo_oci. 49 | add_oci_helper() { 50 | if ! shared_extension "$ext"; then 51 | status='Installed and enabled' 52 | read -r "${ext}_LINUX_LIBS" <<< "libaio-dev" 53 | read -r "${ext}_CONFIGURE_OPTS" <<< "--with-php-config=$(command -v php-config) --with-${ext/_/-}=instantclient,$oracle_client" 54 | patch_phpize 55 | if [[ $(printf "%s\n%s" "${version:?}" "8.3" | sort -V | head -n1) != "$version" ]]; then 56 | add_extension_from_source "$ext" https://github.com php pecl-database-"$ext" main extension get 57 | else 58 | read -r "${ext}_PATH" <<< "ext/$ext" 59 | add_extension_from_source "$ext" https://github.com php php-src "$(php_src_tag)" extension get 60 | fi 61 | restore_phpize 62 | else 63 | enable_extension "$ext" extension 64 | fi 65 | } 66 | 67 | # Function to add oci extension oci8 and pdo_oci. 68 | add_oci() { 69 | ext=$1 70 | status='Enabled' 71 | oracle_home='/opt/oracle' 72 | oracle_client=$oracle_home/instantclient 73 | os=$(uname -s) 74 | add_client >/dev/null 2>&1 75 | add_oci_helper >/dev/null 2>&1 76 | add_extension_log "$ext" "$status" 77 | check_extension "$ext" && add_license_log 78 | } 79 | 80 | # shellcheck source=. 81 | . "${scripts:?}"/extensions/patches/phpize.sh 82 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at contact@shivammathur.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: 6 | - master 7 | - develop 8 | - verbose 9 | paths-ignore: 10 | - '**.md' 11 | - 'examples/**' 12 | push: 13 | branches: 14 | - master 15 | - develop 16 | - verbose 17 | paths-ignore: 18 | - '**.md' 19 | - 'examples/**' 20 | permissions: 21 | contents: read 22 | env: 23 | default-php-version: '8.2' 24 | jobs: 25 | run: 26 | name: Run 27 | runs-on: ${{ matrix.operating-system }} 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | operating-system: [ubuntu-24.04, ubuntu-22.04, ubuntu-20.04, windows-2022, macos-13] 32 | php-versions: ['5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] 33 | include: 34 | - operating-system: ubuntu-24.04 35 | php-versions: '' 36 | php-version-file: 'php-version-file' 37 | env: 38 | extensions: xml, opcache, xdebug, pcov, gd 39 | key: cache-v5 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | 44 | - name: Setup cache environment 45 | id: cache-env 46 | uses: shivammathur/cache-extensions@fd4374f6cc3a4747e191fcbad130788189842c2b # develop/master 47 | with: 48 | php-version: ${{ matrix.php-versions || env.default-php-version }} 49 | extensions: ${{ env.extensions }} 50 | key: ${{ env.key }} 51 | 52 | - name: Cache extensions 53 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 54 | with: 55 | path: ${{ steps.cache-env.outputs.dir }} 56 | key: ${{ steps.cache-env.outputs.key }} 57 | restore-keys: ${{ steps.cache-env.outputs.key }} 58 | 59 | - name: Stage php-version-file 60 | if: ${{ matrix.php-version-file == 'php-version-file' }} 61 | run: | 62 | echo ${{ env.default-php-version }} > php-version-file 63 | 64 | - name: Setup PHP with extensions and custom config 65 | run: node dist/index.js 66 | env: 67 | php-version: ${{ matrix.php-versions }} 68 | php-version-file: ${{ matrix.php-version-file }} 69 | extensions: ${{ env.extensions }} 70 | ini-values: post_max_size=256M, short_open_tag=On, date.timezone=Asia/Kolkata 71 | 72 | - name: Testing PHP version 73 | run: | 74 | php -v 75 | php -r "if(strpos(phpversion(), '${{ matrix.php-versions || env.default-php-version }}') === false) {throw new Exception('Wrong PHP version Installed');}" 76 | 77 | - name: Testing Composer version 78 | run: | 79 | composer -V 80 | php -r "if(strpos(@exec('composer -V'), 'Composer version') === false) {throw new Exception('Composer not found');}" 81 | - name: Testing Extensions 82 | run: | 83 | php -m 84 | php -r "if(! extension_loaded('gd')) {throw new Exception('gd not found');}" 85 | php -r "if(! extension_loaded('xml')) {throw new Exception('xml not found');}" 86 | php -r "if(! extension_loaded('Xdebug')) {throw new Exception('Xdebug not found');}" 87 | php -r "if(phpversion()>=7.1 && ! extension_loaded('pcov')) {throw new Exception('PCOV not found');}" 88 | - name: Testing ini values 89 | run: | 90 | php -r "if(ini_get('memory_limit')!='-1') {throw new Exception('memory_limit not disabled');}" 91 | php -r "if(ini_get('post_max_size')!='256M') {throw new Exception('post_max_size not added');}" 92 | php -r "if(ini_get('short_open_tag')!=1) {throw new Exception('short_open_tag not added');}" 93 | php -r "if(ini_get('date.timezone')!='Asia/Kolkata') {throw new Exception('date.timezone not added');}" 94 | -------------------------------------------------------------------------------- /examples/cakephp.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for CakePHP 2 | # Tested with https://github.com/cakephp/app 3 | name: Testing CakePHP 4 | on: [push, pull_request] 5 | jobs: 6 | tests: 7 | strategy: 8 | matrix: 9 | operating-system: [ubuntu-latest, windows-latest, macos-latest] 10 | php-versions: ['7.4', '8.0', '8.1'] 11 | runs-on: ${{ matrix.operating-system }} 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | # Docs: https://github.com/shivammathur/setup-php 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php-versions }} 21 | extensions: mbstring, intl, pdo_sqlite, pdo_mysql 22 | coverage: pcov 23 | 24 | - name: Get composer cache directory 25 | id: composer-cache 26 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 27 | 28 | - name: Cache composer dependencies 29 | uses: actions/cache@v3 30 | with: 31 | path: ${{ steps.composer-cache.outputs.dir }} 32 | # Use composer.json for key, if composer.lock is not committed. 33 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 34 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 35 | restore-keys: ${{ runner.os }}-composer- 36 | 37 | - name: Install dependencies 38 | run: | 39 | composer install --no-progress --prefer-dist --optimize-autoloader 40 | composer run-script post-install-cmd 41 | 42 | - name: Test with phpunit 43 | run: vendor/bin/phpunit --coverage-text 44 | 45 | coding-standard: 46 | name: Coding Standard 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v4 51 | 52 | # Docs: https://github.com/shivammathur/setup-php 53 | - name: Setup PHP 54 | uses: shivammathur/setup-php@v2 55 | with: 56 | php-version: '8.1' 57 | extensions: mbstring, intl 58 | - name: Get composer cache directory 59 | id: composer-cache 60 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 61 | 62 | - name: Cache composer dependencies 63 | uses: actions/cache@v3 64 | with: 65 | path: ${{ steps.composer-cache.outputs.dir }} 66 | # Use composer.json for key, if composer.lock is not committed. 67 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 68 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 69 | restore-keys: ${{ runner.os }}-composer- 70 | 71 | - name: Install dependencies 72 | run: composer install --no-progress --prefer-dist --optimize-autoloader 73 | 74 | - name: PHP CodeSniffer 75 | run: composer cs-check 76 | 77 | static-analysis: 78 | name: Static Analysis 79 | runs-on: ubuntu-latest 80 | steps: 81 | - name: Checkout 82 | uses: actions/checkout@v4 83 | 84 | # Docs: https://github.com/shivammathur/setup-php 85 | - name: Setup PHP 86 | uses: shivammathur/setup-php@v2 87 | with: 88 | php-version: '8.1' 89 | extensions: mbstring, intl 90 | tools: phpstan 91 | 92 | - name: Get composer cache directory 93 | id: composer-cache 94 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 95 | 96 | - name: Cache composer dependencies 97 | uses: actions/cache@v3 98 | with: 99 | path: ${{ steps.composer-cache.outputs.dir }} 100 | # Use composer.json for key, if composer.lock is not committed. 101 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 102 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 103 | restore-keys: ${{ runner.os }}-composer- 104 | 105 | - name: Install dependencies 106 | run: composer install --no-progress --prefer-dist --optimize-autoloader 107 | 108 | - name: Static Analysis using PHPStan 109 | run: phpstan analyse --no-progress src/ 110 | -------------------------------------------------------------------------------- /src/scripts/extensions/http.sh: -------------------------------------------------------------------------------- 1 | # Function to get http version for a PHP version. 2 | get_http_version() { 3 | if [[ ${version:?} =~ 5.[3-6] ]]; then 4 | echo "pecl_http-2.6.0" 5 | elif [[ ${version:?} =~ 7.[0-4] ]]; then 6 | echo "pecl_http-3.2.4" 7 | else 8 | echo "pecl_http-$(get_pecl_version "pecl_http" "stable")" 9 | fi 10 | } 11 | 12 | # Function to enable http extension. 13 | enable_http() { 14 | enable_extension iconv extension 15 | enable_extension propro extension 16 | enable_extension raphf extension 17 | if (! [[ ${version:?} =~ ${jit_versions:?} ]] && check_extension iconv && check_extension propro && check_extension raphf) || 18 | ( [[ ${version:?} =~ ${jit_versions:?} ]] && check_extension iconv && check_extension raphf); then 19 | enable_extension http extension 20 | fi 21 | } 22 | 23 | # Function to install http dependencies. 24 | add_http_dependencies() { 25 | if [[ ${version:?} =~ ${old_versions:?} ]]; then 26 | add_pecl_extension raphf 1.1.2 extension 27 | add_pecl_extension propro 1.0.2 extension 28 | elif [[ ${version:?} =~ 5.6|7.[0-4] ]]; then 29 | add_extension iconv extension 30 | add_extension propro extension 31 | add_extension raphf extension 32 | else 33 | add_extension iconv extension 34 | add_extension raphf extension 35 | fi 36 | } 37 | 38 | # Function to get configure options for http. 39 | get_http_configure_opts() { 40 | if [ "$os" = 'Linux' ]; then 41 | for lib in zlib libbrotli libcurl libevent libicu libidn2 libidn libidnkit2 libidnkit; do 42 | http_opts+=( "--with-http-$lib-dir=/usr" ) 43 | done 44 | else 45 | http_opts+=( "--with-http-zlib-dir=$(xcrun --show-sdk-path)/usr" ) 46 | http_opts+=( "--with-http-libbrotli-dir=$(brew --prefix brotli)" ) 47 | http_opts+=( "--with-http-libcurl-dir=$(brew --prefix curl)" ) 48 | http_opts+=( "--with-http-libicu-dir=$(brew --prefix icu4c)" ) 49 | http_opts+=( "--with-http-libevent-dir=$(brew --prefix libevent)" ) 50 | http_opts+=( "--with-http-libidn2-dir=$(brew --prefix libidn2)" ) 51 | fi 52 | } 53 | 54 | # Compile and install http explicitly. 55 | # This is done as pecl compiles raphf and propro as well. 56 | add_http_helper() { 57 | ext=$1 58 | http_opts=() && get_http_configure_opts 59 | export HTTP_PREFIX_CONFIGURE_OPTS="CFLAGS=-Wno-implicit-function-declaration" 60 | http_configure_opts="--with-http --with-php-config=$(command -v php-config) ${http_opts[*]}" 61 | export HTTP_CONFIGURE_OPTS="$http_configure_opts" 62 | export HTTP_LINUX_LIBS="zlib1g libbrotli-dev libcurl4-openssl-dev libevent-dev libicu-dev libidn2-dev" 63 | export HTTP_DARWIN_LIBS="brotli curl icu4c libevent libidn2" 64 | if [[ "${version:?}" =~ ${nightly_versions:?} ]]; then 65 | add_extension_from_source http https://github.com m6w6 ext-http master extension 66 | else 67 | add_extension_from_source pecl_http https://pecl.php.net http http "${ext##*-}" extension pecl 68 | fi 69 | } 70 | 71 | # Function to setup latest http extension. 72 | add_http_latest() { 73 | enable_http 74 | if ! check_extension http; then 75 | if [ "$os" = "Linux" ]; then 76 | add_http_dependencies 77 | package="php$version-http" 78 | add_ppa ondrej/php >/dev/null 2>&1 || update_ppa ondrej/php 79 | (check_package "$package" && install_packages "$package") || add_http_helper "$(get_http_version)" "$os" 80 | else 81 | if ! [[ "${version:?}" =~ ${old_versions:?} ]]; then 82 | add_brew_extension pecl_http extension 83 | fi 84 | fi 85 | status="Installed and enabled" 86 | fi 87 | } 88 | 89 | # Function to setup http extension given a version. 90 | add_http_version() { 91 | ext=$1 92 | enable_http 93 | if [ "x$(php -r "echo phpversion('http');")" != "x${ext##*-}" ]; then 94 | add_http_dependencies 95 | disable_extension_helper http >/dev/null 96 | add_http_helper pecl_http-"${ext##*-}" "$os" 97 | status="Installed and enabled" 98 | fi 99 | } 100 | 101 | # Function to setup http extension 102 | add_http() { 103 | ext=$1 104 | status="Enabled" 105 | if [[ "$ext" =~ ^(pecl_http|http)$ ]]; then 106 | add_http_latest >/dev/null 2>&1 107 | else 108 | add_http_version "$ext" >/dev/null 2>&1 109 | fi 110 | add_extension_log "http" "$status" 111 | } 112 | 113 | os="$(uname -s)" 114 | -------------------------------------------------------------------------------- /src/coverage.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './utils'; 2 | import * as extensions from './extensions'; 3 | import * as config from './config'; 4 | 5 | export async function checkXdebugError( 6 | extension: string, 7 | version: string 8 | ): Promise { 9 | if ( 10 | (/^5\.[3-6]$|^7\.[0-1]$/.test(version) && extension == 'xdebug3') || 11 | (/^8\.[0-9]$/.test(version) && extension == 'xdebug2') 12 | ) { 13 | return extension + ' is not supported on PHP ' + version; 14 | } 15 | return ''; 16 | } 17 | 18 | /** 19 | * Function to set up Xdebug 20 | * 21 | * @param extension 22 | * @param version 23 | * @param os 24 | * @param pipe 25 | */ 26 | export async function addCoverageXdebug( 27 | extension: string, 28 | version: string, 29 | os: string, 30 | pipe: string 31 | ): Promise { 32 | let script = '\n'; 33 | const error: string = await checkXdebugError(extension, version); 34 | if (!error) { 35 | script += 36 | (await extensions.addExtension(':pcov:false', version, os, true)) + pipe; 37 | extension = extension == 'xdebug3' ? 'xdebug' : extension; 38 | script += 39 | (await extensions.addExtension(extension, version, os, true)) + pipe; 40 | script += await utils.setVariable( 41 | 'xdebug_version', 42 | 'php -r "echo phpversion(\'xdebug\');"', 43 | os 44 | ); 45 | script += 46 | (await utils.getCommand(os, 'extension_log')) + 47 | 'xdebug "Xdebug $xdebug_version enabled as coverage driver"'; 48 | } else { 49 | script += await utils.addLog('$cross', extension, error, os); 50 | } 51 | return script; 52 | } 53 | 54 | /** 55 | * Function to set up PCOV 56 | * 57 | * @param version 58 | * @param os 59 | * @param pipe 60 | */ 61 | export async function addCoveragePCOV( 62 | version: string, 63 | os: string, 64 | pipe: string 65 | ): Promise { 66 | let script = '\n'; 67 | switch (true) { 68 | default: 69 | script += 70 | (await extensions.addExtension(':xdebug:false', version, os, true)) + 71 | pipe; 72 | script += 73 | (await extensions.addExtension('pcov', version, os, true)) + pipe; 74 | script += (await config.addINIValues('pcov.enabled=1', os, true)) + '\n'; 75 | script += await utils.setVariable( 76 | 'pcov_version', 77 | 'php -r "echo phpversion(\'pcov\');"', 78 | os 79 | ); 80 | script += 81 | (await utils.getCommand(os, 'extension_log')) + 82 | 'pcov "PCOV $pcov_version enabled as coverage driver"'; 83 | break; 84 | 85 | case /5\.[3-6]|7\.0/.test(version): 86 | script += await utils.addLog( 87 | '$cross', 88 | 'pcov', 89 | 'PHP 7.1 or newer is required', 90 | os 91 | ); 92 | break; 93 | } 94 | 95 | return script; 96 | } 97 | 98 | /** 99 | * Function to disable Xdebug and PCOV 100 | * 101 | * @param version 102 | * @param os 103 | * @param pipe 104 | */ 105 | export async function disableCoverage( 106 | version: string, 107 | os: string, 108 | pipe: string 109 | ): Promise { 110 | let script = '\n'; 111 | script += 112 | (await extensions.addExtension(':pcov:false', version, os, true)) + pipe; 113 | script += 114 | (await extensions.addExtension(':xdebug:false', version, os, true)) + pipe; 115 | script += await utils.addLog('$tick', 'none', 'Disabled Xdebug and PCOV', os); 116 | 117 | return script; 118 | } 119 | 120 | /** 121 | * Function to set coverage driver 122 | * 123 | * @param coverage_driver 124 | * @param version 125 | * @param os 126 | */ 127 | export async function addCoverage( 128 | coverage_driver: string, 129 | version: string, 130 | os: string 131 | ): Promise { 132 | coverage_driver = coverage_driver.toLowerCase(); 133 | const script: string = '\n' + (await utils.stepLog('Setup Coverage', os)); 134 | const pipe: string = (await utils.suppressOutput(os)) + '\n'; 135 | switch (coverage_driver) { 136 | case 'pcov': 137 | return script + (await addCoveragePCOV(version, os, pipe)); 138 | case 'xdebug': 139 | case 'xdebug2': 140 | case 'xdebug3': 141 | return ( 142 | script + (await addCoverageXdebug(coverage_driver, version, os, pipe)) 143 | ); 144 | case 'none': 145 | return script + (await disableCoverage(version, os, pipe)); 146 | default: 147 | return ''; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/configs/tools_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://raw.githubusercontent.com/shivammathur/setup-php/develop/src/configs/tools_schema.json", 4 | "type": "object", 5 | "title": "Tools", 6 | "default": {}, 7 | "examples": [ 8 | { 9 | "tool": { 10 | "alias": "tool_alias", 11 | "domain": "https://example.com", 12 | "extension": ".ext", 13 | "fetch_latest": "true", 14 | "function": "function_name", 15 | "repository": "user/tool", 16 | "packagist": "user/tool", 17 | "scope": "global, scoped", 18 | "type": "phar, composer, custom-package or custom-function", 19 | "version_parameter": "--version", 20 | "version_prefix": "v" 21 | } 22 | } 23 | ], 24 | "items": { 25 | "properties": { 26 | "alias": { 27 | "$id": "#/items/properties/alias", 28 | "type": "string", 29 | "title": "The alias schema", 30 | "description": "Alias for a tool.", 31 | "examples": [ 32 | "tool_alias" 33 | ] 34 | }, 35 | "domain": { 36 | "$id": "#/items/properties/domain", 37 | "type": "string", 38 | "title": "The domain schema", 39 | "description": "Domain URL of the tool.", 40 | "examples": [ 41 | "https://example.com" 42 | ] 43 | }, 44 | "extension": { 45 | "$id": "#/items/properties/extension", 46 | "type": "string", 47 | "title": "The extension schema", 48 | "description": "File extension of the tool.", 49 | "examples": [ 50 | ".ext" 51 | ] 52 | }, 53 | "fetch_latest": { 54 | "$id": "#/items/properties/fetch_latest", 55 | "type": "string", 56 | "title": "The fetch_latest schema", 57 | "description": "Fetch the latest version from GitHub releases.", 58 | "enum": [ 59 | "true", 60 | "false" 61 | ] 62 | }, 63 | "function": { 64 | "$id": "#/items/properties/function", 65 | "type": "string", 66 | "title": "The function schema", 67 | "description": "Function name in tools.ts which returns the script to setup the tool.", 68 | "examples": [ 69 | "function_name" 70 | ] 71 | }, 72 | "repository": { 73 | "$id": "#/items/properties/repository", 74 | "type": "string", 75 | "title": "The repository schema", 76 | "description": "GitHub repository of the tool.", 77 | "examples": [ 78 | "user/tool" 79 | ] 80 | }, 81 | "packagist": { 82 | "$id": "#/items/properties/packagist", 83 | "type": "string", 84 | "title": "The repository schema", 85 | "description": "Packagist repository of the tool in case different from repository.", 86 | "examples": [ 87 | "user/tool" 88 | ] 89 | }, 90 | "scope": { 91 | "$id": "#/items/properties/scope", 92 | "type": "string", 93 | "title": "The scope schema", 94 | "description": "Scope of tool installation: global or scoped", 95 | "enum": [ 96 | "global", 97 | "scoped" 98 | ] 99 | }, 100 | "type": { 101 | "$id": "#/items/properties/type", 102 | "type": "string", 103 | "title": "The type schema", 104 | "description": "Type of tool: phar, composer, custom-package or custom-function.", 105 | "enum": [ 106 | "phar", 107 | "composer", 108 | "custom-package", 109 | "custom-function" 110 | ] 111 | }, 112 | "version_parameter": { 113 | "$id": "#/items/properties/version_parameter", 114 | "type": "string", 115 | "title": "The version_parameter schema", 116 | "description": "Parameter to get the tool version.", 117 | "examples": [ 118 | "--version" 119 | ] 120 | }, 121 | "version_prefix": { 122 | "$id": "#/items/properties/version_prefix", 123 | "type": "string", 124 | "title": "The version_prefix schema", 125 | "description": "Prefix of the version in the download URL.", 126 | "examples": [ 127 | "v" 128 | ] 129 | } 130 | }, 131 | "required": [ 132 | "type" 133 | ], 134 | "additionalProperties": true 135 | } 136 | } -------------------------------------------------------------------------------- /src/scripts/extensions/extension_map.php: -------------------------------------------------------------------------------- 1 | extension_dir = ini_get('extension_dir'); 26 | $this->file_extension = (PHP_OS == 'WINNT' ? '.dll' : '.so'); 27 | $this->file_prefix = (PHP_OS == 'WINNT' ? 'php_' : ''); 28 | $this->map = array(); 29 | } 30 | 31 | /** 32 | * Function to read the extension map. 33 | */ 34 | private function parseMap($path) { 35 | if(file_exists($path)) { 36 | $handle = fopen($path, "r"); 37 | if ($handle) { 38 | while (($line = fgets($handle)) !== false) { 39 | $line_parts = explode(':', $line); 40 | $this->map[$line_parts[0]] = explode(' ', trim($line_parts[1])); 41 | } 42 | fclose($handle); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Function to check if a shared extension file exists. 49 | * 50 | * @param string $extension 51 | * @return bool 52 | */ 53 | private function checkSharedExtension($extension) { 54 | $extension_file = $this->extension_dir. DIRECTORY_SEPARATOR . $this->file_prefix . $extension . $this->file_extension; 55 | return file_exists($extension_file); 56 | } 57 | 58 | /** 59 | * Function to get all shared extensions. 60 | * 61 | * @return string[] 62 | */ 63 | private function getSharedExtensions() { 64 | $files = scandir($this->extension_dir); 65 | $extensions = array_diff($files, array('.','..')); 66 | $filter_pattern = "/$this->file_extension|$this->file_prefix/"; 67 | return array_map(function ($extension) use($filter_pattern) { 68 | return preg_replace($filter_pattern, '', $extension); 69 | }, $extensions); 70 | } 71 | 72 | /** 73 | * Function to patch dependencies if there are any bugs in Reflection data. 74 | * 75 | * @param string $extension 76 | * @param array $dependencies 77 | * @return array 78 | */ 79 | private function patchDependencies($extension, $dependencies) { 80 | // memcached 2.2.0 has no dependencies in reflection data. 81 | if($extension == 'memcached') { 82 | $dependencies = array_unique(array_merge($dependencies, array('igbinary', 'json', 'msgpack'))); 83 | } 84 | return $dependencies; 85 | } 86 | 87 | /** 88 | * Function to add extension to the map. 89 | * 90 | * @param string $extension 91 | * @throws ReflectionException 92 | */ 93 | private function addExtensionToMap($extension) { 94 | if($this->map && array_key_exists($extension, $this->map) && !empty($this->map[$extension])) { 95 | return; 96 | } 97 | // PHP 5.3 does not allow using $this. 98 | $self = $this; 99 | 100 | $ref = new ReflectionExtension($extension); 101 | $dependencies = array_keys(array_map('strtolower', $ref->getDependencies())); 102 | $dependencies = $this->patchDependencies($extension, $dependencies); 103 | $dependencies = array_filter($dependencies, function ($dependency) use ($self) { 104 | return $self->checkSharedExtension($dependency); 105 | }); 106 | $self->map[$extension] = $dependencies; 107 | } 108 | 109 | /** 110 | * Function to write the map of shared extensions and their dependent extensions. 111 | */ 112 | public function write() { 113 | $path = $_SERVER['argv'][1]; 114 | $this->parseMap($path); 115 | $extensions = array_map('strtolower', $this->getSharedExtensions()); 116 | foreach ($extensions as $extension) { 117 | try { 118 | $this->addExtensionToMap($extension); 119 | } catch (ReflectionException $e) { 120 | 121 | } 122 | } 123 | $map_string = ''; 124 | foreach($this->map as $extension => $dependencies) { 125 | $map_string .= $extension . ': ' . implode(' ', $dependencies) . PHP_EOL; 126 | } 127 | file_put_contents($path, $map_string); 128 | } 129 | } 130 | 131 | $extension_map = new ExtensionMap(); 132 | $extension_map->write(); 133 | -------------------------------------------------------------------------------- /__tests__/install.test.ts: -------------------------------------------------------------------------------- 1 | import * as install from '../src/install'; 2 | import * as utils from '../src/utils'; 3 | 4 | /** 5 | * Mock install.ts 6 | */ 7 | jest.mock('../src/install', () => ({ 8 | getScript: jest 9 | .fn() 10 | .mockImplementation(async (os: string): Promise => { 11 | const filename = os + (await utils.scriptExtension(os)); 12 | const version: string = await utils.parseVersion( 13 | await utils.readPHPVersion() 14 | ); 15 | const ini_file: string = await utils.parseIniFile( 16 | await utils.getInput('ini-file', false) 17 | ); 18 | const extension_csv: string = process.env['extensions'] || ''; 19 | const ini_values_csv: string = process.env['ini-values'] || ''; 20 | const coverage_driver: string = process.env['coverage'] || ''; 21 | const tools_csv: string = process.env['tools'] || ''; 22 | let script = await utils.joins(filename, version, ini_file); 23 | script += extension_csv ? ' install extensions' : ''; 24 | script += tools_csv ? ' add_tool' : ''; 25 | script += coverage_driver ? ' set coverage driver' : ''; 26 | script += ini_values_csv ? ' edit php.ini' : ''; 27 | return script; 28 | }), 29 | run: jest.fn().mockImplementation(async (): Promise => { 30 | const os: string = process.env['RUNNER_OS'] || ''; 31 | const tool = await utils.scriptTool(os); 32 | return tool + (await install.getScript(os)); 33 | }) 34 | })); 35 | 36 | /** 37 | * Mock fetch.ts 38 | */ 39 | jest.mock('../src/fetch', () => ({ 40 | fetch: jest.fn().mockImplementation(() => { 41 | return { 42 | data: '{ "latest": "8.3", "lowest": "8.1", "highest": "8.3", "nightly": "8.4", "5.x": "5.6" }' 43 | }; 44 | }) 45 | })); 46 | 47 | describe('Install', () => { 48 | it.each` 49 | version | os | extension_csv | ini_file | ini_values_csv | coverage_driver | tools | output 50 | ${'7.3'} | ${'darwin'} | ${''} | ${'production'} | ${''} | ${''} | ${''} | ${'bash darwin.sh 7.3 production'} 51 | ${'7.3'} | ${'darwin'} | ${'a, b'} | ${'development'} | ${'a=b'} | ${'x'} | ${''} | ${'bash darwin.sh 7.3 development install extensions set coverage driver edit php.ini'} 52 | ${'7.4.1'} | ${'darwin'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash darwin.sh 7.4 none'} 53 | ${'8'} | ${'darwin'} | ${''} | ${''} | ${''} | ${''} | ${''} | ${'bash darwin.sh 8.0 production'} 54 | ${'8.0'} | ${'darwin'} | ${''} | ${'development'} | ${''} | ${''} | ${''} | ${'bash darwin.sh 8.0 development'} 55 | ${'8.1'} | ${'darwin'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash darwin.sh 8.1 none'} 56 | ${'7.3'} | ${'linux'} | ${''} | ${'invalid'} | ${''} | ${''} | ${''} | ${'bash linux.sh 7.3 production'} 57 | ${'7.3'} | ${'linux'} | ${'a, b'} | ${'development'} | ${'a=b'} | ${'x'} | ${'phpunit'} | ${'bash linux.sh 7.3 development install extensions add_tool set coverage driver edit php.ini'} 58 | ${'latest'} | ${'linux'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash linux.sh 8.3 none'} 59 | ${'lowest'} | ${'linux'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash linux.sh 8.1 none'} 60 | ${'highest'} | ${'linux'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash linux.sh 8.3 none'} 61 | ${'nightly'} | ${'linux'} | ${''} | ${'none'} | ${''} | ${''} | ${''} | ${'bash linux.sh 8.4 none'} 62 | ${'7.0'} | ${'win32'} | ${''} | ${'production'} | ${''} | ${''} | ${''} | ${'pwsh win32.ps1 7.0 production'} 63 | ${'7.3'} | ${'win32'} | ${''} | ${'development'} | ${''} | ${''} | ${''} | ${'pwsh win32.ps1 7.3 development'} 64 | ${'7.3'} | ${'win32'} | ${'a, b'} | ${'none'} | ${'a=b'} | ${'x'} | ${''} | ${'pwsh win32.ps1 7.3 none install extensions set coverage driver edit php.ini'} 65 | `( 66 | 'Test install on $os for $version with extensions=$extension_csv, ini_values=$ini_values_csv, coverage_driver=$coverage_driver, tools=$tools', 67 | async ({ 68 | version, 69 | os, 70 | extension_csv, 71 | ini_file, 72 | ini_values_csv, 73 | coverage_driver, 74 | tools, 75 | output 76 | }) => { 77 | process.env['php-version'] = version.toString(); 78 | process.env['RUNNER_OS'] = os; 79 | process.env['extensions'] = extension_csv; 80 | process.env['ini-file'] = ini_file; 81 | process.env['ini-values'] = ini_values_csv; 82 | process.env['coverage'] = coverage_driver; 83 | process.env['tools'] = tools; 84 | expect(await install.run()).toBe(output); 85 | } 86 | ); 87 | }); 88 | -------------------------------------------------------------------------------- /src/scripts/extensions/phalcon.ps1: -------------------------------------------------------------------------------- 1 | # Function to get the url of the phalcon release asset. 2 | Function Get-PhalconReleaseAssetUrl() { 3 | Param ( 4 | [Parameter(Position = 0, Mandatory = $true)] 5 | [ValidateNotNull()] 6 | [string] 7 | $Semver 8 | ) 9 | $domain = 'https://api.github.com/repos' 10 | $releases = 'phalcon/cphalcon/releases' 11 | $match = $null 12 | if($extension_version -match '[3-4]') { 13 | $nts = if (!$installed.ThreadSafe) { "_nts" } else { "" } 14 | try { 15 | $match = (Invoke-RestMethod -Uri "$domain/$releases/tags/v$Semver").assets | Select-String -Pattern "browser_download_url=.*(phalcon_${arch}_.*_php${version}_${extension_version}.*[0-9]${nts}.zip)" 16 | } catch { } 17 | if($null -eq $match) { 18 | try { 19 | $match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(phalcon_${arch}_.*_php${version}_${extension_version}.*[0-9]${nts}.zip)" 20 | } catch { } 21 | } 22 | } else { 23 | $nts = if (!$installed.ThreadSafe) { "-nts" } else { "-ts" } 24 | try { 25 | $match = (Invoke-RestMethod -Uri "$domain/$releases/tags/v$Semver").assets | Select-String -Pattern "browser_download_url=.*(php_phalcon-php${version}${nts}-windows.*-x64.zip)" 26 | } catch { } 27 | if($null -eq $match) { 28 | try { 29 | $match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(php_phalcon-php${version}${nts}-windows.*-x64.zip)" 30 | } catch { } 31 | } 32 | if($null -eq $match) { 33 | try { 34 | $match = (Invoke-RestMethod -Uri "$domain/$releases/tags/v$Semver").assets | Select-String -Pattern "browser_download_url=.*(phalcon-php${version}${nts}-windows.*-x64.zip)" 35 | } catch { } 36 | } 37 | if($null -eq $match) { 38 | try { 39 | $match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(phalcon-php${version}${nts}-windows.*-x64.zip)" 40 | } catch { } 41 | } 42 | } 43 | if($NULL -ne $match) { 44 | return "$github/$releases/download/v$Semver/$($match.Matches[0].Groups[1].Value)" 45 | } 46 | return false; 47 | } 48 | 49 | # Function to add phalcon using GitHub releases. 50 | Function Add-PhalconFromGitHub() { 51 | Param ( 52 | [Parameter(Position = 0, Mandatory = $true)] 53 | [ValidateNotNull()] 54 | [string] 55 | $Semver 56 | ) 57 | $zip_url = Get-PhalconReleaseAssetUrl $Semver 58 | if($zip_url) { 59 | Get-File -Url $zip_url -OutFile $ENV:RUNNER_TOOL_CACHE\phalcon.zip > $null 2>&1 60 | Expand-Archive -Path $ENV:RUNNER_TOOL_CACHE\phalcon.zip -DestinationPath $ENV:RUNNER_TOOL_CACHE\phalcon -Force > $null 2>&1 61 | Copy-Item -Path "$ENV:RUNNER_TOOL_CACHE\phalcon\php_phalcon.dll" -Destination "$ext_dir\php_phalcon.dll" 62 | Enable-PhpExtension -Extension phalcon -Path $php_dir 63 | } else { 64 | throw "Unable to get Phalcon release from the GitHub release" 65 | } 66 | } 67 | 68 | # Function to get phalcon semver. 69 | Function Get-PhalconSemver() { 70 | if($extension_version -eq '3') { 71 | return '3.4.5' 72 | } elseif (($extension_version -eq '4') -and ($version -eq '7.2')) { 73 | return '4.1.0' 74 | } elseif (($extension_version -eq '5') -and ($version -eq '7.4')) { 75 | return '5.4.0' 76 | } 77 | return Get-PeclPackageVersion phalcon $extension_version stable stable | Select-Object -First 1 78 | } 79 | 80 | # Function to install phalcon 81 | Function Add-PhalconHelper() { 82 | $semver = Get-PhalconSemver 83 | if (($extension_version -eq '3') -or ($extension_version -eq '5')) { 84 | Add-PhalconFromGitHub $semver 85 | } elseif ($extension_version -eq '4') { 86 | Add-Extension -Extension phalcon -Stability stable -Extension_version $semver 87 | } 88 | } 89 | 90 | # Function to add phalcon 91 | Function Add-Phalcon() { 92 | Param ( 93 | [Parameter(Position = 0, Mandatory = $true)] 94 | [ValidateNotNull()] 95 | [ValidateSet('phalcon3', 'phalcon4', 'phalcon5')] 96 | [string] 97 | $extension 98 | ) 99 | try { 100 | $status = 'Enabled' 101 | $extension_version = $extension.substring($extension.Length - 1) 102 | 103 | if($extension_version -eq '4') { 104 | if (Test-Path $ext_dir\php_psr.dll) { 105 | Enable-PhpExtension -Extension psr -Path $php_dir 106 | } else { 107 | Install-Phpextension -Extension psr -MinimumStability stable -Path $php_dir 108 | } 109 | } 110 | 111 | if(Test-Path $ext_dir\php_phalcon.dll) { 112 | $phalcon = Get-PhpExtension $ext_dir\php_phalcon.dll 113 | if($phalcon.Version[0] -eq $extension_version) { 114 | Enable-PhpExtension -Extension phalcon -Path $php_dir 115 | } else { 116 | $status = 'Installed and enabled' 117 | Remove-Item $ext_dir\php_phalcon.dll 118 | Add-PhalconHelper 119 | } 120 | } else { 121 | $status = 'Installed and enabled' 122 | Add-PhalconHelper 123 | } 124 | Add-Log $tick $extension $status 125 | } catch [Exception] { 126 | Add-Log $cross $extension "Could not install $extension on PHP $($installed.FullVersion)" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/cakephp-postgres.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for CakePHP with PostgreSQL and Redis 2 | # Tested with https://github.com/cakephp/app 3 | name: Testing CakePHP with PostgreSQL 4 | on: [push, pull_request] 5 | jobs: 6 | tests: 7 | strategy: 8 | matrix: 9 | php-versions: ['7.4', '8.0', '8.1'] 10 | runs-on: ubuntu-latest 11 | 12 | # Docs: https://docs.github.com/en/actions/using-containerized-services 13 | services: 14 | postgres: 15 | image: postgres:latest 16 | env: 17 | POSTGRES_USER: postgres 18 | POSTGRES_PASSWORD: postgres 19 | POSTGRES_DB: postgres 20 | ports: 21 | - 5432/tcp 22 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 23 | 24 | redis: 25 | image: redis 26 | ports: 27 | - 6379/tcp 28 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | # Docs: https://github.com/shivammathur/setup-php 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php-versions }} 38 | # You can also use ext-apcu or ext-memcached instead of ext-redis 39 | # Install memcached if using ext-memcached 40 | extensions: mbstring, intl, redis, pdo_pgsql 41 | coverage: pcov 42 | 43 | # Local PostgreSQL service in GitHub hosted environments is disabled by default. 44 | # If you are using it instead of service containers, make sure you start it. 45 | # - name: Start postgresql service 46 | # run: sudo systemctl start postgresql.service 47 | 48 | - name: Get composer cache directory 49 | id: composer-cache 50 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 51 | 52 | - name: Cache composer dependencies 53 | uses: actions/cache@v3 54 | with: 55 | path: ${{ steps.composer-cache.outputs.dir }} 56 | # Use composer.json for key, if composer.lock is not committed. 57 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 58 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 59 | restore-keys: ${{ runner.os }}-composer- 60 | 61 | - name: Install dependencies 62 | run: | 63 | composer install --no-progress --prefer-dist --optimize-autoloader 64 | composer run-script post-install-cmd 65 | 66 | # Add a step to run migrations if required 67 | - name: Test with phpunit 68 | run: vendor/bin/phpunit --coverage-text 69 | env: 70 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 71 | DB_DSN: postgres://postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres 72 | 73 | coding-standard: 74 | name: Coding Standard 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v4 79 | 80 | # Docs: https://github.com/shivammathur/setup-php 81 | - name: Setup PHP 82 | uses: shivammathur/setup-php@v2 83 | with: 84 | php-version: '8.1' 85 | extensions: mbstring, intl 86 | 87 | - name: Get composer cache directory 88 | id: composer-cache 89 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 90 | 91 | - name: Cache composer dependencies 92 | uses: actions/cache@v3 93 | with: 94 | path: ${{ steps.composer-cache.outputs.dir }} 95 | # Use composer.json for key, if composer.lock is not committed. 96 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 97 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 98 | restore-keys: ${{ runner.os }}-composer- 99 | 100 | - name: Install dependencies 101 | run: composer install --no-progress --prefer-dist --optimize-autoloader 102 | 103 | - name: PHP CodeSniffer 104 | run: composer cs-check 105 | 106 | static-analysis: 107 | name: Static Analysis 108 | runs-on: ubuntu-latest 109 | steps: 110 | - name: Checkout 111 | uses: actions/checkout@v4 112 | 113 | # Docs: https://github.com/shivammathur/setup-php 114 | - name: Setup PHP 115 | uses: shivammathur/setup-php@v2 116 | with: 117 | php-version: '8.1' 118 | extensions: mbstring, intl 119 | tools: phpstan 120 | 121 | - name: Get composer cache directory 122 | id: composer-cache 123 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 124 | 125 | - name: Cache composer dependencies 126 | uses: actions/cache@v3 127 | with: 128 | path: ${{ steps.composer-cache.outputs.dir }} 129 | # Use composer.json for key, if composer.lock is not committed. 130 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 131 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 132 | restore-keys: ${{ runner.os }}-composer- 133 | 134 | - name: Install dependencies 135 | run: composer install --no-progress --prefer-dist --optimize-autoloader 136 | 137 | - name: Static Analysis using PHPStan 138 | run: phpstan analyse --no-progress src/ 139 | -------------------------------------------------------------------------------- /examples/cakephp-mysql.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for CakePHP with MySQL and Redis 2 | # Tested with https://github.com/cakephp/app 3 | name: Testing CakePHP with MySQL 4 | on: [push, pull_request] 5 | jobs: 6 | tests: 7 | strategy: 8 | matrix: 9 | php-versions: ['7.4', '8.0', '8.1'] 10 | runs-on: ubuntu-latest 11 | 12 | # Docs: https://docs.github.com/en/actions/using-containerized-services 13 | services: 14 | mysql: 15 | image: mysql:latest 16 | env: 17 | MYSQL_ALLOW_EMPTY_PASSWORD: false 18 | MYSQL_ROOT_PASSWORD: password 19 | MYSQL_DATABASE: cakephp 20 | ports: 21 | - 3306/tcp 22 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 23 | 24 | redis: 25 | image: redis 26 | ports: 27 | - 6379/tcp 28 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | # Docs: https://github.com/shivammathur/setup-php 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php-versions }} 38 | # You can also use ext-apcu or ext-memcached instead of ext-redis 39 | # Install memcached if using ext-memcached 40 | extensions: mbstring, intl, redis, pdo_mysql 41 | coverage: pcov 42 | 43 | # Local MySQL service in GitHub hosted environments is disabled by default. 44 | # If you are using it instead of service containers, make sure you start it. 45 | # - name: Start mysql service 46 | # run: sudo systemctl start mysql.service 47 | 48 | - name: Get composer cache directory 49 | id: composer-cache 50 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 51 | 52 | - name: Cache composer dependencies 53 | uses: actions/cache@v3 54 | with: 55 | path: ${{ steps.composer-cache.outputs.dir }} 56 | # Use composer.json for key, if composer.lock is not committed. 57 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 58 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 59 | restore-keys: ${{ runner.os }}-composer- 60 | 61 | - name: Install dependencies 62 | run: | 63 | composer install --no-progress --prefer-dist --optimize-autoloader 64 | composer run-script post-install-cmd 65 | 66 | # Add a step to run migrations if required 67 | - name: Test with phpunit 68 | run: vendor/bin/phpunit --coverage-text 69 | env: 70 | REDIS_PORT: ${{ job.services.redis.ports['6379'] }} 71 | DB_DSN: "mysql://root:password@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/cakephp?init[]=SET sql_mode = \"STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION\"" 72 | 73 | coding-standard: 74 | name: Coding Standard 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v4 79 | 80 | # Docs: https://github.com/shivammathur/setup-php 81 | - name: Setup PHP 82 | uses: shivammathur/setup-php@v2 83 | with: 84 | php-version: '8.1' 85 | extensions: mbstring, intl 86 | 87 | - name: Get composer cache directory 88 | id: composer-cache 89 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 90 | 91 | - name: Cache composer dependencies 92 | uses: actions/cache@v3 93 | with: 94 | path: ${{ steps.composer-cache.outputs.dir }} 95 | # Use composer.json for key, if composer.lock is not committed. 96 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 97 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 98 | restore-keys: ${{ runner.os }}-composer- 99 | 100 | - name: Install dependencies 101 | run: composer install --no-progress --prefer-dist --optimize-autoloader 102 | 103 | - name: PHP CodeSniffer 104 | run: composer cs-check 105 | 106 | static-analysis: 107 | name: Static Analysis 108 | runs-on: ubuntu-latest 109 | steps: 110 | - name: Checkout 111 | uses: actions/checkout@v4 112 | 113 | # Docs: https://github.com/shivammathur/setup-php 114 | - name: Setup PHP 115 | uses: shivammathur/setup-php@v2 116 | with: 117 | php-version: '8.1' 118 | extensions: mbstring, intl 119 | tools: phpstan 120 | 121 | - name: Get composer cache directory 122 | id: composer-cache 123 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 124 | 125 | - name: Cache composer dependencies 126 | uses: actions/cache@v3 127 | with: 128 | path: ${{ steps.composer-cache.outputs.dir }} 129 | # Use composer.json for key, if composer.lock is not committed. 130 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 131 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 132 | restore-keys: ${{ runner.os }}-composer- 133 | 134 | - name: Install dependencies 135 | run: composer install --no-progress --prefer-dist --optimize-autoloader 136 | 137 | - name: Static Analysis using PHPStan 138 | run: phpstan analyse --no-progress src/ 139 | -------------------------------------------------------------------------------- /src/scripts/extensions/relay.sh: -------------------------------------------------------------------------------- 1 | # Get relay version 2 | get_relay_version() { 3 | local ext=$1 4 | if [[ "$ext" =~ ^relay$ ]]; then 5 | if [ "${version:?}" = "7.4" ]; then 6 | echo 'v0.7.0' 7 | else 8 | get -s -n "" "${relay_releases:?}"/latest 2<&1 | grep -m 1 -Eo "tag/(v[0-9]+(\.[0-9]+)?(\.[0-9]+)?)" | head -n 1 | cut -d '/' -f 2 9 | fi 10 | else 11 | relay_version="${ext##*-}" 12 | echo "v${relay_version/v//}" 13 | fi 14 | } 15 | 16 | # Get OS suffix in relay artifact URL. 17 | get_os_suffix() { 18 | if [ "$os" = "Linux" ]; then 19 | if [[ "$ID" =~ ubuntu|debian ]]; then 20 | echo debian 21 | elif [ "$ID" = "centos" ]; then 22 | echo centos"$VERSION_ID" 23 | else 24 | echo "$ID" 25 | fi 26 | else 27 | echo darwin 28 | fi 29 | } 30 | 31 | # Get openssl suffix in relay artifact URL. 32 | get_openssl_suffix() { 33 | openssl_3=$(php -r "echo strpos(OPENSSL_VERSION_TEXT, 'SSL 3') !== false;") 34 | [ "$openssl_3" = "1" ] && echo '+libssl3' || echo '' 35 | } 36 | 37 | # Change library paths in relay binary. 38 | change_library_paths() { 39 | if [ "$os" = "Darwin" ]; then 40 | otool -L "${ext_dir:?}"/relay.so | grep -q 'ssl.1' && openssl_version='1.1' || openssl_version='3' 41 | [ -e "${brew_prefix:?}"/opt/openssl@"$openssl_version" ] || brew install openssl@"$openssl_version" 42 | dylibs="$(otool -L "${ext_dir:?}"/relay.so | grep -Eo '.*\.dylib' | cut -f1 -d ' ')" 43 | install_name_tool -change "$(echo "${dylibs}" | grep -E "libzstd.*dylib" | xargs)" "$brew_prefix"/opt/zstd/lib/libzstd.dylib "$ext_dir"/relay.so 44 | install_name_tool -change "$(echo "${dylibs}" | grep -E "liblz4.*dylib" | xargs)" "$brew_prefix"/opt/lz4/lib/liblz4.dylib "$ext_dir"/relay.so 45 | install_name_tool -change "$(echo "${dylibs}" | grep -E "libssl.*dylib" | xargs)" "$brew_prefix"/opt/openssl@"$openssl_version"/lib/libssl.dylib "$ext_dir"/relay.so 46 | install_name_tool -change "$(echo "${dylibs}" | grep -E "libcrypto.*dylib" | xargs)" "$brew_prefix"/opt/openssl@"$openssl_version"/lib/libcrypto.dylib "$ext_dir"/relay.so 47 | install_name_tool -change "$(echo "${dylibs}" | grep -E "libck.*dylib" | xargs)" "$brew_prefix"/opt/concurrencykit/lib/libck.dylib "$ext_dir"/relay.so 48 | fi 49 | } 50 | 51 | # Add relay dependencies 52 | add_relay_dependencies() { 53 | add_extension json 54 | add_extension msgpack 55 | add_extension igbinary 56 | if [ "$os" = "Darwin" ]; then 57 | . "${0%/*}"/tools/brew.sh 58 | configure_brew 59 | brew install hiredis lz4 zstd concurrencykit 60 | fi 61 | } 62 | 63 | # Initialize relay extension ini configuration 64 | init_relay_ini() { 65 | relay_ini=$1 66 | if [ -e "$relay_ini" ]; then 67 | if [[ -n "$RELAY_KEY" ]]; then 68 | sudo sed -i.bak "s/^; relay.key =.*/relay.key = $RELAY_KEY/" "$relay_ini" 69 | fi 70 | if [[ -n "$RELAY_ENVIRONMENT" ]]; then 71 | sudo sed -i.bak "s/^; relay.environment =.*/relay.environment = $RELAY_ENVIRONMENT/" "$relay_ini" 72 | fi 73 | if [[ -n "$RELAY_EVICTION_POLICY" ]]; then 74 | sudo sed -i.bak "s/^; relay.eviction_policy =.*/relay.eviction_policy = $RELAY_EVICTION_POLICY/" "$relay_ini" 75 | fi 76 | if [[ -n "$RELAY_MAX_MEMORY" ]]; then 77 | sudo sed -i.bak "s/^; relay.maxmemory =.*/relay.maxmemory = $RELAY_MAX_MEMORY/" "$relay_ini" 78 | fi 79 | sudo rm -rf "$relay_ini".bak 80 | fi 81 | } 82 | 83 | # Enable relay extension 84 | enable_relay() { 85 | relay_ini=$1 86 | if [ -e "$relay_ini" ]; then 87 | init_relay_ini "$relay_ini" 88 | if [ "$os" = "Linux" ]; then 89 | sudo cp "$relay_ini" "${ini_dir:?}"/../mods-available/relay.ini 90 | sudo phpenmod -v "${version:?}" relay 91 | else 92 | sudo cp "${relay_ini}" "${scan_dir:?}"/60-relay.ini 93 | fi 94 | fi 95 | } 96 | 97 | # Patch binary id in relay extension 98 | init_relay_binary_id() { 99 | if [ -e "${ext_dir:?}"/relay.so ]; then 100 | sudo LC_ALL=C sed -i.bak "s/00000000-0000-0000-0000-000000000000/$(uuidgen)/" "$ext_dir"/relay.so || true 101 | fi 102 | } 103 | 104 | # Configure relay extension 105 | configure_relay() { 106 | change_library_paths 107 | init_relay_binary_id 108 | enable_relay "${ext_dir}"/relay.ini 109 | } 110 | 111 | # Helper function to add relay extension 112 | add_relay_helper() { 113 | arch="$(uname -m | sed 's/_/-/')" 114 | os_suffix="$(get_os_suffix)" 115 | openssl_suffix="$(get_openssl_suffix)" 116 | artifact_file_name="relay-$relay_version-php${version:?}-$os_suffix-$arch$openssl_suffix.tar.gz" 117 | url="$relay_trunk"/"$relay_version"/"$artifact_file_name" 118 | get -q -n /tmp/relay.tar.gz "$url" 119 | if (! [ -e /tmp/relay.tar.gz ] || ! file /tmp/relay.tar.gz | grep -q 'gzip'); then 120 | if [ "$openssl_suffix" = '+libssl3' ]; then 121 | get -q -n /tmp/relay.tar.gz "${url/+libssl3/}" 122 | else 123 | get -q -n /tmp/relay.tar.gz "${url/.tar/+libssl3.tar}" 124 | fi 125 | fi 126 | if [ -e /tmp/relay.tar.gz ] && file /tmp/relay.tar.gz | grep -q 'gzip'; then 127 | sudo tar --strip-components=1 -xzf /tmp/relay.tar.gz -C "${ext_dir:?}" 128 | sudo mv "${ext_dir:?}"/relay-pkg.so "${ext_dir:?}"/relay.so 129 | fi 130 | } 131 | 132 | # Add relay extension 133 | add_relay() { 134 | local ext=$1 135 | local arch 136 | local url 137 | os=$(uname -s) 138 | relay_releases=https://github.com/cachewerk/relay/releases 139 | relay_trunk=https://builds.r2.relay.so 140 | relay_version=$(get_relay_version "$ext") 141 | add_relay_dependencies >/dev/null 2>&1 142 | if shared_extension relay; then 143 | message="Enabled" 144 | else 145 | add_relay_helper >/dev/null 2>&1 146 | message="Installed and enabled" 147 | fi 148 | configure_relay >/dev/null 2>&1 149 | add_extension_log relay "$message" 150 | } 151 | -------------------------------------------------------------------------------- /src/scripts/extensions/source.sh: -------------------------------------------------------------------------------- 1 | # Function to parse extension environment variables 2 | parse_args() { 3 | local extension=${1%-*} 4 | suffix=$(echo "$2" | tr '[:lower:]' '[:upper:]') 5 | up_ext_name=$(echo "$extension" | tr '[:lower:]' '[:upper:]') 6 | var="${extension}_${suffix}" 7 | up_var="${up_ext_name}_${suffix}" 8 | ! [[ "$suffix" =~ .*PREFIX|LIBS|PATH.* ]] && hyp='-' 9 | output=$(echo "${!var} ${!up_var}" | sed "s/, *$hyp/ $hyp/g" | sed -E "s/^,|,$//g") 10 | echo "$output" | xargs -n 1 | sort | uniq | xargs 11 | } 12 | 13 | # Function to parse configure options for pecl 14 | # Make sure we have all options in name="value" form i.e XML properties. 15 | parse_pecl_configure_options() { 16 | configure_opts=$(echo "$1" | sed -E -e "s#['\"]|--##g") 17 | IFS=' ' read -r -a opts_array <<< "$configure_opts" 18 | output_opts=() 19 | for opt in "${opts_array[@]}"; do 20 | [ "${opt##*=}" != "${opt%=*}" ] && value="${opt##*=}" || value=yes 21 | output_opts+=("${opt%=*}=\"$value\"") 22 | done 23 | echo "${output_opts[@]}" 24 | } 25 | 26 | # Function to log if a library is installed 27 | add_lib_log() { 28 | local lib=$1 29 | if check_lib "$lib"; then 30 | add_log "${tick:?}" "$lib" "Installed" 31 | else 32 | add_log "${cross:?}" "$lib" "Could not install $lib" 33 | fi 34 | } 35 | 36 | # Function to check if a library is installed 37 | check_lib() { 38 | local lib=$1 39 | if [ "$(uname -s)" = "Linux" ]; then 40 | [ "x$(dpkg -s "$lib" 2>/dev/null | grep Status)" != "x" ] 41 | else 42 | [ "x$(find "${brew_prefix:?}"/Cellar -maxdepth 1 -name "$lib")" != "x" ] 43 | fi 44 | } 45 | 46 | # Function to add a library on linux 47 | add_linux_libs() { 48 | local lib=$1 49 | if ! check_lib "$lib"; then 50 | install_packages "$lib" >/dev/null 2>&1 || true 51 | fi 52 | add_lib_log "$lib" 53 | } 54 | 55 | # Function to add a library on macOS 56 | add_darwin_libs() { 57 | local lib=$1 58 | if ! check_lib "$lib"; then 59 | brew install "$lib" >/dev/null 2>&1 || true 60 | if [[ "$lib" = *@* ]]; then 61 | brew link --overwrite --force "$lib" >/dev/null 2>&1 || true 62 | fi 63 | fi 64 | add_lib_log "$lib" 65 | } 66 | 67 | # Function to add required libraries 68 | add_libs() { 69 | local all_libs=("$@") 70 | for lib in "${all_libs[@]}"; do 71 | if [ "$(uname -s)" = "Linux" ]; then 72 | add_linux_libs "$lib" 73 | else 74 | add_darwin_libs "$lib" 75 | fi 76 | done 77 | } 78 | 79 | # Function to run command in a group 80 | run_group() { 81 | local command=$1 82 | local log=$2 83 | echo "$command" | sudo tee ./run_group.sh >/dev/null 2>&1 84 | echo "$GROUP$log" 85 | . ./run_group.sh 86 | rm ./run_group.sh 87 | echo "$END_GROUP" 88 | } 89 | 90 | patch_extension() { 91 | local extension=$1 92 | # shellcheck source=. 93 | . "${scripts:?}"/extensions/patches/common.sh 94 | if [ -e "${scripts:?}"/extensions/patches/"$extension".sh ]; then 95 | # shellcheck source=. 96 | . "${scripts:?}"/extensions/patches/"$extension".sh 97 | patch_"${extension}" 98 | fi 99 | } 100 | 101 | fetch_extension() { 102 | local extension=$1 103 | local fetch=$2 104 | if [ "$fetch" = "clone" ]; then 105 | run_group "git clone -nv $url/$org/$repo /tmp/$repo-$release" "git clone" 106 | cd /tmp/"$repo-$release" || exit 1 107 | git checkout -q "$release" 108 | cd "$sub_dir" || exit 1 109 | if [ -e .gitmodules ]; then 110 | jobs="$(grep -c "\[submodule" .gitmodules)" 111 | run_group "git submodule update --jobs $jobs --init --recursive" "git submodule" 112 | fi 113 | elif [ "$fetch" = "get" ]; then 114 | get -q -n /tmp/"$extension".tar.gz "$url/$org/$repo/archive/$release.tar.gz" 115 | tar -xzf /tmp/"$extension".tar.gz -C /tmp 116 | cd /tmp/"$repo"-"$release"/"$sub_dir" || exit 117 | elif [ "$fetch" = "pecl" ]; then 118 | source="pecl" 119 | pecl_name=${extension/http/pecl_http} 120 | get -q -n /tmp/"$pecl_name".tgz https://pecl.php.net/get/"$pecl_name"-"$release".tgz 121 | tar -xzf /tmp/"$pecl_name".tgz -C /tmp 122 | cd /tmp/"$pecl_name"-"$release" || exit 123 | fi 124 | } 125 | 126 | # Function to install extension from a git repository 127 | add_extension_from_source() { 128 | local extension="${1/pecl_/}" 129 | local url=$2 130 | local org=$3 131 | local repo=$4 132 | local release=$5 133 | local prefix=$6 134 | local fetch=${7:-clone} 135 | slug="$extension-$release" 136 | source="$url/$org/$repo" 137 | libraries="$(parse_args "$extension" LIBS) $(parse_args "$extension" "$(uname -s)"_LIBS)" 138 | opts="$(parse_args "$extension" CONFIGURE_OPTS)" 139 | prefix_opts="$(parse_args "$extension" CONFIGURE_PREFIX_OPTS)" 140 | suffix_opts="$(parse_args "$extension" CONFIGURE_SUFFIX_OPTS)" 141 | sub_dir="$(parse_args "$extension" PATH)" 142 | step_log "Setup $slug" 143 | ( 144 | add_devtools phpize >/dev/null 2>&1 145 | disable_extension_helper "$extension" 146 | fetch_extension "$extension" "$fetch" 147 | if ! [ "$(find . -maxdepth 1 -name '*.m4' -exec grep -H 'PHP_NEW_EXTENSION' {} \; | wc -l)" != "0" ]; then 148 | add_log "${cross:?}" "$source" "$source does not have a PHP extension" 149 | else 150 | [[ -n "${libraries// }" ]] && run_group "add_libs $libraries" "add libraries" 151 | [ "${debug:?}" = "debug" ] && suffix_opts="$suffix_opts --enable-debug" 152 | patch_extension "$extension" >/dev/null 2>&1 153 | run_group "phpize" "phpize" 154 | run_group "sudo $prefix_opts ./configure $suffix_opts $opts" "configure" 155 | run_group "sudo $prefix_opts make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)" "make" 156 | run_group "sudo make install" "make install" 157 | enable_extension "$extension" "$prefix" 158 | fi 159 | ) 160 | add_extension_log "$slug" "Installed from $source and enabled" 161 | } 162 | --------------------------------------------------------------------------------