← Back to docs

NetCurl

NetCurl

NetCurl is a PHP communication library built around one core idea: application code should normally describe what to request, not which low-level transport must perform it.

The maintained 6.1 line can select between built-in communication drivers at runtime. The important compatibility contract is that callers should not need separate business logic for cURL, PHP streams, SOAP, RSS/XML, or registered custom drivers when the same logical request can be represented by more than one transport.

Source and issue tracking:

Installation

composer require tornevall/tornelib-php-netcurl:^6.1

Do not infer PHP support only from old README text or old build systems. The actively maintained compatibility information is the current GitHub Actions matrix in the repository.

The old PHP 5.6 compatibility claim is no longer valid for the current 6.1 source.

The important part: automatic driver selection

For a normal request, use NetWrapper and let NetCurl select a usable transport:

use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request('https://example.com/api/status');

$body = $client->getBody();
$status = $client->getCode();
$driver = $client->getCurrentWrapperClass(true);

The application does not need to instantiate CurlWrapper or SimpleStreamWrapper itself.

For the built-in drivers, 6.1 currently routes requests in this general order:

  1. SOAP for WSDL-style requests when SoapClient is available.
  2. RSS handling when DataType::RSS_XML is requested.
  3. cURL for ordinary HTTP requests when cURL is available.
  4. PHP stream handling when cURL is unavailable but streams can perform the request.
  5. Registered external drivers according to their configured priority.

This fallback behavior is part of NetCurl's core design, not an implementation accident.

cURL is preferred, not mandatory

A typical installation may include cURL, XML and SOAP support:

apt-get install php-curl php-xml php-soap

But generic HTTP communication must not require cURL just to initialize NetCurl. If cURL is missing and PHP streams are usable, NetCurl should be able to route the request through the stream driver instead.

For HTTP/HTTPS stream fallback in 6.1, PHP's URL-aware stream wrappers must be usable. In practice, allow_url_fopen must be enabled for SimpleStreamWrapper to handle remote URLs.

This behavior is now covered by explicit compatibility work because a regression was found where cURL constants were accessed before fallback could occur:

Use case: deliberately use one specific wrapper

NetWrapper is the normal application-facing choice because it preserves automatic routing and fallback. Direct wrapper access is still useful when the application deliberately guarantees a transport and wants transport-specific behavior.

For example, an environment that guarantees cURL can use CurlWrapper directly:

use TorneLIB\Module\Network\Wrappers\CurlWrapper;

$client = new CurlWrapper();
$client->request('https://example.com/api/status');

$body = $client->getBody();

Doing this intentionally gives up NetWrapper's automatic fallback. Prefer it only when that trade-off is part of the application's design or when diagnosing/testing an individual transport.

Use case: POST JSON, then choose raw or parsed output

Input format and output access are separate concerns.

use TorneLIB\Model\Type\DataType;
use TorneLIB\Model\Type\RequestMethod;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request(
    'https://example.com/api/items',
    [
        'name' => 'Example',
        'enabled' => true,
    ],
    RequestMethod::POST,
    DataType::JSON
);

$raw = $client->getBody();
$parsed = $client->getParsed();
$status = $client->getCode();

The request data is encoded as JSON for the transport, while the same completed request can still be inspected as a raw body or through NetCurl's parsed representation.

Use case: send XML without changing the calling model

use TorneLIB\Model\Type\DataType;
use TorneLIB\Model\Type\RequestMethod;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request(
    'https://example.com/api/xml',
    '<request><id>123</id></request>',
    RequestMethod::POST,
    DataType::XML
);

$response = $client->getParsed();

Arrays can also be converted to XML by the configuration layer when XML is the selected request data type.

Use case: normal form/query data

Use DataType::NORMAL for ordinary HTTP form/query handling:

use TorneLIB\Model\Type\DataType;
use TorneLIB\Model\Type\RequestMethod;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request(
    'https://example.com/api/search',
    ['query' => 'netcurl'],
    RequestMethod::GET,
    DataType::NORMAL
);

The same request method is used regardless of whether the selected transport becomes cURL or a stream implementation.

Use case: SOAP/WSDL without hard-coding SoapClient

A URL containing ?wsdl or &wsdl is treated as a SOAP/WSDL-style request by NetWrapper.

When PHP's SoapClient is available, NetCurl selects the SOAP wrapper. When SOAP is unavailable, the current 6.1 router can fall back to XML-over-HTTP where that request can be represented safely.

use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request('https://example.com/service?wsdl');

$driver = $client->getCurrentWrapperClass(true);

Do not instantiate SoapClientWrapper in ordinary application code merely to choose the transport. Direct wrapper access is intended for cases where transport-specific behavior is deliberately required.

