Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/code-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ jobs:
restore-keys: |
${{ runner.os }}-php-
- name: Install Pint
run: composer global require laravel/pint
- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress

- name: Run Pint
run: pint --test
run: vendor/bin/pint --test src tests

- name: Run tests
run: composer test
38 changes: 10 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ permissions:
jobs:
release:
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.version }}
release_name: ${{ github.event.inputs.version }}
Expand All @@ -49,33 +47,19 @@ jobs:
php-version: ${{ matrix.php-version }}
tools: composer

- name: Remove Vendor and Composer Lock
- name: Capture Runtime PHP
run: |
rm -rf vendor composer.lock

- name: Install Dependencies
run: composer install --no-dev
echo "RUNTIME_PHP=$(command -v php)" >> "$GITHUB_ENV"
echo "COMPOSER_BIN=$(command -v composer)" >> "$GITHUB_ENV"

- name: Setup PHP for Box
uses: shivammathur/setup-php@v2
with:
php-version: "8.2"
tools: composer

- name: Install Box Globally
run: |
composer global require humbug/box
env:
COMPOSER_HOME: ${{ runner.temp }}/composer

- name: Add Composer Global Bin to PATH
run: echo "$COMPOSER_HOME/vendor/bin" >> $GITHUB_PATH
env:
COMPOSER_HOME: ${{ runner.temp }}/composer

- name: Compile with Box
run: |
box compile
- name: Build PHAR
run: make build UPDATE_LOCK=true PHP_BIN="$RUNTIME_PHP" BOX_PHP="$(command -v php)" COMPOSER_BIN="$COMPOSER_BIN"
env:
COMPOSER_HOME: ${{ runner.temp }}/composer

Expand All @@ -84,11 +68,9 @@ jobs:
mv client.phar client-${{ matrix.php-version }}.phar

- name: Upload Release Asset
uses: actions/upload-release-asset@v1
uses: softprops/action-gh-release@v2
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: ./client-${{ matrix.php-version }}.phar
asset_name: client-${{ matrix.php-version }}.phar
asset_content_type: application/octet-stream
tag_name: ${{ github.event.inputs.version }}
files: ./client-${{ matrix.php-version }}.phar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 changes: 32 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: tests

on:
push:
branches:
- main
pull_request:

jobs:
tests:
runs-on: ubuntu-22.04

strategy:
fail-fast: false
matrix:
php: [ "7.4", "8.0", "8.1", "8.2", "8.3", "8.4" ]

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: json
tools: composer

- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress --ignore-platform-req=php

- name: Run tests
run: composer test
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.idea
.vscode

vendor
client.phar

