├── .github └── FUNDING.yml ├── action.yml ├── composer-action.bash └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: [g105b] -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Composer (php-actions)" 2 | description: "Use the Composer CLI in your GitHub Actions." 3 | 4 | inputs: 5 | version: 6 | description: "What version of Composer to use" 7 | default: latest 8 | required: false 9 | 10 | php_version: 11 | description: "What version of PHP to use" 12 | default: latest 13 | required: false 14 | 15 | php_extensions: 16 | description: "Space separated list of extensions to configure with the PHP build" 17 | required: false 18 | 19 | command: 20 | description: "Composer command to run" 21 | required: true 22 | default: install 23 | 24 | only_args: 25 | description: "Only run the desired command with this args. Ignoring all other provided arguments" 26 | required: false 27 | 28 | interaction: 29 | description: "Whether to ask any interactive questions. Values: 'yes' or 'no'" 30 | required: false 31 | default: no 32 | 33 | dev: 34 | description: "Whether to install dev packages. Values: 'yes' or 'no'" 35 | required: false 36 | default: yes 37 | 38 | progress: 39 | description: "Whether to output download progress" 40 | required: false 41 | default: no 42 | 43 | quiet: 44 | description: "Whether to suppress all messages" 45 | required: false 46 | default: no 47 | 48 | args: 49 | description: "Optional arguments to pass" 50 | required: false 51 | 52 | ssh_key: 53 | description: "The private key contents to use for private repositories" 54 | required: false 55 | 56 | ssh_key_pub: 57 | description: "The public key contents to use for private repositories" 58 | required: false 59 | 60 | ssh_domain: 61 | description: "The domain to gather SSH public keys for (automatic for github.com, gitlab.com, bitbucket.org)" 62 | required: false 63 | 64 | ssh_port: 65 | description: Custom port to use in conjunction with ssh_domain 66 | required: false 67 | 68 | working_dir: 69 | description: "Use the given directory as working directory" 70 | required: false 71 | 72 | memory_limit: 73 | description: "Sets the composer memory limit" 74 | required: false 75 | 76 | container_workdir: 77 | description: "Sets the application workdir inside container" 78 | required: false 79 | 80 | outputs: 81 | full_command: 82 | description: "The full command passed to docker to run" 83 | value: ${{ steps.composer_run.outputs.full_command }} 84 | 85 | runs: 86 | using: "composite" 87 | steps: 88 | - env: 89 | ACTION_VERSION: ${{ inputs.version }} 90 | ACTION_PHP_VERSION: ${{ inputs.php_version }} 91 | ACTION_PHP_EXTENSIONS: ${{ inputs.php_extensions }} 92 | ACTION_TOKEN: ${{ github.token }} 93 | ACTION_COMMAND: ${{ inputs.command }} 94 | ACTION_ONLY_ARGS: ${{ inputs.only_args }} 95 | ACTION_INTERACTION: ${{ inputs.interaction }} 96 | ACTION_DEV: ${{ inputs.dev }} 97 | ACTION_PROGRESS: ${{ inputs.progress }} 98 | ACTION_QUIET: ${{ inputs.quiet }} 99 | ACTION_ARGS: ${{ inputs.args }} 100 | ACTION_SSH_KEY: ${{ inputs.ssh_key }} 101 | ACTION_SSH_KEY_PUB: ${{ inputs.ssh_key_pub }} 102 | ACTION_SSH_DOMAIN: ${{ inputs.ssh_domain }} 103 | ACTION_SSH_PORT: ${{ inputs.ssh_port }} 104 | ACTION_WORKING_DIR: ${{ inputs.working_dir }} 105 | ACTION_MEMORY_LIMIT: ${{ inputs.memory_limit }} 106 | ACTION_CONTAINER_WORKDIR: ${{ inputs.container_workdir }} 107 | id: composer_run 108 | run: | 109 | set -e 110 | bash <(curl -s https://raw.githubusercontent.com/php-actions/php-build/v2/php-build.bash) composer 111 | ${{ github.action_path }}/composer-action.bash 112 | shell: bash 113 | 114 | branding: 115 | icon: 'package' 116 | color: 'purple' 117 | -------------------------------------------------------------------------------- /composer-action.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | container_workdir="/app" 5 | github_action_path=$(dirname "$0") 6 | docker_tag=$(cat ./docker_tag) 7 | echo "Docker tag: $docker_tag" >> output.log 2>&1 8 | 9 | phar_url="https://getcomposer.org/" 10 | # check if $ACTION_VERSION is not set or empty or set to latest 11 | if [ -z "$ACTION_VERSION" ] || [ "$ACTION_VERSION" == "latest" ]; 12 | then 13 | # if a version is not set, use latest composer version 14 | phar_url="${phar_url}download/latest-stable/composer.phar" 15 | else 16 | # if a version is set, choose the correct download 17 | case "$ACTION_VERSION" in 18 | # get the latest preview 19 | Preview | preview) 20 | phar_url="${phar_url}download/latest-preview/composer.phar" 21 | ;; 22 | # get the latest snapshot 23 | Snapshot | snapshot) 24 | phar_url="${phar_url}composer.phar" 25 | ;; 26 | # get the latest version of the v1 tree 27 | 1 | 1.x) 28 | phar_url="${phar_url}download/latest-1.x/composer.phar" 29 | ;; 30 | # get the latest version of the v2 tree 31 | 2 | 2.x) 32 | phar_url="${phar_url}download/latest-2.x/composer.phar" 33 | ;; 34 | # get the latest version of the v2.2 tree 35 | 2.2 | 2.2.x) 36 | phar_url="${phar_url}download/latest-2.2.x/composer.phar" 37 | ;; 38 | # if the version is not one of the above, assume that it is a exact 39 | # naming, possibly with additions (RC, beta1, ...) 40 | *) 41 | phar_url="${phar_url}download/${ACTION_VERSION}/composer.phar" 42 | ;; 43 | esac 44 | fi 45 | curl --silent -H "User-agent: cURL (https://github.com/php-actions)" -L "$phar_url" > "${github_action_path}/composer.phar" 46 | chmod +x "${github_action_path}/composer.phar" 47 | 48 | # command_string is passed directly to the docker executable. It includes the 49 | # container name and version, and this script will build up the rest of the 50 | # arguments according to the action's input values. 51 | command_string="composer" 52 | 53 | # In case there is need to install private repositories, SSH details are stored 54 | # in these two places, which are mounted on the Composer docker container later. 55 | ssh_path="${github_action_path}/__tmp/ssh" 56 | gitconfig_path="${github_action_path}/__tmp/gitconfig" 57 | mkdir -p "$ssh_path" 58 | touch "$gitconfig_path" 59 | 60 | if [ -n "$ACTION_SSH_KEY" ] 61 | then 62 | echo "Storing private key file for root" >> output.log 2>&1 63 | ssh-keyscan -t rsa github.com >> "$ssh_path/known_hosts" 64 | ssh-keyscan -t rsa gitlab.com >> "$ssh_path/known_hosts" 65 | ssh-keyscan -t rsa bitbucket.org >> "$ssh_path/known_hosts" 66 | 67 | if [ -n "$ACTION_SSH_DOMAIN" ] 68 | then 69 | if [ -n "$ACTION_SSH_PORT" ] 70 | then 71 | ssh-keyscan -t rsa -p $ACTION_SSH_PORT "$ACTION_SSH_DOMAIN" >> "$ssh_path/known_hosts" 72 | else 73 | ssh-keyscan -t rsa "$ACTION_SSH_DOMAIN" >> "$ssh_path/known_hosts" 74 | fi 75 | fi 76 | 77 | echo "$ACTION_SSH_KEY" > "$ssh_path/action_rsa" 78 | echo "$ACTION_SSH_KEY_PUB" > "$ssh_path/action_rsa.pub" 79 | chmod 600 "$ssh_path/action_rsa" 80 | 81 | echo "PRIVATE KEY:" >> output.log 2>&1 82 | md5sum "$ssh_path/action_rsa" >> output.log 2>&1 83 | echo "PUBLIC KEY:" >> output.log 2>&1 84 | md5sum "$ssh_path/action_rsa.pub" >> output.log 2>&1 85 | 86 | echo "[core]" >> "$gitconfig_path" 87 | echo "sshCommand = \"ssh -i ~/.ssh/action_rsa\"" >> "$gitconfig_path" 88 | else 89 | echo "No private keys supplied" >> output.log 2>&1 90 | fi 91 | 92 | if [ -n "$ACTION_COMMAND" ] 93 | then 94 | command_string="$command_string $ACTION_COMMAND" 95 | fi 96 | 97 | if [ -n "$ACTION_WORKING_DIR" ] 98 | then 99 | command_string="$command_string --working-dir=$ACTION_WORKING_DIR" 100 | fi 101 | 102 | # If the ACTION_ONLY_ARGS has _not_ been passed, then we build up the arguments 103 | # that have been specified. The else condition to this if statement allows 104 | # the developer to specify exactly what arguments to pass to Composer. 105 | if [ -z "$ACTION_ONLY_ARGS" ] 106 | then 107 | if [ "$ACTION_COMMAND" = "install" ] 108 | then 109 | case "$ACTION_DEV" in 110 | yes) 111 | # Default behaviour 112 | ;; 113 | no) 114 | command_string="$command_string --no-dev" 115 | ;; 116 | *) 117 | echo "Invalid input for action argument: dev (must be yes or no)" 118 | exit 1 119 | ;; 120 | esac 121 | 122 | case "$ACTION_PROGRESS" in 123 | yes) 124 | # Default behaviour 125 | ;; 126 | no) 127 | command_string="$command_string --no-progress" 128 | ;; 129 | *) 130 | echo "Invalid input for action argument: progress (must be yes or no)" 131 | exit 1 132 | ;; 133 | esac 134 | fi 135 | 136 | case "$ACTION_INTERACTION" in 137 | yes) 138 | # Default behaviour 139 | ;; 140 | no) 141 | command_string="$command_string --no-interaction" 142 | ;; 143 | *) 144 | echo "Invalid input for action argument: interaction (must be yes or no)" 145 | exit 1 146 | ;; 147 | esac 148 | 149 | case "$ACTION_QUIET" in 150 | yes) 151 | command_string="$command_string --quiet" 152 | ;; 153 | no) 154 | # Default behaviour 155 | ;; 156 | *) 157 | echo "Invalid input for action argument: quiet (must be yes or no)" 158 | exit 1 159 | ;; 160 | esac 161 | 162 | if [ -n "$ACTION_ARGS" ] 163 | then 164 | command_string="$command_string $ACTION_ARGS" 165 | fi 166 | else 167 | command_string="$command_string $ACTION_ONLY_ARGS" 168 | fi 169 | 170 | if [ -n "$ACTION_MEMORY_LIMIT" ] 171 | then 172 | memory_limit="--env COMPOSER_MEMORY_LIMIT=$ACTION_MEMORY_LIMIT" 173 | else 174 | memory_limit='' 175 | fi 176 | 177 | echo "Command: $command_string" >> output.log 2>&1 178 | mkdir -p /tmp/composer-cache 179 | 180 | export COMPOSER_CACHE_DIR="/tmp/composer-cache" 181 | unset ACTION_SSH_KEY 182 | unset ACTION_SSH_KEY_PUB 183 | 184 | dockerKeys=() 185 | while IFS= read -r line 186 | do 187 | dockerKeys+=( $(echo "$line" | cut -f1 -d=) ) 188 | done <<<$(docker run --rm "${docker_tag}" env) 189 | 190 | while IFS= read -r line 191 | do 192 | key=$(echo "$line" | cut -f1 -d=) 193 | if printf '%s\n' "${dockerKeys[@]}" | grep -q -P "^${key}\$" 194 | then 195 | echo "Skipping env variable $key" >> output.log 196 | else 197 | echo "$line" >> DOCKER_ENV 198 | fi 199 | done <<<$(env) 200 | 201 | if [ -n "$ACTION_CONTAINER_WORKDIR" ]; then 202 | container_workdir="${ACTION_CONTAINER_WORKDIR}" 203 | fi 204 | 205 | echo "name=full_command::${command_string}" >> $GITHUB_OUTPUT 206 | 207 | docker run --rm \ 208 | --volume "${github_action_path}/composer.phar":/usr/local/bin/composer \ 209 | --volume "$gitconfig_path":/root/.gitconfig \ 210 | --volume "$ssh_path":/root/.ssh \ 211 | --volume "${GITHUB_WORKSPACE}":/app \ 212 | --volume "/tmp/composer-cache":/tmp/composer-cache \ 213 | --workdir ${container_workdir} \ 214 | --env-file ./DOCKER_ENV \ 215 | --network host \ 216 | ${memory_limit} \ 217 | ${docker_tag} ${command_string} 218 | 219 | rm -rf "${github_action_path}/__tmp" 220 | 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Actions for Github 2 | 3 | Use the Composer CLI in your Github Actions. 4 | ============================================ 5 | 6 | Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on, and it will manage (install/update) them for you. 7 | 8 | If you are running tests like [PHPUnit][php-actions-phpunit], [phpspec][php-actions-phpspec] or [Behat][php-actions-behat] in your Github actions, chances are you will need to install your project's dependencies using Composer. 9 | 10 | An example repository has been created at https://github.com/php-actions/example-composer to show how to use this action in a real project. The repository also depends on a private dependency and uses SSH keys for authentication. 11 | 12 | Usage 13 | ----- 14 | 15 | Create your Github Workflow configuration in `.github/workflows/ci.yml` or similar. 16 | 17 | ```yaml 18 | name: CI 19 | 20 | on: [push] 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v5 28 | - uses: php-actions/composer@v6 29 | # ... then your own project steps ... 30 | ``` 31 | 32 | ### Version numbers 33 | 34 | This action is released with semantic version numbers, but also tagged so the latest major release's tag always points to the latest release within the matching major version. 35 | 36 | Please feel free to use `uses: php-actions/composer@v6` to always run the latest version of v6, or `uses: php-actions/composer@v6.0.0` to specify the exact release. 37 | 38 | 39 | Running custom commands 40 | ----------------------- 41 | 42 | By default, adding `- uses: php-actions/composer@v6` into your workflow will run `composer install`, as `install` is the default command name. The `install` command will be provided with a default set of arguments (see below). 43 | 44 | You can issue custom commands by passing a `command` input, like so: 45 | 46 | ```yaml 47 | jobs: 48 | build: 49 | 50 | ... 51 | 52 | - name: Install dependencies 53 | uses: php-actions/composer@v6 54 | with: 55 | command: your-command-here 56 | ``` 57 | 58 | Passing arguments 59 | ----------------- 60 | 61 | Any arbitrary arguments can be passed to composer by using the `args` input, however there are a few inputs pre-configured to handle common arguments. All inputs are optional. Please see the following list: 62 | 63 | + `interaction` - Whether to ask any interactive questions - yes / no (default `no`) 64 | + `dev` - Whether to install dev packages - yes / no (default `yes`) 65 | + `progress` - Whether to output download progress - yes / no (default `no`) 66 | + `quiet` - Whether to suppress all messages - yes / no (default `no`) 67 | + `args` - Optional arguments to pass - no constraints (default _empty_) 68 | + `only_args` - Only run the desired command with this args. Ignoring all other provided arguments(default _empty_) 69 | + `php_version` - Choose which version of PHP you want to use - x.y (default `latest`) (e.g. `7.4.29`, `8.2`, or any version listed on https://www.php.net/releases/index.php) 70 | + `version` - Choose which version of Composer you want to use - default `latest` (e.g. `1.x`, `1.10.26`, `2.x`, `2.2.x`, or any exact version listed on https://getcomposer.org/download/) 71 | + `memory_limit` - Sets the composer memory limit - (default _empty_) 72 | + `container_workdir` - Sets the aplication workdir inside container - (default /app) 73 | 74 | There are also SSH input available: `ssh_key`, `ssh_key_pub` and `ssh_domain` that are used for depending on private repositories. See below for more information on usage. 75 | 76 | Example of a yaml config that does not want to install dev packages, and passes the `--profile` and `--ignore-platform-reqs` arguments: 77 | 78 | ```yaml 79 | jobs: 80 | build: 81 | 82 | ... 83 | 84 | - name: Install dependencies 85 | uses: php-actions/composer@v6 86 | with: 87 | dev: no 88 | args: --profile --ignore-platform-reqs 89 | ``` 90 | 91 | Using different versions of PHP or Composer 92 | ------------------------------------------- 93 | 94 | This action runs on a custom base image, available at https://github.com/php-actions/php-build which allows for switching the active PHP version on-the-fly, and this repository allows switching of Composer versions on-the-fly. 95 | 96 | Use the following inputs to run a specific PHP/Composer version combination: 97 | 98 | + `php_version` Available versions: `7.1`, `7.2`, `7.3`, `7.4`, `8.0`, `8.1` (default: `latest` aka: `8.1`) 99 | + `version` Available versions: `latest`, `preview`, `snapshot`, `1.x`, `2.x`, `2.2.x` or an exact version like `2.5.8` (default: `latest`) 100 | 101 | Make sure to put the PHP version number in quotes, otherwise YAML will interpret e.g. `8.0` as `8` which means latest 8.x, not 8.0. 102 | 103 | Example configuration that runs Composer version 1 on PHP version 7.1: 104 | ```yaml 105 | jobs: 106 | build: 107 | 108 | ... 109 | 110 | - name: Install dependencies 111 | uses: php-actions/composer@v6 112 | with: 113 | php_version: "7.1" 114 | version: 1 115 | ``` 116 | 117 | Execute composer install in a different folder 118 | ------------------------------------------- 119 | 120 | ```yaml 121 | - name: Install dependencies 122 | uses: "php-actions/composer@v6" 123 | env: 124 | COMPOSER: "composer.json" 125 | with: 126 | php_version: "5.6.40" 127 | version: "2.2" 128 | args: "--ignore-platform-reqs --optimize-autoloader" 129 | working_dir: "my/different/folder" 130 | ``` 131 | 132 | Including PHP Extensions 133 | ------------------------------------------- 134 | 135 | This action includes the [extensions that Composer suggests](https://github.com/composer/composer/blob/master/composer.json#L44) by default. To include additional PHP extensions in your action steps, set the `php_extensions` input with any of the [supported extension names](https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions) separated by spaces. 136 | 137 | Example configuration that runs Composer version 2 on PHP version 7.4 with the Redis and Exif extensions enabled: 138 | 139 | ```yaml 140 | jobs: 141 | build: 142 | 143 | ... 144 | 145 | - name: Install dependencies 146 | uses: php-actions/composer@v6 147 | with: 148 | php_version: "7.4" 149 | php_extensions: redis exif 150 | version: 2.x 151 | ``` 152 | 153 | Caching dependencies for faster builds 154 | -------------------------------------- 155 | 156 | Github actions supports dependency caching, allowing Composer downloads to be cached between workflows, as long as the `composer.lock` file has not changed. This produces much faster builds, as the `composer install` command does not have to download files over the network at all if the cache is valid. 157 | 158 | Example workflow (taken from https://github.com/PhpGt/Database): 159 | 160 | ```yaml 161 | name: CI 162 | 163 | on: [push] 164 | 165 | jobs: 166 | build: 167 | runs-on: [ubuntu-latest] 168 | 169 | steps: 170 | - uses: actions/checkout@v5 171 | 172 | - name: Cache Composer dependencies 173 | uses: actions/cache@v4 174 | with: 175 | path: /tmp/composer-cache 176 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} 177 | 178 | - uses: php-actions/composer@v6 179 | 180 | ... 181 | ``` 182 | 183 | In the example above, the "key" is passed to the Cache action that consists of a hash of the composer.lock file. This means that as long as the contents of composer.lock doesn't change between workflows, the Composer cache directory will be persisted between workflows. 184 | 185 | Installing private repositories 186 | ------------------------------- 187 | 188 | To install from a private repository, SSH authentication must be used. Generate an SSH key pair for this purpose and add it to your private repository's configuration, preferable with only read-only privileges. On Github for instance, this can be done by using [deploy keys][deploy-keys]. 189 | 190 | Add the key pair to your project using [Github Secrets][secrets], and pass them into the `php-actions/composer` action by using the `ssh_key` and `ssh_key_pub` inputs. If your private repository is stored on another server than github.com, you also need to pass the domain via `ssh_domain`. If the private repository is configured to use a non-standard SSH port, you can configure this by passing `ssh_port`. 191 | 192 | Example yaml, showing how to pass secrets: 193 | 194 | ```yaml 195 | jobs: 196 | build: 197 | 198 | ... 199 | 200 | - name: Install dependencies 201 | uses: php-actions/composer@v6 202 | with: 203 | ssh_key: ${{ secrets.ssh_key }} 204 | ssh_key_pub: ${{ secrets.ssh_key_pub }} 205 | ``` 206 | 207 | There is an example repository available for reference at https://github.com/php-actions/example-composer that uses a private dependency. Check it out for a live working project. 208 | 209 | ### HTTP basic authentication 210 | 211 | It's recommended to use SSH keys for authentication, but sometimes HTTP basic authentication is the only tool available at the time. In order to use this authentication mechanism as securely as possible, please follow these steps: 212 | 213 | 1) Create a [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) for the Github account you wish to authenticate with. 214 | 215 | 2) Create a new GitHub Secret called `PAT` with a value of personal access token. 216 | 217 | 3) Pass this secret to COMPOSER_AUTH variable: 218 | 219 | ```yaml 220 | jobs: 221 | build: 222 | 223 | ... 224 | 225 | - name: Install dependencies 226 | uses: php-actions/composer@v6 227 | env: 228 | COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }' 229 | ``` 230 | 231 | 4) Now, any connections Composer makes to GitHub.com will use your HTTP basic auth credentials, which is essentially the same as being logged in as you, so your private repositories will now be available to Composer. 232 | 233 | *** 234 | 235 | If you found this repository helpful, please consider [sponsoring the developer][sponsor]. 236 | 237 | [php-actions-phpunit]: https://github.com/marketplace/actions/phpunit-php-actions 238 | [php-actions-phpspec]: https://github.com/marketplace/actions/phpspec-php-actions 239 | [php-actions-behat]: https://github.com/marketplace/actions/behat-php-actions 240 | [deploy-keys]: https://docs.github.com/en/developers/overview/managing-deploy-keys 241 | [secrets]: https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets 242 | [sponsor]: https://github.com/sponsors/g105b 243 | --------------------------------------------------------------------------------