Use case: RSS/XML

RSS can be selected through DataType::RSS_XML:

use TorneLIB\Model\Type\DataType;
use TorneLIB\Model\Type\RequestMethod;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$client->request(
    'https://example.com/feed.xml',
    [],
    RequestMethod::GET,
    DataType::RSS_XML
);

$feed = $client->getParsed();

Optional feed libraries may provide richer RSS handling, but NetCurl keeps its own fallback path so an optional dependency does not automatically become a hard requirement.

Use case: headers, authentication, proxy and timeout

Shared configuration is applied before the selected transport executes the request:

use TorneLIB\Model\Type\AuthType;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();

$client
    ->setHeader('X-Client', 'my-service')
    ->setAuthentication('username', 'password', AuthType::BASIC)
    ->setTimeout(15)
    ->setProxy('proxy.example.net:8080');

$client->request('https://example.com/private-api');

The point of the shared configuration layer is that the caller should not need one authentication/header/timeout setup for cURL and another for streams or SOAP when the selected driver supports the same logical option.

Per-instance state

Configuration belongs to the NetWrapper instance. Separate clients can therefore carry different headers, authentication, proxy and timeout state:

$first = new NetWrapper();
$first->setHeader('X-Tenant', 'first');

$second = new NetWrapper();
$second->setHeader('X-Tenant', 'second');

This behavior is being protected with deterministic contract tests before the 6.2 refactor.

Use case: multiple requests

NetWrapper::request() also accepts an associative request map. Each entry can carry its own data, method, data type and optional WrapperConfig:

use TorneLIB\Model\Type\DataType;
use TorneLIB\Model\Type\RequestMethod;
use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();

$client->request([
    'https://example.com/one' => [
        [],
        RequestMethod::GET,
        DataType::NORMAL,
    ],
    'https://example.com/two' => [
        ['name' => 'Example'],
        RequestMethod::POST,
        DataType::JSON,
    ],
]);

$firstBody = $client->getBody('https://example.com/one');
$secondParsed = $client->getParsed('https://example.com/two');

Multi-request behavior is part of the compatibility contract and is being expanded with deterministic tests rather than relying only on remote integration endpoints.

Use case: custom/external drivers

Applications can register an object implementing NetCurl's WrapperInterface:

$client = new NetWrapper();
$client->register($myDriver, true);
$client->request('custom-or-http-target');

The second argument controls whether registered drivers should be tried before the built-in drivers. With false, built-ins remain preferred and external drivers act as a later fallback.

This is an important extension point and is intended to remain available in 6.2 and 7.0.

Inspect which driver handled the request

Normally the caller should not care, but diagnostics can inspect the selected wrapper:

$client->request('https://example.com/');

printf(
    "Driver: %s\n",
    $client->getCurrentWrapperClass(true)
);

This is useful for tests, diagnostics and capability verification without making transport selection part of business logic.

Legacy MODULE_CURL

MODULE_CURL exists in 6.1 only as a compatibility facade for older 6.0-style clients.

Legacy code may still look like this:

use TorneLIB\MODULE_CURL;

$legacy = new MODULE_CURL();
$response = $legacy->doGet('https://example.com/');

For new 6.1 code, prefer NetWrapper:

use TorneLIB\Module\Network\NetWrapper;

$client = new NetWrapper();
$response = $client->request('https://example.com/');

MODULE_CURL is deprecated in 6.1 and planned for removal in 6.2. The migration is intentionally being designed so old calling styles can move forward without losing NetCurl's flexible argument and transport behavior.

6.1, 6.2 and 7.0 direction

6.1

6.1 is the compatibility/reference line. Existing behavior is being documented with broader deterministic tests and defects found by those tests are fixed without large architectural changes.

6.2

6.2 is the cleanup/rebuild bridge. MODULE_CURL is planned to disappear, but automatic driver selection, flexible request formats, custom drivers, raw/parsed response access and legacy-friendly calling patterns must not disappear with it.

7.0

7.0 is planned for PHP 8.3 and newer with typed/model-oriented internals. The modern core must still preserve NetCurl's original strength: callers can use a high-level facade while transports remain replaceable and automatically selected.

The cross-version requirements are tracked in:

Testing and compatibility

The maintained test strategy is split into two kinds of coverage:

  • deterministic local tests for configuration, parsing, input/output behavior and driver contracts
  • environment/capability tests where PHP extensions such as cURL or SOAP are explicitly removed so fallback is tested for real

Relevant tracking:

For current PHP support, use the repository's GitHub Actions results rather than historical Bamboo, Bitbucket, Confluence or old README claims.