.DS_Store
.DS_Store
.phpunit.result.cache
e2e_tests/*
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: build

PHP_BIN ?= php
BOX_PHP ?= $(PHP_BIN)
COMPOSER_BIN ?= $(shell command -v composer)
BOX_VERSION ?= 4.6.2
BOX_COMPOSER_HOME := $(if $(strip $(COMPOSER_HOME)),$(COMPOSER_HOME),$(shell mktemp -d))
BOX_BIN := $(shell COMPOSER_HOME="$(BOX_COMPOSER_HOME)" $(COMPOSER_BIN) global config bin-dir --absolute 2>/dev/null)/box

ifeq ($(UPDATE_LOCK),true)
COMPOSER_ACTION := update
COMPOSER_ACTION_FLAGS := --with-all-dependencies
else
COMPOSER_ACTION := install
COMPOSER_ACTION_FLAGS :=
endif

build:
rm -rf vendor
$(PHP_BIN) $(COMPOSER_BIN) $(COMPOSER_ACTION) --no-dev --prefer-dist --optimize-autoloader --no-interaction --no-progress $(COMPOSER_ACTION_FLAGS)
COMPOSER_HOME="$(BOX_COMPOSER_HOME)" $(BOX_PHP) $(COMPOSER_BIN) global require humbug/box:$(BOX_VERSION) --with-all-dependencies --no-interaction
$(BOX_PHP) $(BOX_BIN) compile
156 changes: 154 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,157 @@
# TweakPHP Client

TweakPHP Client is a packaged Phar file that [TweakPHP](https://github.com/tweakphp/tweakphp) uses under the hood to run your tweaks!
TweakPHP Client is a PHAR used by [TweakPHP](https://github.com/tweakphp/tweakphp)
to execute PHP code inside a project.

(Documentation in progress...)
## Requirements

- PHP 7.4 or higher

## Build

Build the PHAR with production dependencies only:

```bash
make build
```
The build removes `vendor`, installs the project dependencies with `--no-dev`,
installs the pinned Box version in a temporary Composer home, and creates
`client.phar`. Development dependencies are not included in the PHAR.

Release builds resolve the dependencies against the PHP runtime that will
execute each PHAR. PHP 7.4 and 8.0 use Symfony 5.4, while newer PHP versions
can use the compatible Symfony 6.4, 7.x, or 8.x release. To do the same for a
local build, run:

```bash
make build UPDATE_LOCK=true
```

## Command Syntax

The project directory is always required and must come before the command:

```bash
php client.phar <project-directory> <command> [base64-code]
```

For the current directory, use `.` or `$PWD`:

```bash
php client.phar . info
php client.phar "$PWD" info
```

Available commands:

```bash
php client.phar <project-directory> info
php client.phar <project-directory> execute <base64-code>
php client.phar <project-directory> execute-stream <base64-code>
```

## Check Project Info

`info` reports the detected project type, project version, and PHP version:

```bash
php client.phar /path/to/project info
```

Example response:

```json
{"name":"Laravel","version":"11.x","php_version":"8.4.0"}
```

## Execute Code

The PHP code must be Base64-encoded before it is passed to the client.

```bash
code=$(printf '%s' 'echo "Hello";' | base64)
php client.phar /path/to/project execute "$code"
```

The command waits until the complete script has finished and returns one JSON
result prefixed with `TWEAKPHP_RESULT:`:

```text
TWEAKPHP_RESULT:{"output":[{"line":2,"code":"echo \"Hello\";","output":"Hello","queries":[]}],"queries":[]}
```

If the command fails, it returns `TWEAKPHP_ERROR:` and exits with status `1`:

```text
TWEAKPHP_ERROR:{"class":"InvalidArgumentException","message":"Invalid Base64-encoded PHP code."}
```

User code calling `exit()` or `die()` preserves the requested exit status.
Instrumentation failures are reported in the optional `query_errors` field rather
than being silently discarded.

Multiple statements are supported:

```bash
code=$(printf '%s' 'echo "First"; sleep(1); echo "Second";' | base64)
php client.phar "$PWD" execute "$code"
```

## Execute As A Stream

Use `execute-stream` when the caller needs output while the script is running.
The project directory is still required:

```bash
code=$(printf '%s' 'echo "First"; sleep(1); echo "Second";' | base64)
php client.phar "$PWD" execute-stream "$code"
```

The command writes one JSON event per line, each prefixed with
`TWEAKPHP_STREAM:`:

```text
TWEAKPHP_STREAM:{"type":"statement.started","index":0,"line":2,"code":"echo \"First\";"}
TWEAKPHP_STREAM:{"type":"output","index":0,"data":"First"}
TWEAKPHP_STREAM:{"type":"statement.completed","index":0,"queries":[]}
TWEAKPHP_STREAM:{"type":"completed"}
```

Event types:

- `statement.started`: a statement started
- `output`: the statement produced output
- `statement.completed`: a statement finished, with collected queries
- `error`: a statement or the client failed; the process exits with status `1`,
or with the status requested by `exit()`/`die()`
- `completed`: all statements finished

An `exit()` or `die()` call emits an `error` event with its exit status and the
process exits with that status.

## Supported Projects

The client detects the project type automatically:

- Laravel: `vendor/autoload.php` and `bootstrap/app.php`
- Symfony: `vendor/autoload.php`, `symfony.lock`, and `src/Kernel.php`
- WordPress: `wp-load.php`
- Pimcore: `vendor/pimcore/pimcore`
- Composer: `vendor/autoload.php`
- Plain PHP: any directory not matching a more specific project type

Examples:

```bash
# Laravel
code=$(printf '%s' 'return App\Models\User::query()->latest()->first();' | base64)
php client.phar /path/to/laravel execute "$code"

# WordPress
code=$(printf '%s' 'return get_option("blogname");' | base64)
php client.phar /path/to/wordpress execute "$code"

# Plain PHP or Composer
code=$(printf '%s' 'return PHP_VERSION;' | base64)
php client.phar /path/to/project execute "$code"
```
28 changes: 23 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"name": "tweakphp/client",
"type": "library",
"license": "MIT",
"description": "PHAR client for executing PHP code inside a project",
Comment thread
ILDaviz marked this conversation as resolved.
"autoload": {
"psr-4": {
"TweakPHP\\Client\\": "src/"
Expand All @@ -10,16 +12,32 @@
{
"name": "Saeed Vaziry",
"email": "mr.saeedvaziry@gmail.com"
},
{
"name": "David Galet",
"email": "davidgalet@gmail.com"
}
],
"require": {
"php": ">=7.4",
"ext-json": "*",
"psy/psysh": "*",
"nikic/php-parser": "*",
"symfony/var-dumper": "*",
"symfony/console": "*"
"psy/psysh": "^0.12",
"nikic/php-parser": "^5.0",
"symfony/var-dumper": "^5.4 || ^6.4 || ^7.0 || ^8.0",
"symfony/console": "^5.4 || ^6.4 || ^7.0 || ^8.0"
},
"minimum-stability": "stable",
"prefer-stable": true
"prefer-stable": true,
"require-dev": {
"phpunit/phpunit": "^9.6",
"laravel/pint": "^1.29"
},
"autoload-dev": {
"psr-4": {
"TweakPHP\\Client\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit tests/"
}
}
Loading
Loading