diff --git a/.buildscript/e2e.sh b/.buildscript/e2e.sh
index ed25afe..5ccdab4 100755
--- a/.buildscript/e2e.sh
+++ b/.buildscript/e2e.sh
@@ -6,7 +6,7 @@ if [ "$RUN_E2E_TESTS" != "true" ]; then
echo "Skipping end to end tests."
else
echo "Running end to end tests..."
- wget https://github.com/segmentio/library-e2e-tester/releases/download/0.2.1/tester_linux_amd64 -O tester
+ wget https://github.com/segmentio/library-e2e-tester/releases/download/0.4.1-pre1/tester_linux_amd64 -O tester
chmod +x tester
./tester -path='./bin/analytics'
echo "End to end tests completed!"
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..193a7e7
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,21 @@
+codecov:
+ notify:
+ after_n_builds: 2
+
+coverage:
+ round: nearest
+ # Status will be green when coverage is between 70 and 100%.
+ range: "70...100"
+ status:
+ project:
+ default:
+ threshold: 2%
+ paths:
+ - "src"
+ patch:
+ default:
+ threshold: 0%
+ paths:
+ - "src"
+
+comment: false
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..ea28cc7
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,2 @@
+# Global users
+@lubird @bsneed @pooyaj
\ No newline at end of file
diff --git a/.github/workflows/create_jira.yml b/.github/workflows/create_jira.yml
new file mode 100644
index 0000000..8180ac0
--- /dev/null
+++ b/.github/workflows/create_jira.yml
@@ -0,0 +1,39 @@
+name: Create Jira Ticket
+
+on:
+ issues:
+ types:
+ - opened
+
+jobs:
+ create_jira:
+ name: Create Jira Ticket
+ runs-on: ubuntu-latest
+ environment: IssueTracker
+ steps:
+ - name: Checkout
+ uses: actions/checkout@master
+ - name: Login
+ uses: atlassian/gajira-login@master
+ env:
+ JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
+ JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
+ JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }}
+ JIRA_EPIC_KEY: ${{ secrets.JIRA_EPIC_KEY }}
+ JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }}
+
+ - name: Create
+ id: create
+ uses: atlassian/gajira-create@master
+ with:
+ project: ${{ secrets.JIRA_PROJECT }}
+ issuetype: Bug
+ summary: |
+ [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }}
+ description: |
+ Github Link: ${{ github.event.issue.html_url }}
+ ${{ github.event.issue.body }}
+ fields: '{"parent": {"key": "${{ secrets.JIRA_EPIC_KEY }}"}}'
+
+ - name: Log created issue
+ run: echo "Issue ${{ steps.create.outputs.issue }} was created"
\ No newline at end of file
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
new file mode 100644
index 0000000..9dca93b
--- /dev/null
+++ b/.github/workflows/e2e-tests.yml
@@ -0,0 +1,58 @@
+name: E2E Tests
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+ workflow_dispatch:
+ inputs:
+ e2e_tests_ref:
+ description: 'Branch or ref of sdk-e2e-tests to use'
+ required: false
+ default: 'main'
+
+jobs:
+ e2e-tests:
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout SDK
+ uses: actions/checkout@v4
+ with:
+ path: sdk
+
+ - name: Checkout sdk-e2e-tests
+ uses: actions/checkout@v4
+ with:
+ repository: segmentio/sdk-e2e-tests
+ ref: ${{ inputs.e2e_tests_ref || 'main' }}
+ token: ${{ secrets.E2E_TESTS_TOKEN }}
+ path: sdk-e2e-tests
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ extensions: curl, json
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Run E2E tests
+ working-directory: sdk-e2e-tests
+ run: |
+ ./scripts/run-tests.sh \
+ --sdk-dir "${{ github.workspace }}/sdk/e2e-cli" \
+ --cli "php ${{ github.workspace }}/sdk/e2e-cli/main.php"
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-test-results
+ path: sdk-e2e-tests/test-results/
+ if-no-files-found: ignore
diff --git a/.github/workflows/publish-e2e-cli.yml b/.github/workflows/publish-e2e-cli.yml
new file mode 100644
index 0000000..ec647f0
--- /dev/null
+++ b/.github/workflows/publish-e2e-cli.yml
@@ -0,0 +1,39 @@
+name: Publish E2E CLI
+
+on:
+ push:
+ branches: [master]
+ paths:
+ - 'e2e-cli/**'
+ - 'lib/**'
+ schedule:
+ - cron: '0 0 1 * *'
+ workflow_dispatch:
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout SDK
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ extensions: curl, json
+
+ - name: Prepare artifact
+ run: |
+ mkdir -p artifact
+ cp e2e-cli/main.php artifact/
+ cp e2e-cli/e2e-config.json artifact/
+ cp e2e-cli/run-e2e.sh artifact/
+ cp -r lib artifact/lib
+
+ - name: Upload CLI artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-cli-php
+ path: artifact/
+ retention-days: 90
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..53bf512
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,141 @@
+name: "Tests"
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+
+ coding-standard:
+ runs-on: ubuntu-22.04
+ name: Coding standards
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.1'
+ coverage: none
+ tools: cs2pr
+
+ # Install dependencies and handle caching in one go.
+ # @link https://github.com/marketplace/actions/install-composer-dependencies
+ - name: Install Composer dependencies
+ uses: "ramsey/composer-install@v2"
+
+ - name: Check coding standards
+ continue-on-error: true
+ run: ./vendor/bin/phpcs -s --report-full --report-checkstyle=./phpcs-report.xml
+
+ - name: Show PHPCS results in PR
+ run: cs2pr ./phpcs-report.xml
+
+ lint:
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ php: ['7.4', '8.0', '8.1', '8.2', '8.3']
+ experimental: [false]
+
+ name: "Lint: PHP ${{ matrix.php }}"
+ continue-on-error: ${{ matrix.experimental }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ tools: cs2pr
+
+ # Install dependencies and handle caching in one go.
+ # @link https://github.com/marketplace/actions/install-composer-dependencies
+ - name: Install Composer dependencies
+ uses: "ramsey/composer-install@v2"
+
+ - name: Lint against parse errors
+ if: ${{ matrix.php != '8.1' }}
+ run: composer lint -- --checkstyle | cs2pr
+
+ - name: Lint against parse errors (PHP 8.1)
+ if: ${{ matrix.php == '8.1' }}
+ run: composer lint
+
+ test:
+ needs: ['coding-standard', 'lint']
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ php: ['7.4', '8.0', '8.1', '8.2', '8.3']
+ coverage: [false]
+ experimental: [false]
+ include:
+ # Run code coverage on high/low PHP.
+ - php: '7.4'
+ coverage: true
+ experimental: false
+ - php: '8.0'
+ coverage: true
+ experimental: false
+ - php: '8.1'
+ coverage: true
+ experimental: false
+ - php: '8.2'
+ coverage: true
+ experimental: false
+ - php: '8.3'
+ coverage: true
+ experimental: false
+
+ name: "Test: PHP ${{ matrix.php }}"
+
+ continue-on-error: ${{ matrix.experimental }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}
+ ini-values: sendmail_path=/usr/sbin/sendmail -t -i, error_reporting=E_ALL, display_errors=On
+ extensions: imap, mbstring, intl, ctype, filter, hash
+
+ # Install dependencies and handle caching in one go.
+ # @link https://github.com/marketplace/actions/install-composer-dependencies
+ - name: Install dependencies
+ if: ${{ matrix.php != '8.1' }}
+ uses: "ramsey/composer-install@v2"
+
+ - name: Install dependencies - ignore-platform-reqs
+ if: ${{ matrix.php == '8.1' }}
+ uses: "ramsey/composer-install@v2"
+ with:
+ composer-options: --ignore-platform-reqs
+
+ - name: Run tests, no code coverage
+ if: ${{ matrix.coverage }}
+ run: ./vendor/bin/phpunit --no-coverage
+
+ - name: Run tests with code coverage
+ if: ${{ matrix.coverage }}
+ run: ./vendor/bin/phpunit
+
+ - name: Send coverage report to Codecov
+ if: ${{ success() && matrix.coverage }}
+ uses: codecov/codecov-action@v5
+ with:
+ files: ./build/logs/clover.xml
+ fail_ci_if_error: true
+ verbose: true
+ env:
+ CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
diff --git a/.gitignore b/.gitignore
index de6e5ad..5c16b2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,8 @@ composer.phar
test/analytics.log
/.vscode
.phplint-cache
+/.idea
+clover.xml
+junit.xml
+.phpunit.result.cache
+/build/*
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index dd6bc10..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-language: php
-dist: precise
-php:
- - 5.6
- - 5.5
- - 5.4
- - 5.3
- - hhvm
-
-matrix:
- allow_failures:
- - php: hhvm
-
-script:
- - if [[ ! ${TRAVIS_PHP_VERSION:0:3} < "5.5" ]]; then composer require overtrue/phplint; composer require squizlabs/php_codesniffer; ./vendor/bin/phplint; ./vendor/bin/phpcs; fi
- - phpunit
- - .buildscript/e2e.sh
-
-after_success:
- - bash <(curl -s https://codecov.io/bash)
diff --git a/HISTORY.md b/HISTORY.md
index 6dce29a..1642c3f 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,4 +1,99 @@
+3.8.2 / 2026-03-11
+==================
+
+
+
+3.8.2 / 2026-03-11
+==================
+
+ * Fix OS command injection in ForkCurl consumer (#246)
+
+3.8.1 / 2025-01-27
+==================
+
+ * Convert the exec output to string (#239)
+
+3.8.0 / 2024-02-15
+==================
+
+ * Include support for 8.3 (#231)
+
+
+3.7.0 / 2023-09-11
+==================
+
+ * Convert to github actions
+ * Remove circleci config/files
+ * Update autoloader
+
+
+3.6.0 / 2023-03-28
+==================
+
+ * Issue #208 Correct autoload require statement (#209)
+ * Fix missing version information (#207)
+ * Include support for 8.1 * 8.2 (#210)
+
+
+3.5.0 / 2022-08-17
+==================
+
+ * Correct Payload size check of 512kb (#202)
+ * Add new consumer configurable options: curl_timeout, curl_connecttimeout, max_item_size_bytes, max_queue_size_bytes (#192, #197, #198)
+ * Deprecate HTTP Option (#194 & #195)
+
+
+3.0.0 / 2021-10-14
+==================
+
+ * PSR-12 coding standard with Slevomat and phcs extensions
+ * Namespace and file rearrangement to follow PSR-4 naming scheme and more logical separation
+ * Provide strict types for all properties, parameters, and return values
+ * Add an exception class so we can have segment-specific exceptions
+ * Add dependencies on JSON extension
+ * Add dependency on the Roave security checker
+ * Since the library already required a minimum of PHP 7.4, make use of PHP 7.4+ features, and avoid compat issues with 8.0
+ * More sensible error handling, don't try to catch exceptions that are never thrown
+ * Extensive linting and static analysis using phpcs, psalm, phpstan, and PHPStorm to spot issues
+
+
+2.0.0 / 2021-07-16
+==================
+
+ * Modify Endpoint to match API docs (#171)
+ * usleep in flush() causes unexpected delays on page loads (#173)
+ * Support PHP 8 (#152)
+ * Remove Support for PHP 7.2
+ * Namespacing (#182)
+
+
+1.8.0 / 2021-05-31
+==================
+
+ * Fix socket return response (#174)
+ * API Endpoint update (#168)
+ * Update Batch Size Check (#168)
+ * Remove messageID override capabilities (#163)
+ * Update flush sleep waiting period (#161)
+
+1.7.0 / 2021-03-10
+=======================
+
+ * Retry Network errors (#136)
+ * Update Tests [Improvement] (#132)
+ * Updtate Readme Status Badging (#139)
+ * Bump e2e tests to latest version [Improvement] (#142)
+ * Add Limits to message, batch and memory usage [Feature] (#137)
+ * Add Configurable flush parameters [Feature] (#135)
+ * Add ability to use custom consumer [Feature] (#61)
+ * Add ability to set file permissions [Feature] (#122)
+ * Fix curl error handler [Improvement] (#97)
+ * Fix timestamp implementation for microseconds (#94)
+ * Modify max queue size setting to match requirements (#153, #146)
+ * Add ability to set userid as zero (#157)
+
+
1.6.1-beta / 2018-05-01
=======================
@@ -32,7 +127,7 @@
1.5.1 / 2017-04-06
==================
- * Use require_once() instead of require(). Fixes issue where seperate plugins in systems such as Moodle break because of class redeclaration when using seperate internal versions of Segment.io.
+ * Use require_once() instead of require(). Fixes issue where separate plugins in systems such as Moodle break because of class redeclaration when using separate internal versions of Segment.io.
1.5.0 / 2017-03-03
==================
@@ -40,7 +135,7 @@
* Adding context.library.consumer to all PHP events
* libcurl consumer will retry once if http response is not 200
* update link to php docs
- * improve portability and reliability of Makefile accros different platforms (#74)
+ * improve portability and reliability of Makefile across different platforms (#74)
1.4.2 / 2016-07-11
==================
diff --git a/Makefile b/Makefile
index 7903cb5..3819410 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,8 @@ lint: dependencies
then \
php ./composer.phar require overtrue/phplint --dev; \
php ./composer.phar require squizlabs/php_codesniffer --dev; \
+ php ./composer.phar require dealerdirect/phpcodesniffer-composer-installer --dev; \
+
./vendor/bin/phplint; \
./vendor/bin/phpcs; \
else \
@@ -26,8 +28,7 @@ lint: dependencies
release:
@printf "releasing ${VERSION}..."
- @printf ' ./lib/Segment/Version.php
- @node -e "var fs = require('fs'), pkg = require('./composer'); pkg.version = '${VERSION}'; fs.writeFileSync('./composer.json', JSON.stringify(pkg, null, '\t'));"
+ @printf ' ./lib/Version.php
@git changelog -t ${VERSION}
@git release ${VERSION}
@@ -37,4 +38,4 @@ clean:
vendor \
composer.lock
-.PHONY: boostrap release clean
+.PHONY: bootstrap release clean
diff --git a/README.md b/README.md
index 946db82..40d8a28 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,55 @@
analytics-php
==============
-
-[](https://travis-ci.org/segmentio/analytics-php)
+[](https://github.com/segmentio/analytics-php/actions)
+[](https://codecov.io/gh/segmentio/analytics-php)
analytics-php is a php client for [Segment](https://segment.com)
+### ⚠️ Maintenance ⚠️
+This library is in maintenance mode. It will send data as intended, but receive no new feature support and only critical maintenance updates from Segment.
+
+
+

+
You can't fix what you can't measure
+
+
+Analytics helps you measure your users, product, and business. It unlocks insights into your app's funnel, core business metrics, and whether you have product-market fit.
+
+## How to get started
+1. **Collect analytics data** from your app(s).
+ - The top 200 Segment companies collect data from 5+ source types (web, mobile, server, CRM, etc.).
+2. **Send the data to analytics tools** (for example, Google Analytics, Amplitude, Mixpanel).
+ - Over 250+ Segment companies send data to eight categories of destinations such as analytics tools, warehouses, email marketing and remarketing systems, session recording, and more.
+3. **Explore your data** by creating metrics (for example, new signups, retention cohorts, and revenue generation).
+ - The best Segment companies use retention cohorts to measure product market fit. Netflix has 70% paid retention after 12 months, 30% after 7 years.
+
+[Segment](https://segment.com) collects analytics data and allows you to send it to more than 250 apps (such as Google Analytics, Mixpanel, Optimizely, Facebook Ads, Slack, Sentry) just by flipping a switch. You only need one Segment code snippet, and you can turn integrations on and off at will, with no additional code. [Sign up with Segment today](https://app.segment.com/signup).
+
+### Why?
+1. **Power all your analytics apps with the same data**. Instead of writing code to integrate all of your tools individually, send data to Segment, once.
+
+2. **Install tracking for the last time**. We're the last integration you'll ever need to write. You only need to instrument Segment once. Reduce all of your tracking code and advertising tags into a single set of API calls.
+
+3. **Send data from anywhere**. Send Segment data from any device, and we'll transform and send it on to any tool.
+
+4. **Query your data in SQL**. Slice, dice, and analyze your data in detail with Segment SQL. We'll transform and load your customer behavioral data directly from your apps into Amazon Redshift, Google BigQuery, or Postgres. Save weeks of engineering time by not having to invent your own data warehouse and ETL pipeline.
+
+ For example, you can capture data on any app:
+ ```js
+ analytics.track('Order Completed', { price: 99.84 })
+ ```
+ Then, query the resulting data in SQL:
+ ```sql
+ select * from app.order_completed
+ order by price desc
+ ```
+
+### 🚀 Startup Program
+
+

+
+If you are part of a new startup (<$5M raised, <2 years since founding), we just launched a new startup program for you. You can get a Segment Team plan (up to $25,000 value in Segment credits) for free up to 2 years — apply here!
+
## Documentation
Documentation is available at [segment.com/docs/sources/server/php](https://segment.com/docs/sources/server/php/)
diff --git a/bin/analytics b/bin/analytics
index 1472054..b50ae59 100755
--- a/bin/analytics
+++ b/bin/analytics
@@ -1,106 +1,94 @@
#!/usr/bin/env php
$options['userId'],
- 'event' => $options['event'],
- 'properties' => parse_json($options['properties'])
- ));
- break;
-
- case 'identify':
- Segment::identify(array(
- 'userId' => $options['userId'],
- 'traits' => parse_json($options['traits'])
- ));
- break;
-
- case 'page':
- Segment::page(array(
- 'userId' => $options['userId'],
- 'name' => $options['name'],
- 'properties' => parse_json($options['properties'])
- ));
- break;
-
- case 'group':
- Segment::identify(array(
- 'userId' => $options['userId'],
- 'groupId' => $options['groupId'],
- 'traits' => parse_json($options['traits'])
- ));
- break;
-
- case 'alias':
- Segment::alias(array(
- 'userId' => $options['userId'],
- 'previousId' => $options['previousId']
- ));
- break;
-
- default:
- error(usage());
- break;
+ case 'track':
+ Segment::track(array(
+ 'userId' => $options['userId'],
+ 'event' => $options['event'],
+ 'properties' => parse_json($options['properties'])
+ ));
+ break;
+ case 'identify':
+ Segment::identify(array(
+ 'userId' => $options['userId'],
+ 'traits' => parse_json($options['traits'])
+ ));
+ break;
+ case 'page':
+ Segment::page(array(
+ 'userId' => $options['userId'],
+ 'name' => $options['name'],
+ 'properties' => parse_json($options['properties'])
+ ));
+ break;
+ case 'group':
+ Segment::identify(array(
+ 'userId' => $options['userId'],
+ 'groupId' => $options['groupId'],
+ 'traits' => parse_json($options['traits'])
+ ));
+ break;
+ case 'alias':
+ Segment::alias(array(
+ 'userId' => $options['userId'],
+ 'previousId' => $options['previousId']
+ ));
+ break;
+ default:
+ error(usage());
}
Segment::flush();
-function usage() {
- return "\n Usage: analytics --type