PHPackages                             farzai/transport - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. farzai/transport

ActiveLibrary[HTTP &amp; Networking](/categories/http)

farzai/transport
================

A modern, PSR-compliant HTTP client for PHP with middleware architecture, advanced retry strategies, and fluent API for building requests

2.1.0(7mo ago)07.1k[3 PRs](https://github.com/farzai/transport-php/pulls)3MITPHPPHP ^8.1CI passing

Since Jun 21Pushed 1mo agoCompare

[ Source](https://github.com/farzai/transport-php)[ Packagist](https://packagist.org/packages/farzai/transport)[ Docs](https://github.com/farzai/transport-php)[ GitHub Sponsors](https://github.com/parsilver)[ RSS](/packages/farzai-transport/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (8)Dependencies (13)Versions (17)Used By (3)

Transport PHP
=============

[](#transport-php)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4b3ddf64fbdaaaf0bff5eb93fabcdd3dd1487dceee819e7b16ae0548b9a09b0b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6661727a61692f7472616e73706f72742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/farzai/transport)[![Tests](https://camo.githubusercontent.com/0acb4ed140df54444fcfc1107a3466686554b82a021f692f9329766ca1cb3c4a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6661727a61692f7472616e73706f72742d7068702f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/farzai/transport-php/actions/workflows/run-tests.yml)[![codecov](https://camo.githubusercontent.com/4aa0c9bfdd0709b0b0164578380b6369ec303aecefd2dd1ddfb671122cf576ef/68747470733a2f2f636f6465636f762e696f2f67682f6661727a61692f7472616e73706f72742d7068702f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/farzai/transport-php)[![Total Downloads](https://camo.githubusercontent.com/0b71c85baf4ea17be9cfed3ae27c71b23bd58c6700c04f8c9457a1e3491aacd6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6661727a61692f7472616e73706f72742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/farzai/transport)

A modern, PSR-compliant HTTP client for PHP with middleware architecture, advanced retry strategies, and fluent API for building requests.

Features
--------

[](#features)

- ✅ **PSR Standards** - Built on PSR-7 (HTTP Messages), PSR-17 (HTTP Factories), PSR-18 (HTTP Client)
- ✅ **No Hard Dependencies** - Use any PSR-18 HTTP client (Guzzle, Symfony, or custom)
- ✅ **Auto-Detection** - Automatically discovers and uses available HTTP clients
- ✅ **Middleware Architecture** - Extensible plugin system for custom behavior
- ✅ **Advanced Retry Strategies** - Exponential backoff with jitter, custom retry conditions
- ✅ **Fluent Request Builder** - Chainable API for building requests
- ✅ **Immutable Configuration** - Thread-safe, predictable behavior
- ✅ **Type-Safe** - Full PHP 8.1+ type hints and strict types
- ✅ **JSON Helpers** - Parse JSON with proper error handling and dot-notation access
- ✅ **Custom Exceptions** - Detailed error context implementing PSR standards
- ✅ **Easy to Swap HTTP Clients** - Switch between Guzzle, Symfony, or any PSR-18 client
- ✅ **File Upload &amp; Multipart** - RFC 7578 compliant multipart/form-data with file upload support
- ✅ **Cookie Management** - RFC 6265 compliant automatic cookie handling with session support

Requirements
------------

[](#requirements)

- PHP 8.1 or higher

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require farzai/transport
```

The library will auto-detect any available PSR-18 HTTP client. If you don't have one installed, we recommend:

```
# Recommended: Modern HTTP client with async support
composer require symfony/http-client

# Alternative: Popular and widely-used
composer require guzzlehttp/guzzle
```

Quick Start
-----------

[](#quick-start)

Transport PHP automatically detects available HTTP clients (Symfony, Guzzle, etc.) - no configuration needed!

```
use Farzai\Transport\TransportBuilder;

// Just works! Auto-detects your HTTP client
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->build();

$response = $transport->get('/users')->send();
echo $response->json('data.0.name'); // Dot notation support!
```

Usage
-----

[](#usage)

### Basic Usage (Fluent API)

[](#basic-usage-fluent-api)

```
use Farzai\Transport\TransportBuilder;

// Create a transport client with configuration
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withHeaders([
        'Authorization' => 'Bearer token123',
        'Accept' => 'application/json',
    ])
    ->withTimeout(30)
    ->build();

// Make requests using fluent API
$response = $transport->get('/users/123')->send();

// Access response data
echo $response->statusCode(); // 200
echo $response->body(); // Raw response body
$data = $response->json(); // Parsed JSON as array
```

### Fluent Request Building

[](#fluent-request-building)

```
// GET request with query parameters
$response = $transport
    ->get('/users')
    ->withQuery(['page' => 1, 'limit' => 10])
    ->withHeader('X-Custom-Header', 'value')
    ->send();

// POST with JSON body
$response = $transport
    ->post('/users')
    ->withJson([
        'name' => 'John Doe',
        'email' => 'john@example.com'
    ])
    ->send();

// POST with form data
$response = $transport
    ->post('/login')
    ->withForm([
        'username' => 'john',
        'password' => 'secret'
    ])
    ->send();

// With authentication
$response = $transport
    ->get('/protected')
    ->withBearerToken('your-token')
    ->send();

// Or basic auth
$response = $transport
    ->get('/protected')
    ->withBasicAuth('username', 'password')
    ->send();
```

### Working with JSON Responses

[](#working-with-json-responses)

```
// Parse JSON with automatic error handling
$data = $response->json(); // ['id' => 123, 'name' => 'John Doe']

// Get specific field using dot notation
$name = $response->json('name'); // 'John Doe'
$city = $response->json('user.address.city'); // 'New York'

// Get as array
$array = $response->toArray();

// Safe JSON parsing (returns null on error instead of throwing)
$data = $response->jsonOrNull();

// Check if response is successful
if ($response->isSuccessful()) {
    // Handle success (2xx status codes)
}
```

### Advanced Retry Logic

[](#advanced-retry-logic)

```
use Farzai\Transport\Retry\ExponentialBackoffStrategy;
use Farzai\Transport\Retry\RetryCondition;
use Farzai\Transport\Exceptions\NetworkException;

// Configure retry with exponential backoff
$transport = TransportBuilder::make()
    ->withRetries(
        maxRetries: 3,
        strategy: new ExponentialBackoffStrategy(
            baseDelayMs: 1000,      // Start with 1 second
            multiplier: 2.0,         // Double each retry
            maxDelayMs: 30000,       // Cap at 30 seconds
            useJitter: true          // Add randomization
        ),
        condition: (new RetryCondition())
            ->onExceptions([NetworkException::class])
    )
    ->build();

// Retries automatically with exponential backoff + jitter
$response = $transport->get('/unreliable-endpoint')->send();

// Or use default retry condition (retries on any exception)
$transport = TransportBuilder::make()
    ->withRetries(
        maxRetries: 3,
        condition: RetryCondition::default() // Retries on ANY exception
    )
    ->build();
```

**Retry Conditions:**

- `RetryCondition::default()` - Retries on any exception
- `(new RetryCondition())->onExceptions([...])` - Retry only specific exceptions
- `(new RetryCondition())->onStatusCodes([500, 502, 503])` - Retry specific HTTP status codes

### Custom Middleware

[](#custom-middleware)

```
use Farzai\Transport\Middleware\MiddlewareInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

// Create custom middleware
class AuthMiddleware implements MiddlewareInterface
{
    public function handle(RequestInterface $request, callable $next): ResponseInterface
    {
        // Add auth token to all requests
        $request = $request->withHeader('Authorization', 'Bearer ' . $this->getToken());

        return $next($request);
    }

    private function getToken(): string
    {
        // Your token logic
        return 'your-token';
    }
}

// Add to transport
$transport = TransportBuilder::make()
    ->withMiddleware(new AuthMiddleware())
    ->build();
```

### Configuration Options

[](#configuration-options)

```
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withHeaders(['Accept' => 'application/json'])
    ->withTimeout(30)  // Seconds
    ->withRetries(3)   // Max retry attempts
    ->withMiddleware($customMiddleware)
    ->withoutDefaultMiddlewares()  // Disable logging, timeout, retry middlewares
    ->setClient($customPsrClient)  // Use custom PSR-18 client
    ->setLogger($customLogger)     // Use custom PSR-3 logger
    ->build();
```

### HTTP Client Selection

[](#http-client-selection)

```
use Farzai\Transport\Factory\ClientFactory;

// Auto-detect (recommended - uses Symfony > Guzzle > Others)
$transport = TransportBuilder::make()->build();

// Explicitly use Guzzle
$transport = TransportBuilder::make()
    ->setClient(ClientFactory::createGuzzle(['timeout' => 30]))
    ->build();

// Explicitly use Symfony HTTP Client
$transport = TransportBuilder::make()
    ->setClient(ClientFactory::createSymfony(['max_redirects' => 5]))
    ->build();

// Check which client is being used
echo ClientFactory::getDetectedClientName(); // e.g., "Symfony\Component\HttpClient\Psr18Client"
```

### File Upload &amp; Multipart Requests

[](#file-upload--multipart-requests)

```
// Simple file upload
$response = $transport->post('/upload')
    ->withFile(
        name: 'document',
        path: '/path/to/file.pdf',
        filename: 'report.pdf',
        additionalFields: ['title' => 'Monthly Report']
    )
    ->send();

// Multiple files with form data
$response = $transport->post('/upload')
    ->withMultipart([
        // Text fields
        ['name' => 'title', 'contents' => 'My Upload'],
        ['name' => 'description', 'contents' => 'File description'],

        // File uploads
        [
            'name' => 'avatar',
            'contents' => file_get_contents('photo.jpg'),
            'filename' => 'avatar.jpg',
            'content-type' => 'image/jpeg'
        ],
        [
            'name' => 'document',
            'contents' => fopen('/path/to/file.pdf', 'r'),
            'filename' => 'document.pdf'
        ]
    ])
    ->send();

// Advanced: Using MultipartStreamBuilder
use Farzai\Transport\Multipart\MultipartStreamBuilder;

$builder = new MultipartStreamBuilder();
$builder->addField('username', 'john_doe')
    ->addFile('avatar', '/path/to/avatar.jpg', 'profile.jpg')
    ->addFileContents('data', $jsonData, 'data.json', 'application/json');

$response = $transport->post('/api/upload')
    ->withMultipartBuilder($builder)
    ->send();

// Memory-efficient streaming for large files
use Farzai\Transport\Multipart\StreamingMultipartBuilder;

$streamBuilder = new StreamingMultipartBuilder();
$stream = $streamBuilder
    ->addFile('video', '/path/to/large-video.mp4', 'video.mp4')
    ->addField('title', 'My Video')
    ->build();

// Streams file without loading entire content into memory
$response = $transport->request()
    ->withBody($stream)
    ->withHeader('Content-Type', $streamBuilder->getContentType())
    ->post('/upload')
    ->send();
```

**Note:** The library automatically selects `StreamingMultipartBuilder` for large files (&gt;1MB by default) to optimize memory usage. You can also manually use it for memory-efficient uploads of any size.

### Cookie Management

[](#cookie-management)

```
// Automatic cookie handling
$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    ->withCookies() // Enable automatic cookie management
    ->build();

// Login - cookies are automatically stored
$transport->post('/login')
    ->withJson(['username' => 'user', 'password' => 'pass'])
    ->send();

// Subsequent requests automatically include cookies
$response = $transport->get('/profile')->send();

// Advanced: Manual cookie management
use Farzai\Transport\Cookie\CookieJar;
use Farzai\Transport\Cookie\Cookie;

$cookieJar = new CookieJar();

// Add cookies manually
$cookieJar->setCookie(new Cookie(
    name: 'session_id',
    value: 'abc123',
    expiresAt: time() + 3600,
    domain: 'example.com',
    path: '/',
    secure: true,
    httpOnly: true
));

$transport = TransportBuilder::make()
    ->withCookieJar($cookieJar)
    ->build();

// Inspect cookies
echo "Cookies: {$cookieJar->count()}\n";
foreach ($cookieJar->getAllCookies() as $cookie) {
    echo "{$cookie->getName()}: {$cookie->getValue()}\n";
}

// Export/Import cookies for persistence
$data = $cookieJar->toArray();
file_put_contents('cookies.json', json_encode($data));

// Later...
$newJar = new CookieJar();
$newJar->fromArray(json_decode(file_get_contents('cookies.json'), true));
```

**Performance Note:** The cookie management system automatically optimizes for different workloads. For applications with many cookies (50+), it uses indexed collections with O(1) domain lookups. For smaller cookie counts, it uses simpler collections to minimize overhead.

### Event Monitoring

[](#event-monitoring)

Monitor HTTP requests lifecycle with event listeners:

```
use Farzai\Transport\Events\RequestSendingEvent;
use Farzai\Transport\Events\ResponseReceivedEvent;
use Farzai\Transport\Events\RequestFailedEvent;
use Farzai\Transport\Events\RetryAttemptEvent;

$transport = TransportBuilder::make()
    ->withBaseUri('https://api.example.com')
    // Track successful responses
    ->addEventListener(ResponseReceivedEvent::class, function ($event) {
        printf(
            "[SUCCESS] %s %s → %d (%.2fms)\n",
            $event->getMethod(),
            $event->getUri(),
            $event->getStatusCode(),
            $event->getDuration()
        );
    })
    // Track failed requests
    ->addEventListener(RequestFailedEvent::class, function ($event) {
        printf(
            "[ERROR] %s %s failed: %s\n",
            $event->getMethod(),
            $event->getUri(),
            $event->getExceptionMessage()
        );
    })
    // Monitor retry attempts
    ->addEventListener(RetryAttemptEvent::class, function ($event) {
        printf(
            "[RETRY] Attempt %d/%d (delay: %dms)\n",
            $event->getAttemptNumber(),
            $event->getMaxAttempts(),
            $event->getDelay()
        );
    })
    ->withRetries(3)
    ->build();

// Events are automatically dispatched during request lifecycle
$response = $transport->get('/api/endpoint')->send();
```

**Available Events:**

- `RequestSendingEvent` - Before a request is sent
- `ResponseReceivedEvent` - After successful response (includes duration metrics)
- `RequestFailedEvent` - When a request fails with exception details
- `RetryAttemptEvent` - Before each retry attempt with delay information

**Use Cases:**

- Performance monitoring and metrics collection
- Logging and debugging request/response cycles
- Custom retry notifications
- Request/response instrumentation

### Error Handling

[](#error-handling)

```
use Farzai\Transport\Exceptions\ClientException;
use Farzai\Transport\Exceptions\ServerException;
use Farzai\Transport\Exceptions\RetryExhaustedException;
use Farzai\Transport\Exceptions\JsonParseException;

// Throw exception on non-2xx responses
try {
    $response->throw();
} catch (ClientException $e) {
    // 4xx errors
    echo "Client error: {$e->getStatusCode()}\n";
    echo "Request: {$e->getRequest()->getUri()}\n";
    var_dump($e->getContext()); // Rich debugging context
} catch (ServerException $e) {
    // 5xx errors
    echo "Server error: {$e->getStatusCode()}\n";
}

// Custom error handling callback
$response->throw(function ($response, $exception) {
    if ($response->statusCode() === 404) {
        throw new \Exception('Resource not found!');
    }
    throw $exception;
});

// Handle retry exhaustion
try {
    $response = $transport->get('/flaky-endpoint')->send();
} catch (RetryExhaustedException $e) {
    echo "Failed after {$e->getAttempts()} attempts\n";
    echo "Delays used: " . implode(', ', $e->getDelaysUsed()) . "ms\n";

    foreach ($e->getRetryExceptions() as $attempt => $exception) {
        echo "Attempt $attempt: {$exception->getMessage()}\n";
    }
}

// Handle JSON parse errors
try {
    $data = $response->json();
} catch (JsonParseException $e) {
    echo "Invalid JSON: {$e->getMessage()}\n";
    echo "JSON string: {$e->jsonString}\n";
    echo "Error code: {$e->jsonErrorCode}\n";
}
```

### Using Custom PSR-18 Client and Logger

[](#using-custom-psr-18-client-and-logger)

```
use Farzai\Transport\TransportBuilder;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create custom logger
$logger = new Logger('http-client');
$logger->pushHandler(new StreamHandler('path/to/your.log'));

// Use any PSR-18 compliant client
$client = new \Your\Custom\Psr18Client();

$transport = TransportBuilder::make()
    ->setClient($client)
    ->setLogger($logger)
    ->build();
```

### Testing with Response Builder

[](#testing-with-response-builder)

```
use Farzai\Transport\ResponseBuilder;

$response = ResponseBuilder::create()
    ->statusCode(200)
    ->withHeader('Content-Type', 'application/json')
    ->withBody('{"success": true}')
    ->build();

// Or use the fluent builder methods
$response = ResponseBuilder::create()
    ->statusCode(404)
    ->withHeaders([
        'Content-Type' => 'application/json',
        'X-Request-ID' => '12345'
    ])
    ->withBody('{"error": "Not found"}')
    ->withVersion('1.1')
    ->withReason('Not Found')
    ->build();
```

Documentation
-------------

[](#documentation)

- **[Examples](examples/)** - Practical usage examples:
    - [Basic Usage](examples/basic-usage.php)
    - [Custom HTTP Clients](examples/custom-client.php)
    - [Advanced Retry Logic](examples/advanced-retry.php)
    - [Custom Middleware](examples/middleware-example.php)
    - [File Upload](examples/file-upload.php)
    - [Streaming Upload](examples/streaming-upload.php)
    - [Cookie Session Management](examples/cookie-session.php)
    - [Event Monitoring](examples/event-monitoring.php)

Architecture
------------

[](#architecture)

### No Hard Dependencies on Guzzle

[](#no-hard-dependencies-on-guzzle)

Transport PHP v2.x uses PSR standards and auto-detection:

- **PSR-7** - HTTP Message Interface
- **PSR-17** - HTTP Factories for creating requests/responses
- **PSR-18** - HTTP Client Interface

This means you can use **any** PSR-18 compliant HTTP client:

- ✅ Symfony HTTP Client (modern, async, HTTP/2)
- ✅ Guzzle (popular, stable)
- ✅ Any custom PSR-18 implementation

### Middleware System

[](#middleware-system)

```
Request → Middleware Stack → HTTP Client → Response
          ↓
     [LoggingMiddleware]
     [TimeoutMiddleware]
     [RetryMiddleware]
     [CustomMiddleware...]

```

Default middlewares (can be disabled with `withoutDefaultMiddlewares()`):

- **LoggingMiddleware**: Logs requests and responses
- **TimeoutMiddleware**: Enforces request timeouts
- **RetryMiddleware**: Handles retry logic with configurable strategies

### Immutable Configuration

[](#immutable-configuration)

All configuration is immutable and set during the build phase:

```
// ✅ Correct - configuration during build
$transport = TransportBuilder::make()
    ->withTimeout(30)
    ->withRetries(3)
    ->build();
```

This makes the Transport instance:

- **Thread-safe** - Can be safely shared across threads
- **Predictable** - Configuration can't change unexpectedly
- **Easier to test** - No hidden state changes

Testing
-------

[](#testing)

```
composer test
```

Code Quality
------------

[](#code-quality)

```
# Run tests with coverage
composer test-coverage

# Fix code style
composer format
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](docs/contributing/CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [parsilver](https://github.com/parsilver)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance76

Regular maintenance activity

Popularity18

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 64.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~95 days

Recently: every ~172 days

Total

10

Last Release

236d ago

Major Versions

0.0.2 → 1.0.02023-08-29

1.3.0 → v2.x-dev2025-10-24

PHP version history (2 changes)0.0.1PHP ^8.0

1.1.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4928451?v=4)[parsilver](/maintainers/parsilver)[@parsilver](https://github.com/parsilver)

---

Top Contributors

[![parsilver](https://avatars.githubusercontent.com/u/4928451?v=4)](https://github.com/parsilver "parsilver (52 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (19 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (10 commits)")

---

Tags

httppsrpsr-7middlewarehttp clientpsr-18multipartretrycookieapi clientfile-uploadfluent-apirest-clientform-data

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/farzai-transport/health.svg)

```
[![Health](https://phpackages.com/badges/farzai-transport/health.svg)](https://phpackages.com/packages/farzai-transport)
```

###  Alternatives

[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B3.7k](/packages/guzzlehttp-psr7)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35729.6k2](/packages/telnyx-telnyx-php)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28146.3k](/packages/phpro-http-tools)[art4/requests-psr18-adapter

Use WordPress/Requests as a PSR-18 HTTP client

155.7k](/packages/art4-requests-psr18-adapter)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

185671.3k41](/packages/laudis-neo4j-php-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
