diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..5aa6974
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+.git
+.github
+vendor
+tests
+examples/*/vendor
+*.log
diff --git a/.gitattributes b/.gitattributes
index a45a036..a7fc084 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,6 @@
/tests export-ignore
+/examples export-ignore
+/benchmarks export-ignore
+/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..961c8f3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,413 @@
+# CI: test matrix + coverage, then on master/main push create a release branch + GitHub prerelease.
+# Promote the prerelease to a full release to publish to Packagist (see packagist-release.yml).
+# Pattern adapted from tishlang/tish CI (test, coverage, build) → release pipeline.
+
+name: CI (test, coverage, release)
+
+on:
+ push:
+ branches: [master, main, v2]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ # Split by DB so each job only pulls one service image (Docker Hub flakes otherwise kill
+ # unrelated matrix cells). Official library images via public.ecr.aws avoid registry-1.docker.io.
+ test-pgsql:
+ name: PHP ${{ matrix.php }} / pgsql
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ permissions:
+ contents: read
+ checks: write
+ issues: write
+ pull-requests: write
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.3', '8.4']
+ include:
+ - php: '8.3'
+ coverage: true
+
+ services:
+ postgres:
+ image: public.ecr.aws/docker/library/postgres:16
+ env:
+ POSTGRES_DB: tipsy
+ POSTGRES_USER: postgres
+ # Trust auth for ephemeral CI — avoids hardcoded password secrets scanners.
+ POSTGRES_HOST_AUTH_METHOD: trust
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd="pg_isready -U postgres"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=5
+
+ env:
+ DB: pgsql
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: pdo, pdo_mysql, pdo_pgsql, pdo_sqlite
+ coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}
+ ini-values: short_open_tag=On
+ tools: composer:v2
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
+
+ - name: Cache Composer packages
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-${{ matrix.php }}-composer-
+ ${{ runner.os }}-php-composer-
+
+ - name: Validate composer.json
+ run: composer validate --strict --no-check-publish
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress --no-interaction
+
+ - name: Start built-in server
+ run: php -S 127.0.0.1:8000 -t tests/web > /tmp/tipsy-php-server.log 2>&1 &
+
+ - name: Wait for built-in server
+ run: |
+ for i in $(seq 1 30); do
+ if curl -sf "http://127.0.0.1:8000/" >/dev/null 2>&1 || curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:8000/" | grep -Eq '^[0-9]+$'; then
+ exit 0
+ fi
+ sleep 0.2
+ done
+ echo "PHP built-in server failed to start" >&2
+ cat /tmp/tipsy-php-server.log >&2 || true
+ exit 1
+
+ - name: Run tests
+ if: ${{ !matrix.coverage }}
+ run: composer test
+
+ - name: Run tests with coverage
+ if: ${{ matrix.coverage }}
+ env:
+ XDEBUG_MODE: coverage
+ run: composer test:coverage
+
+ - name: Upload coverage to Codecov
+ if: ${{ matrix.coverage }}
+ continue-on-error: true
+ uses: codecov/codecov-action@v5
+ with:
+ files: build/logs/clover.xml
+ flags: php
+ fail_ci_if_error: false
+ handle_no_reports_found: true
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ - name: Publish test results
+ if: ${{ matrix.coverage && always() }}
+ continue-on-error: true
+ uses: EnricoMi/publish-unit-test-result-action@v2
+ with:
+ files: build/logs/junit.xml
+ check_name: Test results (PHPUnit)
+
+ - name: Upload coverage artifact
+ if: ${{ matrix.coverage && always() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-clover
+ path: build/logs/clover.xml
+ if-no-files-found: ignore
+
+ test-mysql:
+ name: PHP ${{ matrix.php }} / mysql
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.3', '8.4']
+
+ services:
+ mysql:
+ image: public.ecr.aws/docker/library/mysql:8.0
+ env:
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
+ MYSQL_DATABASE: tipsy
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd="mysqladmin ping -h 127.0.0.1"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=5
+
+ env:
+ DB: mysql
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: pdo, pdo_mysql, pdo_pgsql, pdo_sqlite
+ coverage: none
+ ini-values: short_open_tag=On
+ tools: composer:v2
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
+
+ - name: Cache Composer packages
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-${{ matrix.php }}-composer-
+ ${{ runner.os }}-php-composer-
+
+ - name: Validate composer.json
+ run: composer validate --strict --no-check-publish
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress --no-interaction
+
+ - name: Start built-in server
+ run: php -S 127.0.0.1:8000 -t tests/web > /tmp/tipsy-php-server.log 2>&1 &
+
+ - name: Wait for built-in server
+ run: |
+ for i in $(seq 1 30); do
+ if curl -sf "http://127.0.0.1:8000/" >/dev/null 2>&1 || curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:8000/" | grep -Eq '^[0-9]+$'; then
+ exit 0
+ fi
+ sleep 0.2
+ done
+ echo "PHP built-in server failed to start" >&2
+ cat /tmp/tipsy-php-server.log >&2 || true
+ exit 1
+
+ - name: Run tests
+ run: composer test
+
+ # Aggregate gate so release only needs one `needs` entry.
+ test:
+ name: Tests passed
+ runs-on: ubuntu-latest
+ needs: [test-pgsql, test-mysql]
+ steps:
+ - run: echo "All PHP/DB matrix jobs passed."
+
+ release_check:
+ name: Release check (semantic-release dry-run)
+ runs-on: ubuntu-latest
+ if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') && github.event_name == 'push'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ - name: Dry-run (read-only config)
+ id: semantic
+ uses: cycjimmy/semantic-release-action@v4
+ with:
+ dry_run: true
+ semantic_version: 25
+ extra_plugins: |
+ @semantic-release/commit-analyzer@^13.0.0
+ @semantic-release/release-notes-generator@^14.1.0
+ conventional-changelog-conventionalcommits@^8.0.0
+ env:
+ TIPSY_SEMANTIC_RELEASE_CI: "1"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Report release status
+ run: |
+ if [ "${{ steps.semantic.outputs.new_release_published }}" != "true" ]; then
+ echo "::notice::No version bump in these commits — skipping the release check (no feat/fix/perf/BREAKING CHANGE per conventional commits)."
+ echo "No incremental release would be triggered; skipping rather than failing."
+ exit 0
+ fi
+ echo "Release would be triggered — check passed (next: ${{ steps.semantic.outputs.new_release_version }})."
+
+ release:
+ name: Release (prerelease branch + GitHub API)
+ needs: [test, release_check]
+ if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') && github.event_name == 'push'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ - name: Get next version (semantic-release dry-run)
+ id: next_version_semantic
+ uses: cycjimmy/semantic-release-action@v4
+ with:
+ dry_run: true
+ semantic_version: 25
+ extra_plugins: |
+ @semantic-release/commit-analyzer@^13.0.0
+ @semantic-release/release-notes-generator@^14.1.0
+ conventional-changelog-conventionalcommits@^8.0.0
+ env:
+ TIPSY_SEMANTIC_RELEASE_CI: "1"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Export next version for release steps
+ id: next_version
+ run: |
+ VERSION="${{ steps.next_version_semantic.outputs.new_release_version }}"
+ if [ -z "$VERSION" ]; then
+ echo "::notice::No version bump in these commits — skipping the release/publish steps."
+ echo "published=false" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ echo "published=true" >> "$GITHUB_OUTPUT"
+ echo "Next version: $VERSION"
+
+ - name: Create or update release branch and push
+ if: steps.next_version.outputs.published == 'true'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ BRANCH="release/v${{ steps.next_version.outputs.version }}"
+ git checkout -B "$BRANCH"
+ git push origin "$BRANCH" --force
+
+ - name: Generate release notes from commits
+ if: steps.next_version.outputs.published == 'true'
+ run: |
+ LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
+ if [ -n "$LAST_TAG" ]; then
+ CHANGELOG=$(git log "$LAST_TAG..HEAD" --pretty=format:"- %s (%h)" --no-merges 2>/dev/null || echo "")
+ else
+ CHANGELOG=$(git log -30 --pretty=format:"- %s (%h)" --no-merges 2>/dev/null || echo "")
+ fi
+ [ -z "$CHANGELOG" ] && CHANGELOG="- No commits to list"
+ {
+ echo "## Changes"
+ echo ""
+ echo "$CHANGELOG"
+ echo ""
+ echo "---"
+ echo ""
+ echo "Promote this prerelease to a **full release** to publish \`tipsyphp/tipsy\` to Packagist."
+ } > release-body.md
+
+ - name: Create or update GitHub prerelease via API
+ if: steps.next_version.outputs.published == 'true'
+ id: create_release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ VERSION: ${{ steps.next_version.outputs.version }}
+ run: |
+ TAG="v${VERSION}"
+ BRANCH="release/v${VERSION}"
+ BODY=$(cat release-body.md)
+ RESP=$(curl -s -w "\n%{http_code}" -X POST \
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/${{ github.repository }}/releases" \
+ -d "{\"tag_name\":\"$TAG\",\"target_commitish\":\"$BRANCH\",\"name\":\"$TAG\",\"body\":$(echo "$BODY" | jq -Rs .),\"prerelease\":true}")
+ HTTP_CODE=$(echo "$RESP" | tail -n1)
+ BODY_RESP=$(echo "$RESP" | sed '$d')
+ if [ "$HTTP_CODE" = "201" ]; then
+ echo "Created prerelease $TAG"
+ RELEASE_ID=$(echo "$BODY_RESP" | jq -r .id)
+ elif [ "$HTTP_CODE" = "422" ]; then
+ RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
+ "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r .id)
+ if [ "$RELEASE_ID" != "null" ] && [ -n "$RELEASE_ID" ]; then
+ curl -s -X PATCH \
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID" \
+ -d "{\"target_commitish\":\"$BRANCH\",\"body\":$(echo "$BODY" | jq -Rs .),\"prerelease\":true}"
+ echo "Updated existing prerelease $TAG"
+ else
+ echo "Release creation failed (422) and could not find release by tag"
+ exit 1
+ fi
+ else
+ echo "Unexpected response: $HTTP_CODE"
+ echo "$BODY_RESP"
+ exit 1
+ fi
+ echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
+
+ - name: Download coverage artifact
+ if: steps.next_version.outputs.published == 'true'
+ continue-on-error: true
+ uses: actions/download-artifact@v4
+ with:
+ name: coverage-clover
+ path: coverage
+
+ - name: Upload coverage clover to prerelease
+ if: steps.next_version.outputs.published == 'true'
+ continue-on-error: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ RELEASE_ID="${{ steps.create_release.outputs.release_id }}"
+ REPO="${{ github.repository }}"
+ CLOVER="coverage/clover.xml"
+ if [ ! -f "$CLOVER" ]; then
+ echo "No clover.xml artifact — skipping asset upload"
+ exit 0
+ fi
+ curl -s -X POST \
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ -H "Content-Type: application/xml" \
+ --data-binary @"$CLOVER" \
+ "https://uploads.github.com/repos/${REPO}/releases/${RELEASE_ID}/assets?name=clover.xml&label=PHPUnit%20coverage%20(clover)"
diff --git a/.github/workflows/packagist-release.yml b/.github/workflows/packagist-release.yml
new file mode 100644
index 0000000..122b961
--- /dev/null
+++ b/.github/workflows/packagist-release.yml
@@ -0,0 +1,101 @@
+# Publish tipsyphp/tipsy to Packagist when a GitHub release is promoted from prerelease to full release.
+# Packagist syncs from the git tag; this job forces an update ping and links the Packagist URL on the release.
+
+name: Packagist release
+
+on:
+ release:
+ types: [published, edited]
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "Release tag to (re)publish, e.g. v2.0.0"
+ required: true
+ type: string
+
+permissions:
+ contents: write
+
+jobs:
+ publish:
+ name: Notify Packagist
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Resolve tag
+ id: tag
+ run: |
+ TAG="${{ github.event.release.tag_name || inputs.tag }}"
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
+ echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
+
+ - name: Update Packagist package
+ env:
+ PACKAGIST_USERNAME: ${{ secrets.PACKAGIST_USERNAME }}
+ PACKAGIST_TOKEN: ${{ secrets.PACKAGIST_TOKEN }}
+ run: |
+ if [ -z "$PACKAGIST_USERNAME" ] || [ -z "$PACKAGIST_TOKEN" ]; then
+ echo "::warning::PACKAGIST_USERNAME / PACKAGIST_TOKEN secrets are not set."
+ echo "Packagist may still sync via the GitHub Service Hook if configured."
+ echo "Add secrets from https://packagist.org/profile/ (username + API token)."
+ exit 0
+ fi
+ RESP=$(curl -sS -w "\n%{http_code}" -X POST \
+ -H "Content-Type: application/json" \
+ "https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_TOKEN}" \
+ -d '{"repository":{"url":"https://github.com/tipsyphp/tipsy"}}')
+ HTTP_CODE=$(echo "$RESP" | tail -n1)
+ BODY=$(echo "$RESP" | sed '$d')
+ echo "Packagist response ($HTTP_CODE): $BODY"
+ if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "202" ]; then
+ echo "::error::Packagist update failed"
+ exit 1
+ fi
+
+ - name: Verify version on Packagist
+ continue-on-error: true
+ env:
+ VERSION: ${{ steps.tag.outputs.version }}
+ TAG: ${{ steps.tag.outputs.tag }}
+ run: |
+ # Packagist indexing can lag a few seconds; retry briefly.
+ for i in 1 2 3 4 5 6; do
+ if curl -sS "https://repo.packagist.org/p2/tipsyphp/tipsy.json" \
+ | jq -e --arg v "$TAG" --arg v2 "$VERSION" \
+ '.packages["tipsyphp/tipsy"][] | select(.version == $v or .version == $v2 or .version_normalized == ($v2 + ".0") or .version == ("v" + $v2))' \
+ >/dev/null; then
+ echo "Found tipsyphp/tipsy@$TAG on Packagist"
+ exit 0
+ fi
+ echo "Waiting for Packagist to index $TAG (attempt $i/6)..."
+ sleep 10
+ done
+ echo "::warning::Could not confirm $TAG on Packagist yet — check https://packagist.org/packages/tipsyphp/tipsy"
+
+ - name: Update release description with Packagist URL
+ if: github.event_name == 'release'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ TAG: ${{ steps.tag.outputs.tag }}
+ VERSION: ${{ steps.tag.outputs.version }}
+ run: |
+ PACKAGIST_URL="https://packagist.org/packages/tipsyphp/tipsy#${TAG}"
+ RELEASE_ID="${{ github.event.release.id }}"
+ REPO="${{ github.repository }}"
+ CURRENT_BODY=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
+ "https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""')
+ if [[ "$CURRENT_BODY" == *"packagist.org"* ]]; then
+ echo "Release body already has Packagist link"
+ else
+ NEW_BODY="${CURRENT_BODY}
+
+ ---
+ Published to Packagist: ${PACKAGIST_URL}
+ Install: \`composer require tipsyphp/tipsy:${VERSION}\`"
+ curl -s -X PATCH \
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}" \
+ -d "$(printf '%s' "$NEW_BODY" | jq -Rs '{body: .}')"
+ fi
diff --git a/.gitignore b/.gitignore
index de0179d..9388183 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
/vendor/
.DS_Store
nohup.out
+tests/.phpunit.cache/
+/build/
+examples/*/vendor/
+examples/*/composer.lock
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a1cae3f..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-sudo: false
-
-language:
- - php
-
-php:
- - 7.1
-
-env:
- - DB=mysql
- - DB=pgsql
-
-before_script:
- - mkdir build
- - phpenv config-add tests/php.ini
- - mysql -e "create database IF NOT EXISTS tipsy;" -uroot;
- - psql -c 'create database tipsy;' -U postgres
- - composer self-update
- - composer install
- - nohup php -S localhost:8000 -c tests/php.ini -t tests/web &
-
-script:
- - phpunit --configuration tests/phpunit.xml
-
-after_success:
- - bash <(curl -s https://codecov.io/bash)
- - php vendor/bin/coveralls -v
-
-addons:
- postgresql: "9.4"
-
-services:
- - postgresql
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 611b8b5..860b1f7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,10 +4,7 @@
Found a bug? Got a suggestion? Feel free to check out the [Issue Tracker](https://github.com/tipsyphp/tipsy/issues) to make an issue.
##### Pull Requests
-If you are really awesome, fix an issue by making a pull request. PRs are glady accepted on any of the projects on [tipsyphp](https://github.com/tipsyphp). When contributing to [tipsyphp/tipsy](https://github.com/tipsyphp/tipsy), please make sure your builds pass the unit tests using either [Travis CI](https://travis-ci.org/) or running the tests localy from the [Test Script](https://github.com/tipsyphp/tipsy/blob/master/tests/test.sh) using [PHPUnit](https://phpunit.de/).
+If you are really awesome, fix an issue by making a pull request. PRs are gladly accepted on any of the projects on [tipsyphp](https://github.com/tipsyphp). When contributing to [tipsyphp/tipsy](https://github.com/tipsyphp/tipsy), please make sure your builds pass [GitHub Actions CI](https://github.com/tipsyphp/tipsy/actions) or run the suite locally with `composer test` (PHPUnit 11, PHP 8.3+). Prefer [conventional commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `perf:`, `feat!:`) so releases can be cut automatically — see [docs/RELEASE.md](docs/RELEASE.md).
##### License
Tipsy is licensed under the [MIT License](https://github.com/tipsyphp/tipsy/blob/master/LICENSE).
-
-##### More Info
-For general questions join the [Tipsy Slack](https://tipsy-slack.herokuapp.com/) channel.
diff --git a/README.md b/README.md
index c2b17ce..5ef6c23 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,25 @@
-Tipsy is an MVW (Model, View, Whatever) PHP micro framework inspired by [AngularJS](https://angularjs.org/). It provides a very lightweight, easy to use interface for websites, rest apis, and dependency injection.
+Tipsy is an MVW (Model, View, Whatever) PHP micro framework inspired by [AngularJS](https://angularjs.org/). It provides a very lightweight, easy to use interface for websites, REST APIs, and dependency injection.
+**Tipsy 2.0** targets **PHP 8.3+** with a typed core, **zero runtime Composer dependencies**, and a **PostgreSQL-first** database layer (MySQL and SQLite still supported). See [UPGRADING.md](UPGRADING.md) for breaking changes from 0.11.x.
[](https://packagist.org/packages/tipsyphp/tipsy)
-[](https://travis-ci.org/tipsyphp/tipsy)
-[](https://coveralls.io/github/tipsyphp/tipsy?branch=master)
-[](https://tipsy-slack.herokuapp.com/)
+[](https://github.com/tipsyphp/tipsy/actions/workflows/ci.yml)
+[](https://codecov.io/gh/tipsyphp/tipsy)
---
### Example Usage
-See [Examples](https://github.com/tipsyphp/tipsy/wiki/Examples) for more detailed examples. See [Documentation](https://github.com/tipsyphp/tipsy/wiki) for more information.
+See [examples/](examples/) in this repo for runnable apps. See [Documentation](https://github.com/tipsyphp/tipsy/wiki) for more information.
###### index.php
```php
$app->home(function($View) {
- $View->display('index', [user => 'crystal']);
+ $View->display('index', ['user' => 'crystal']);
});
```
@@ -35,5 +35,14 @@ $app->home(function($View) {
To install using composer use the command below. For additional installation information see [Installation](https://github.com/tipsyphp/tipsy/wiki/Installation).
```sh
-composer require tipsyphp/tipsy
+composer require tipsyphp/tipsy:^2.0
```
+
+### Development
+
+```sh
+composer install
+composer test
+```
+
+CI runs PHP 8.3/8.4 × MySQL/PostgreSQL, then on `master`/`main` can open a GitHub prerelease (promote it to publish to Packagist). See [docs/RELEASE.md](docs/RELEASE.md). Performance notes live in [benchmarks/](benchmarks/).
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000..4b638ae
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,70 @@
+# Upgrading to Tipsy 2.0
+
+Tipsy 2.0 is a hard break from the 0.11.x line. It targets **PHP 8.3+**, uses strict typing throughout, and drops PHP 5/7-era quirks. There are **no runtime Composer dependencies** — same thin-bootstrap identity as 0.x.
+
+## Requirements
+
+- PHP `>= 8.3`
+- PHPUnit `^11` for development
+
+## Breaking changes
+
+| Change | Migration |
+|--------|-----------|
+| PHP floor raised to 8.3 | Upgrade the runtime before upgrading Tipsy |
+| `Router::aliass()` renamed to `aliases()` | Rename call sites; the typo method is gone |
+| Route `:param` matches one path segment (`[^/]+`), not `.*` | Use an explicit regex route if you need multi-segment captures |
+| Unknown DI dependencies are `null` (was `false`) | Replace `=== false` checks with `=== null` / falsy checks |
+| `App::run()` / `App::start()` return `void` | Do not use their return values |
+| `Http` verifies SSL by default | Pass `skipSslVerify => true` for self-signed/local HTTPS |
+| Curly-brace string offsets and bareword array keys | Use `$str[0]` and quoted keys (`['id' => 1]`) |
+| Typed constructors / method signatures | Align subclasses (`Controller::init`, `Middleware::run`, etc.) |
+| Default DB driver is **pgsql** (was mysql) | Set `driver=mysql` (or a `mysql://` URL) explicitly for MySQL |
+| Resource SQL is dialect-aware | No need for `MysqlToPgsql` on normal Resource usage; opt in only for raw MySQL SQL on Postgres |
+
+## Database
+
+Tipsy 2.0 defaults to **PostgreSQL**. Supported drivers: `pgsql` (aliases: `postgres`, `postgresql`), `mysql`, `sqlite`.
+
+```ini
+[db]
+host=127.0.0.1
+user=postgres
+pass=secret
+database=app
+driver=pgsql
+```
+
+MySQL:
+
+```ini
+[db]
+host=127.0.0.1
+user=root
+pass=
+database=app
+driver=mysql
+```
+
+Optional: if you still write raw MySQL-flavored SQL against Postgres, register the compatibility layer:
+
+```php
+$app->service('Db', Tipsy\Db\MysqlToPgsql::class);
+```
+
+## Intentional non-goals (2.0)
+
+- No PSR-7 / PSR-11 / PSR-15 adoption in core
+- No new ORM, template engine, or heavy middleware stack
+- No compatibility shims for PHP < 8.3
+
+## Behavioral improvements worth knowing
+
+- Route patterns are precompiled at construction time
+- DI caches `ReflectionFunction` parameter lists per closure
+- `files` autoload still loads `src/Tipsy.php` so the `t` class alias and `getallheaders()` polyfill remain available
+- Boolean model properties stay PHP `bool` after MySQL save (DB still stores `0`/`1`)
+
+## Examples
+
+In-repo examples use a Composer **path** repository pointing at the monorepo root for local development. After Packagist publishes `tipsyphp/tipsy` `^2.0`, deployable apps should depend on the Packagist constraint instead of the path repo.
diff --git a/benchmarks/README.md b/benchmarks/README.md
new file mode 100644
index 0000000..43de1a4
--- /dev/null
+++ b/benchmarks/README.md
@@ -0,0 +1,38 @@
+# Tipsy Hello World benchmark notes
+
+Tipsy’s historic “fast” claim came from [kenjis/php-framework-benchmark](https://github.com/kenjis/php-framework-benchmark) Hello World measurements (thin bootstrap, low memory) — not a formal award.
+
+## Goal for 2.0
+
+Success is **no material regression** on modern PHP versus a typed Tipsy 2.0 baseline — not a claimed 2–5× gain from micro-optimizations. Platform wins (PHP 8.3/8.4 + OPcache) dominate.
+
+## App under test
+
+[`hello-world/index.php`](hello-world/index.php) — one route, JSON response, zero views/DB.
+
+## How to run
+
+```bash
+# From repo root (PHP 8.3+ with OPcache recommended)
+composer install
+
+# Terminal A
+php -d opcache.enable_cli=1 -S 127.0.0.1:8088 -t benchmarks/hello-world
+
+# Terminal B — wrk (preferred) or ab
+wrk -t2 -c50 -d10s http://127.0.0.1:8088/
+# or
+ab -n 5000 -c 50 http://127.0.0.1:8088/
+```
+
+Peak memory for a single request:
+
+```bash
+php -d memory_limit=64M benchmarks/hello-world/memory.php
+```
+
+## Captured local sample (2026-08-07)
+
+Environment: PHP 8.3.33 NTS, macOS arm64, built-in server, no `wrk` installed in CI image — used `ab` when available.
+
+See [`RESULTS.md`](RESULTS.md) for the latest numbers checked into the branch. Re-run after significant router/DI changes.
diff --git a/benchmarks/RESULTS.md b/benchmarks/RESULTS.md
new file mode 100644
index 0000000..a77fb4c
--- /dev/null
+++ b/benchmarks/RESULTS.md
@@ -0,0 +1,38 @@
+# Benchmark results
+
+Host: macOS arm64, PHP 8.3.33 NTS, ApacheBench (`ab -n 5000 -c 50`), PHP built-in server on `127.0.0.1:8088`
+
+## Tipsy 2.0 Hello World
+
+| Revision | RPS (mean of runs) | Peak memory |
+|----------|--------------------|-------------|
+| Pre-optimization (`b726b75`) | **13929** | 4 MB |
+| Hot-path optimizations (this) | **14800–15215** (~6–9% ↑) | 4 MB |
+
+Sample after optimizations (3 consecutive runs):
+
+```
+Requests per second: 14882.37 [#/sec]
+Requests per second: 15215.42 [#/sec]
+Requests per second: 14434.60 [#/sec]
+```
+
+```bash
+ab -n 5000 -c 50 http://127.0.0.1:8088/
+php benchmarks/hello-world/memory.php
+```
+
+## What changed
+
+- Exact/static routes use string compare instead of `preg_match`
+- HTTP methods precomputed uppercase; request method cached
+- Router match iterates backwards (no `array_reverse` allocation)
+- DI skips rebuilding an `$avail` list (`_getDependency` already resolves unknowns)
+- `Request::path` avoids `preg_replace` for script/dir stripping
+- Removed unused `App` id/`random_bytes` on construct
+
+## vs origin/master (0.11.x)
+
+`origin/master` **cannot run on PHP 8.3+** (fatal curly-brace string offsets). Same-runtime comparison is not possible on this toolchain.
+
+Historical reference (external, PHP 7 era): kenjis Hello World had tipsy-0.10 around ~1377 req/s on that harness — different hardware/runtime.
diff --git a/benchmarks/hello-world/index.php b/benchmarks/hello-world/index.php
new file mode 100644
index 0000000..a3e17e4
--- /dev/null
+++ b/benchmarks/hello-world/index.php
@@ -0,0 +1,14 @@
+=5.5.0"
+ "php": ">=8.3"
},
"require-dev": {
- "satooshi/php-coveralls": "0.7.0",
- "phpunit/phpunit": "3.7.*"
+ "phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": {
"Tipsy\\": "src/"
},
- "files": [
- "src/Tipsy.php"
- ]
+ "files": ["src/Tipsy.php"]
+ },
+ "scripts": {
+ "test": "php -d short_open_tag=1 vendor/bin/phpunit --configuration tests/phpunit.xml --no-coverage",
+ "test:coverage": "php -d short_open_tag=1 vendor/bin/phpunit --configuration tests/phpunit.xml"
},
"archive": {
- "exclude": ["/tests", "/.travis.yml", "/.gitignore"]
+ "exclude": ["/tests", "/examples", "/benchmarks", "/.github", "/.gitignore"]
}
}
diff --git a/composer.lock b/composer.lock
index f2b8f04..4f845b3 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,189 +1,155 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3518b22d9b248a67f8a88bc2bba2ca5b",
+ "content-hash": "e70d39f2f7a97617e388bd02af8f25c5",
"packages": [],
"packages-dev": [
{
- "name": "guzzle/guzzle",
- "version": "v3.9.3",
+ "name": "myclabs/deep-copy",
+ "version": "1.13.4",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/guzzle3.git",
- "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
- "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "php": ">=5.3.3",
- "symfony/event-dispatcher": "~2.1"
- },
- "replace": {
- "guzzle/batch": "self.version",
- "guzzle/cache": "self.version",
- "guzzle/common": "self.version",
- "guzzle/http": "self.version",
- "guzzle/inflection": "self.version",
- "guzzle/iterator": "self.version",
- "guzzle/log": "self.version",
- "guzzle/parser": "self.version",
- "guzzle/plugin": "self.version",
- "guzzle/plugin-async": "self.version",
- "guzzle/plugin-backoff": "self.version",
- "guzzle/plugin-cache": "self.version",
- "guzzle/plugin-cookie": "self.version",
- "guzzle/plugin-curlauth": "self.version",
- "guzzle/plugin-error-response": "self.version",
- "guzzle/plugin-history": "self.version",
- "guzzle/plugin-log": "self.version",
- "guzzle/plugin-md5": "self.version",
- "guzzle/plugin-mock": "self.version",
- "guzzle/plugin-oauth": "self.version",
- "guzzle/service": "self.version",
- "guzzle/stream": "self.version"
+ "php": "^7.1 || ^8.0"
},
- "require-dev": {
- "doctrine/cache": "~1.3",
- "monolog/monolog": "~1.0",
- "phpunit/phpunit": "3.7.*",
- "psr/log": "~1.0",
- "symfony/class-loader": "~2.1",
- "zendframework/zend-cache": "2.*,<2.3",
- "zendframework/zend-log": "2.*,<2.3"
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
- "suggest": {
- "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.9-dev"
- }
- },
"autoload": {
- "psr-0": {
- "Guzzle": "src/",
- "Guzzle\\Tests": "tests/"
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "authors": [
- {
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
- },
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+ },
+ "funding": [
{
- "name": "Guzzle Community",
- "homepage": "https://github.com/guzzle/guzzle/contributors"
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
}
],
- "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
- "homepage": "http://guzzlephp.org/",
- "keywords": [
- "client",
- "curl",
- "framework",
- "http",
- "http client",
- "rest",
- "web service"
- ],
- "time": "2015-03-18T18:23:50+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
- "name": "phpunit/php-code-coverage",
- "version": "1.2.18",
+ "name": "nikic/php-parser",
+ "version": "v5.8.0",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b"
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
- "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
+ "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "phpunit/php-file-iterator": ">=1.3.0@stable",
- "phpunit/php-text-template": ">=1.2.0@stable",
- "phpunit/php-token-stream": ">=1.1.3,<1.3.0"
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
},
"require-dev": {
- "phpunit/phpunit": "3.7.*@dev"
- },
- "suggest": {
- "ext-dom": "*",
- "ext-xdebug": ">=2.0.5"
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
},
+ "bin": [
+ "bin/php-parse"
+ ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2.x-dev"
+ "dev-master": "5.x-dev"
}
},
"autoload": {
- "classmap": [
- "PHP/"
- ]
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
},
"notification-url": "https://packagist.org/downloads/",
- "include-path": [
- ""
- ],
"license": [
"BSD-3-Clause"
],
"authors": [
{
- "name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
+ "name": "Nikita Popov"
}
],
- "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
- "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "description": "A PHP parser written in PHP",
"keywords": [
- "coverage",
- "testing",
- "xunit"
+ "parser",
+ "php"
],
- "time": "2014-09-02T10:13:14+00:00"
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0"
+ },
+ "time": "2026-07-04T14:30:18+00:00"
},
{
- "name": "phpunit/php-file-iterator",
- "version": "1.4.5",
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -196,36 +162,51 @@
"BSD-3-Clause"
],
"authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
}
],
- "description": "FilterIterator implementation that filters files based on a list of suffixes.",
- "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
- "keywords": [
- "filesystem",
- "iterator"
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
],
- "time": "2017-11-27T13:52:08+00:00"
+ "time": "2024-03-03T12:33:53+00:00"
},
{
- "name": "phpunit/php-text-template",
- "version": "1.2.1",
+ "name": "phar-io/version",
+ "version": "3.2.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
@@ -238,43 +219,69 @@
"BSD-3-Clause"
],
"authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
- "role": "lead"
+ "role": "Developer"
}
],
- "description": "Simple template engine.",
- "homepage": "https://github.com/sebastianbergmann/php-text-template/",
- "keywords": [
- "template"
- ],
- "time": "2015-06-21T13:50:34+00:00"
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
},
{
- "name": "phpunit/php-timer",
- "version": "1.0.9",
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.12",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56",
+ "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.7.0",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.1",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.2",
+ "theseer/tokenizer": "^1.3.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ "phpunit/phpunit": "^11.5.46"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-main": "11.0.x-dev"
}
},
"autoload": {
@@ -289,121 +296,151 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
- "description": "Utility class for timing",
- "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
- "timer"
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
+ "type": "tidelift"
+ }
],
- "time": "2017-02-26T11:10:40+00:00"
+ "time": "2025-12-24T07:01:01+00:00"
},
{
- "name": "phpunit/php-token-stream",
- "version": "1.2.2",
+ "name": "phpunit/php-file-iterator",
+ "version": "5.1.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32"
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32",
- "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903",
+ "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903",
"shasum": ""
},
"require": {
- "ext-tokenizer": "*",
- "php": ">=5.3.3"
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2-dev"
+ "dev-main": "5.1-dev"
}
},
"autoload": {
"classmap": [
- "PHP/"
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
- "include-path": [
- ""
- ],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
- "description": "Wrapper around PHP's tokenizer extension.",
- "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
- "tokenizer"
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
+ "type": "tidelift"
+ }
],
- "time": "2014-03-03T05:10:30+00:00"
+ "time": "2026-02-02T13:52:54+00:00"
},
{
- "name": "phpunit/phpunit",
- "version": "3.7.38",
+ "name": "phpunit/php-invoker",
+ "version": "5.0.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6"
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6",
- "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
"shasum": ""
},
"require": {
- "ext-ctype": "*",
- "ext-dom": "*",
- "ext-json": "*",
- "ext-pcre": "*",
- "ext-reflection": "*",
- "ext-spl": "*",
- "php": ">=5.3.3",
- "phpunit/php-code-coverage": "~1.2",
- "phpunit/php-file-iterator": "~1.3",
- "phpunit/php-text-template": "~1.1",
- "phpunit/php-timer": "~1.0",
- "phpunit/phpunit-mock-objects": "~1.2",
- "symfony/yaml": "~2.0"
+ "php": ">=8.2"
},
"require-dev": {
- "pear-pear.php.net/pear": "1.9.4"
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
},
"suggest": {
- "phpunit/php-invoker": "~1.1"
+ "ext-pcntl": "*"
},
- "bin": [
- "composer/bin/phpunit"
- ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.7.x-dev"
+ "dev-main": "5.0-dev"
}
},
"autoload": {
"classmap": [
- "PHPUnit/"
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
- "include-path": [
- "",
- "../../symfony/yaml/"
- ],
"license": [
"BSD-3-Clause"
],
@@ -414,635 +451,1335 @@
"role": "lead"
}
],
- "description": "The PHP Unit Testing framework.",
- "homepage": "http://www.phpunit.de/",
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
"keywords": [
- "phpunit",
- "testing",
- "xunit"
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2014-10-17T09:04:17+00:00"
+ "time": "2024-07-03T05:07:44+00:00"
},
{
- "name": "phpunit/phpunit-mock-objects",
- "version": "1.2.3",
+ "name": "phpunit/php-text-template",
+ "version": "4.0.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875"
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875",
- "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "phpunit/php-text-template": ">=1.1.1@stable"
+ "php": ">=8.2"
},
- "suggest": {
- "ext-soap": "*"
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
"autoload": {
"classmap": [
- "PHPUnit/"
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
- "include-path": [
- ""
- ],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
- "description": "Mock Object library for PHPUnit",
- "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
- "mock",
- "xunit"
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2013-01-13T10:24:48+00:00"
+ "time": "2024-07-03T05:08:43+00:00"
},
{
- "name": "psr/log",
- "version": "1.0.2",
+ "name": "phpunit/php-timer",
+ "version": "7.0.1",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
- "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-main": "7.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Psr\\Log\\": "Psr/Log/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
- "log",
- "psr",
- "psr-3"
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2016-10-10T12:19:37+00:00"
+ "time": "2024-07-03T05:09:35+00:00"
},
{
- "name": "satooshi/php-coveralls",
- "version": "v0.7.0",
+ "name": "phpunit/phpunit",
+ "version": "11.5.56",
"source": {
"type": "git",
- "url": "https://github.com/satooshi/php-coveralls.git",
- "reference": "cad8736dd2ee4419221044d4f154f7e93d77ba4a"
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/cad8736dd2ee4419221044d4f154f7e93d77ba4a",
- "reference": "cad8736dd2ee4419221044d4f154f7e93d77ba4a",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256",
+ "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256",
"shasum": ""
},
"require": {
+ "ext-dom": "*",
+ "ext-filter": "*",
"ext-json": "*",
- "ext-simplexml": "*",
- "guzzle/guzzle": "^2.8|^3.0",
- "php": ">=5.3.3",
- "psr/log": "^1.0",
- "symfony/config": "^2.4|^3.0",
- "symfony/console": "^2.1|^3.0",
- "symfony/stopwatch": "^2.2|^3.0",
- "symfony/yaml": "^2.1|^3.0"
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.4",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.12",
+ "phpunit/php-file-iterator": "^5.1.1",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.3",
+ "sebastian/comparator": "^6.3.3",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.1",
+ "sebastian/exporter": "^6.3.2",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/recursion-context": "^6.0.3",
+ "sebastian/type": "^5.1.3",
+ "sebastian/version": "^5.0.2",
+ "staabm/side-effects-detector": "^1.0.5"
},
"suggest": {
- "symfony/http-kernel": "Allows Symfony integration"
+ "ext-soap": "To be able to generate mocks based on WSDL files"
},
"bin": [
- "bin/coveralls"
+ "phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "0.7-dev"
+ "dev-main": "11.5-dev"
}
},
"autoload": {
- "psr-4": {
- "Satooshi\\": "src/Satooshi/"
- }
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Kitamura Satoshi",
- "email": "with.no.parachute@gmail.com",
- "homepage": "https://www.facebook.com/satooshi.jp"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
- "description": "PHP client library for Coveralls API",
- "homepage": "https://github.com/satooshi/php-coveralls",
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
"keywords": [
- "ci",
- "coverage",
- "github",
- "test"
+ "phpunit",
+ "testing",
+ "xunit"
],
- "time": "2015-12-14T17:50:37+00:00"
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsoring.html",
+ "type": "other"
+ }
+ ],
+ "time": "2026-07-06T14:52:39+00:00"
},
{
- "name": "symfony/config",
- "version": "v3.4.3",
+ "name": "sebastian/cli-parser",
+ "version": "3.0.2",
"source": {
"type": "git",
- "url": "https://github.com/symfony/config.git",
- "reference": "cfd5c972f7b4992a5df41673d25d980ab077aa5b"
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/cfd5c972f7b4992a5df41673d25d980ab077aa5b",
- "reference": "cfd5c972f7b4992a5df41673d25d980ab077aa5b",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/filesystem": "~2.8|~3.0|~4.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.3",
- "symfony/finder": "<3.3"
+ "php": ">=8.2"
},
"require-dev": {
- "symfony/dependency-injection": "~3.3|~4.0",
- "symfony/finder": "~3.3|~4.0",
- "symfony/yaml": "~3.0|~4.0"
- },
- "suggest": {
- "symfony/yaml": "To use the yaml reference dumper"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Config\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
+ },
+ "funding": [
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "Symfony Config Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:37:34+00:00"
+ "time": "2024-07-03T04:41:36+00:00"
},
{
- "name": "symfony/console",
- "version": "v3.4.3",
+ "name": "sebastian/code-unit",
+ "version": "3.0.3",
"source": {
"type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "8394c8ef121949e8f858f13bc1e34f05169e4e7d"
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/8394c8ef121949e8f858f13bc1e34f05169e4e7d",
- "reference": "8394c8ef121949e8f858f13bc1e34f05169e4e7d",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64",
+ "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/debug": "~2.8|~3.0|~4.0",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.4",
- "symfony/process": "<3.3"
+ "php": ">=8.2"
},
"require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~3.3|~4.0",
- "symfony/dependency-injection": "~3.4|~4.0",
- "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
- "symfony/lock": "~3.4|~4.0",
- "symfony/process": "~3.3|~4.0"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/lock": "",
- "symfony/process": ""
+ "phpunit/phpunit": "^11.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3"
+ },
+ "funding": [
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "Symfony Console Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:37:34+00:00"
+ "time": "2025-03-19T07:56:08+00:00"
},
{
- "name": "symfony/debug",
- "version": "v4.0.3",
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/debug.git",
- "reference": "9ae4223a661b56a9abdce144de4886cca37f198f"
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/9ae4223a661b56a9abdce144de4886cca37f198f",
- "reference": "9ae4223a661b56a9abdce144de4886cca37f198f",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
"shasum": ""
},
"require": {
- "php": "^7.1.3",
- "psr/log": "~1.0"
- },
- "conflict": {
- "symfony/http-kernel": "<3.4"
+ "php": ">=8.2"
},
"require-dev": {
- "symfony/http-kernel": "~3.4|~4.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Debug\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
+ },
+ "funding": [
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "Symfony Debug Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T17:15:19+00:00"
+ "time": "2024-07-03T04:45:54+00:00"
},
{
- "name": "symfony/event-dispatcher",
- "version": "v2.8.33",
+ "name": "sebastian/comparator",
+ "version": "6.3.3",
"source": {
"type": "git",
- "url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "d64be24fc1eba62f9daace8a8918f797fc8e87cc"
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d64be24fc1eba62f9daace8a8918f797fc8e87cc",
- "reference": "d64be24fc1eba62f9daace8a8918f797fc8e87cc",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9",
+ "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
},
"require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "^2.0.5|~3.0.0",
- "symfony/dependency-injection": "~2.6|~3.0.0",
- "symfony/expression-language": "~2.6|~3.0.0",
- "symfony/stopwatch": "~2.3|~3.0.0"
+ "phpunit/phpunit": "^11.4"
},
"suggest": {
- "symfony/dependency-injection": "",
- "symfony/http-kernel": ""
+ "ext-bcmath": "For comparing BcMath\\Number objects"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.8-dev"
+ "dev-main": "6.3-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\EventDispatcher\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
},
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
}
],
- "description": "Symfony EventDispatcher Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:36:31+00:00"
- },
- {
- "name": "symfony/filesystem",
- "version": "v4.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/filesystem.git",
- "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed"
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-24T09:26:40+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/760e47a4ee64b4c48f4b30017011e09d4c0f05ed",
- "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
"shasum": ""
},
"require": {
- "php": "^7.1.3"
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Filesystem\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:49:50+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
},
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "Symfony Filesystem Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:38:00+00:00"
+ "time": "2024-07-03T04:53:05+00:00"
},
{
- "name": "symfony/polyfill-mbstring",
- "version": "v1.6.0",
+ "name": "sebastian/environment",
+ "version": "7.2.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
- "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4",
+ "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
},
"suggest": {
- "ext-mbstring": "For best performance"
+ "ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.6-dev"
+ "dev-main": "7.2-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
},
- "files": [
- "bootstrap.php"
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-21T11:55:47+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "70a298763b40b213ec087c51c739efcaa90bcd74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74",
+ "reference": "70a298763b40b213ec087c51c739efcaa90bcd74",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
},
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
}
],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
"keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
+ }
],
- "time": "2017-10-11T12:05:26+00:00"
+ "time": "2025-09-24T06:12:51+00:00"
},
{
- "name": "symfony/stopwatch",
- "version": "v3.4.3",
+ "name": "sebastian/global-state",
+ "version": "7.0.2",
"source": {
"type": "git",
- "url": "https://github.com/symfony/stopwatch.git",
- "reference": "c865551df7c17e63fc1f09f763db04387f91ae4d"
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/stopwatch/zipball/c865551df7c17e63fc1f09f763db04387f91ae4d",
- "reference": "c865551df7c17e63fc1f09f763db04387f91ae4d",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-main": "7.0-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Stopwatch\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:57:36+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:58:38+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:00:13+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:01:32+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc",
+ "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
},
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
}
],
- "description": "Symfony Stopwatch Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:37:34+00:00"
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-13T04:42:22+00:00"
},
{
- "name": "symfony/yaml",
- "version": "v2.8.33",
+ "name": "sebastian/type",
+ "version": "5.1.3",
"source": {
"type": "git",
- "url": "https://github.com/symfony/yaml.git",
- "reference": "be720fcfae4614df204190d57795351059946a77"
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77",
- "reference": "be720fcfae4614df204190d57795351059946a77",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
+ "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.8-dev"
+ "dev-main": "5.1-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Yaml\\": ""
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
},
- "exclude-from-classmap": [
- "/Tests/"
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-09T06:55:48+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-09T05:16:32+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+ },
+ "funding": [
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "url": "https://github.com/theseer",
+ "type": "github"
}
],
- "description": "Symfony Yaml Component",
- "homepage": "https://symfony.com",
- "time": "2018-01-03T07:36:31+00:00"
+ "time": "2025-11-17T20:03:58+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=5.5.0"
+ "php": ">=8.3"
},
- "platform-dev": []
+ "platform-dev": {},
+ "plugin-api-version": "2.9.0"
}
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
new file mode 100644
index 0000000..dccdefa
--- /dev/null
+++ b/docs/RELEASE.md
@@ -0,0 +1,112 @@
+# How to Release Tipsy
+
+Step-by-step guide. Do these in order.
+
+The pipeline mirrors [tish](https://github.com/tishlang/tish): CI creates a **prerelease**; you promote it to a full release to publish to Packagist.
+
+---
+
+## Before You Start: One-Time Setup
+
+### 1. GitHub Secrets (Settings → Secrets and variables → Actions)
+
+| Secret | How to get it |
+|--------|---------------|
+| `PACKAGIST_USERNAME` | Your Packagist username |
+| `PACKAGIST_TOKEN` | [packagist.org/profile](https://packagist.org/profile/) → Show API Token |
+| `CODECOV_TOKEN` | Optional — Codecov project token |
+
+Also ensure the Packagist package `tipsyphp/tipsy` has GitHub as its repository (Auto-Update / GitHub Service Hook).
+
+---
+
+## Every Release
+
+### Step 1: Commit with a release-triggering message
+
+Use [conventional commits](https://www.conventionalcommits.org/). Tipsy CI uses the
+**`conventionalcommits` preset** (not angular), so both of these cut a **major**:
+
+```
+feat!: Tipsy 2.0 PHP 8.3 hard break
+
+# OR (always works, even on stock angular — prefer this if you want belt-and-suspenders):
+feat: Tipsy 2.0 PHP 8.3 hard break
+
+BREAKING CHANGE: requires PHP 8.3+; default DB is PostgreSQL; typed public API.
+```
+
+Other bumps:
+
+```
+feat: add something new → minor
+fix: fix a bug → patch
+perf: make it faster → patch
+```
+
+`docs:` and `chore:` do **not** trigger a release.
+
+**Do not rely on PR titles alone** — semantic-release reads **git commit messages on
+`master`/`main`**. If you squash-merge, put `feat!:` / `BREAKING CHANGE:` in the
+**squash commit subject/body**.
+
+### Cutting `v2.0.0` specifically (from `0.11.x`)
+
+A major bump from `0.11.12` becomes **`1.0.0`**, not `2.0.0` (semver/semrel rule for
+0.x). To ship Tipsy 2.0 as `v2.0.0`:
+
+1. Let CI open the prerelease (likely `v1.0.0`), **or**
+2. Before promoting: edit the GitHub release/tag to **`v2.0.0`** (and the
+ `release/v…` branch name if you care), **or**
+3. Create an annotated `v1.0.0` tag on the pre-2.0 tip first, then push a `feat!:`
+ commit so the next major is `2.0.0`.
+
+Then uncheck “pre-release” to publish to Packagist.
+
+### Step 2: Push to `master` (or `main`)
+
+```bash
+git push origin master
+```
+
+Pushes to `v2` / feature branches run tests only — they do **not** create prereleases.
+
+### Step 3: Let CI run
+
+- Open **Actions** → **CI (test, coverage, release)**
+- Wait for **test** (PHP 8.3/8.4 × pgsql/mysql) and **Release (prerelease branch + GitHub API)**
+
+If nothing releaseable:
+
+- Commits lacked `feat` / `fix` / `perf` / `BREAKING CHANGE` → add one and push again
+- Build/test failures → fix and push again
+
+### Step 4: Promote the prerelease to a full release
+
+1. Go to **Releases**
+2. Open the latest **prerelease** (e.g. `v2.0.0`)
+3. **Edit** → uncheck **Set as a pre-release** → **Update release**
+
+This runs **Packagist release**, which pings Packagist to index the new tag.
+
+---
+
+## Verify
+
+```bash
+composer show tipsyphp/tipsy
+# or
+curl -sS https://repo.packagist.org/p2/tipsyphp/tipsy.json | jq '.packages["tipsyphp/tipsy"][0].version'
+```
+
+Install:
+
+```bash
+composer require tipsyphp/tipsy:^2.0
+```
+
+---
+
+## Manual re-publish
+
+**Actions** → **Packagist release** → **Run workflow** → tag `vX.Y.Z`.
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..1bd9b67
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,60 @@
+# Tipsy Examples
+
+Working examples demonstrating core Tipsy features on modern PHP 8.3+.
+
+## Examples
+
+| Example | Description |
+|---------|-------------|
+| **[hello-world](hello-world/)** | Minimal app — one route, one view |
+| **[rest-api](rest-api/)** | JSON REST API with GET/POST/PUT/DELETE |
+| **[middleware](middleware/)** | Request logging and auth middleware |
+| **[services](services/)** | Dependency injection with services |
+| **[docker](docker/)** | Production Dockerfile + nginx config |
+| **[vercel](vercel/)** | Serverless deploy with vercel-php community runtime |
+
+## Running an Example (local path repo)
+
+In-repo `composer.json` files use a **path** repository (`../../`) so examples track this working tree during development.
+
+```bash
+cd examples/hello-world
+# Mirror (copy) the path repo — avoids symlink loops with coverage tools
+COMPOSER_MIRROR_PATH_REPOS=1 composer install
+php -S localhost:8000 -t web
+```
+
+Then open [http://localhost:8000](http://localhost:8000).
+
+## Packagist / deployable apps
+
+After `tipsyphp/tipsy` `^2.0` is on Packagist, production apps should depend on Packagist instead of a path repo:
+
+```json
+{
+ "require": {
+ "php": ">=8.3",
+ "tipsyphp/tipsy": "^2.0"
+ }
+}
+```
+
+The Vercel example’s path repository will **not** work on Vercel’s build hosts — publish/require from Packagist first, or vendor the package another way.
+
+## Running with Docker
+
+```bash
+# From the repo root:
+docker build -f examples/docker/Dockerfile -t tipsy-app .
+docker run -p 8080:80 tipsy-app
+```
+
+Then open [http://localhost:8080](http://localhost:8080).
+
+## Deploying to Vercel
+
+```bash
+cd examples/vercel
+# Requires tipsyphp/tipsy on Packagist (or adjust composer.json)
+npx vercel
+```
diff --git a/examples/docker/.dockerignore b/examples/docker/.dockerignore
new file mode 100644
index 0000000..3d8165c
--- /dev/null
+++ b/examples/docker/.dockerignore
@@ -0,0 +1,3 @@
+vendor/
+.env
+*.log
diff --git a/examples/docker/Dockerfile b/examples/docker/Dockerfile
new file mode 100644
index 0000000..dc000ae
--- /dev/null
+++ b/examples/docker/Dockerfile
@@ -0,0 +1,73 @@
+# =============================================================================
+# Tipsy PHP — Production Docker Image
+# Multi-stage build: composer install → slim runtime
+#
+# Build from the REPO ROOT:
+# docker build -f examples/docker/Dockerfile -t tipsy-app .
+# =============================================================================
+
+# --- Stage 1: Install dependencies ---
+FROM composer:2 AS deps
+
+WORKDIR /build
+
+# Copy the framework source first (for the path repository)
+COPY src/ /tipsy/src/
+COPY composer.json /tipsy/composer.json
+
+# Copy the example app
+COPY examples/docker/composer.json ./
+
+# Point the path repository at the copied framework source
+# COMPOSER_MIRROR_PATH_REPOS=1 forces a copy instead of symlink (symlinks don't survive multi-stage builds)
+RUN sed -i 's|"url": "../../"|"url": "/tipsy"|' composer.json \
+ && COMPOSER_MIRROR_PATH_REPOS=1 composer install --no-dev --no-scripts --prefer-dist --optimize-autoloader
+
+# --- Stage 2: Runtime ---
+FROM php:8.3-fpm-alpine AS runtime
+
+# Install only what's needed — keep the image tiny
+RUN apk add --no-cache nginx supervisor \
+ && docker-php-ext-install pdo pdo_mysql opcache \
+ && rm -rf /var/cache/apk/*
+
+# OPcache tuning for production
+RUN { \
+ echo 'opcache.enable=1'; \
+ echo 'opcache.memory_consumption=128'; \
+ echo 'opcache.interned_strings_buffer=16'; \
+ echo 'opcache.max_accelerated_files=10000'; \
+ echo 'opcache.validate_timestamps=0'; \
+ echo 'opcache.jit=tracing'; \
+ echo 'opcache.jit_buffer_size=64M'; \
+} > /usr/local/etc/php/conf.d/opcache.ini
+
+# PHP production settings
+RUN { \
+ echo 'expose_php=Off'; \
+ echo 'display_errors=Off'; \
+ echo 'log_errors=On'; \
+ echo 'error_log=/dev/stderr'; \
+ echo 'memory_limit=128M'; \
+ echo 'upload_max_filesize=10M'; \
+ echo 'post_max_size=10M'; \
+} > /usr/local/etc/php/conf.d/production.ini
+
+# nginx config
+COPY examples/docker/docker/nginx.conf /etc/nginx/http.d/default.conf
+
+# supervisord to run nginx + php-fpm
+COPY examples/docker/docker/supervisord.conf /etc/supervisord.conf
+
+# Application code
+WORKDIR /var/www/app
+COPY --from=deps /build/vendor ./vendor
+COPY examples/docker/web ./web
+COPY examples/docker/views ./views
+
+# Ensure nginx can read the files
+RUN chown -R www-data:www-data /var/www/app
+
+EXPOSE 80
+
+CMD ["supervisord", "-c", "/etc/supervisord.conf", "-n"]
diff --git a/examples/docker/composer.json b/examples/docker/composer.json
new file mode 100644
index 0000000..a0beab5
--- /dev/null
+++ b/examples/docker/composer.json
@@ -0,0 +1,14 @@
+{
+ "name": "tipsyphp/docker-example",
+ "description": "Tipsy Docker deployment example",
+ "require": {
+ "tipsyphp/tipsy": "*@dev"
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../"
+ }
+ ],
+ "minimum-stability": "dev"
+}
diff --git a/examples/docker/docker/nginx.conf b/examples/docker/docker/nginx.conf
new file mode 100644
index 0000000..c671f3e
--- /dev/null
+++ b/examples/docker/docker/nginx.conf
@@ -0,0 +1,51 @@
+server {
+ listen 80 default_server;
+ server_name _;
+
+ root /var/www/app/web;
+ index index.php;
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+
+ # Deny access to hidden files
+ location ~ /\. {
+ deny all;
+ access_log off;
+ log_not_found off;
+ }
+
+ # Static files — served directly by nginx
+ location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ {
+ expires 30d;
+ access_log off;
+ try_files $uri =404;
+ }
+
+ # All other requests → Tipsy router
+ location / {
+ try_files $uri $uri/ /index.php?__url=$uri&$args;
+ }
+
+ # PHP-FPM
+ location ~ \.php$ {
+ fastcgi_pass 127.0.0.1:9000;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+
+ # Performance tuning
+ fastcgi_buffer_size 16k;
+ fastcgi_buffers 4 16k;
+ fastcgi_connect_timeout 5s;
+ fastcgi_send_timeout 15s;
+ fastcgi_read_timeout 15s;
+ }
+
+ # Deny access to composer files, vendor, etc.
+ location ~ /(composer\.(json|lock)|vendor) {
+ deny all;
+ }
+}
diff --git a/examples/docker/docker/supervisord.conf b/examples/docker/docker/supervisord.conf
new file mode 100644
index 0000000..1ca570d
--- /dev/null
+++ b/examples/docker/docker/supervisord.conf
@@ -0,0 +1,23 @@
+[supervisord]
+logfile=/dev/stdout
+logfile_maxbytes=0
+loglevel=info
+pidfile=/tmp/supervisord.pid
+
+[program:php-fpm]
+command=php-fpm --nodaemonize
+autostart=true
+autorestart=true
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+
+[program:nginx]
+command=nginx -g "daemon off;"
+autostart=true
+autorestart=true
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
diff --git a/examples/docker/views/index.phtml b/examples/docker/views/index.phtml
new file mode 100644
index 0000000..f4390d7
--- /dev/null
+++ b/examples/docker/views/index.phtml
@@ -0,0 +1,28 @@
+
Welcome to Tipsy on PHP =PHP_VERSION?>.
+ diff --git a/examples/hello-world/views/layout.phtml b/examples/hello-world/views/layout.phtml new file mode 100644 index 0000000..dabd891 --- /dev/null +++ b/examples/hello-world/views/layout.phtml @@ -0,0 +1,13 @@ + + + + +