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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 5 additions & 33 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,39 +138,6 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `ctype_space` | yes | yes | yes | ctype | JIT PHPT |
| `ctype_upper` | yes | yes | yes | ctype | JIT PHPT |
| `ctype_xdigit` | yes | yes | yes | ctype | JIT PHPT |
| `curl_close` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322) |
| `curl_copy_handle` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20495) |
| `curl_errno` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3325) |
| `curl_error` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3325) |
| `curl_escape` | yes | yes | yes | curl | JIT PHPT; AOT PHPT |
| `curl_exec` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3325) |
| `curl_file_create` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6790) |
| `curl_getinfo` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3325) |
| `curl_init` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322); JIT PHPT |
| `curl_multi_add_handle` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_close` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_errno` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20495) |
| `curl_multi_exec` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_getcontent` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_info_read` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20495) |
| `curl_multi_init` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_remove_handle` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_select` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #3721) |
| `curl_multi_setopt` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20495) |
| `curl_multi_strerror` | yes | yes | yes | curl | |
| `curl_pause` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20494) |
| `curl_reset` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20494) |
| `curl_setopt` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322); JIT PHPT; AOT PHPT |
| `curl_setopt_array` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6695) |
| `curl_share_close` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322) |
| `curl_share_errno` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20531) |
| `curl_share_init` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322) |
| `curl_share_setopt` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #6322) |
| `curl_share_strerror` | yes | yes | yes | curl | |
| `curl_strerror` | yes | yes | yes | curl | |
| `curl_unescape` | yes | yes | yes | curl | JIT PHPT |
| `curl_upkeep` | yes | no | no | curl | not implemented for JIT in this compiler build (issue #20977) |
| `curl_version` | yes | yes | yes | curl | |
| `current` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `date` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `date_add` | yes | yes | yes | standard | |
Expand Down Expand Up @@ -1044,6 +1011,11 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand.
| `spl_object_hash` | yes | yes | yes | standard | AOT PHPT |
| `spl_object_id` | yes | yes | yes | standard | |
| `sprintf` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `sqlsrv_close` | yes | no | no | sqlsrv | not implemented for JIT in this compiler build (issue #6577) |
| `sqlsrv_connect` | yes | no | no | sqlsrv | not implemented for JIT in this compiler build (issue #6577) |
| `sqlsrv_errors` | yes | no | no | sqlsrv | not implemented for JIT in this compiler build (issue #6577) |
| `sqlsrv_fetch_array` | yes | no | no | sqlsrv | not implemented for JIT in this compiler build (issue #6577) |
| `sqlsrv_query` | yes | no | no | sqlsrv | not implemented for JIT in this compiler build (issue #6577) |
| `sqrt` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
| `srand` | yes | yes | yes | standard | JIT PHPT |
| `sscanf` | yes | yes | yes | standard | JIT PHPT; AOT PHPT |
Expand Down
50 changes: 50 additions & 0 deletions ext/sqlsrv/Module.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\sqlsrv;

use PHPCompiler\ModuleAbstract;
use PHPCompiler\Runtime;

/**
* sqlsrv extension module entry (Microsoft sqlsrv / php-src ext/sqlsrv; #6577).
*
* Phase-1 procedural API — PHP-in-PHP; host ext/sqlsrv bridge when present.
* Without the Microsoft ODBC driver, connect returns false and sqlsrv_errors() is populated.
*/
class Module extends ModuleAbstract
{
private const SQLSRV_VERSION = '5.12.0';

public function getExtensionVersion(): string
{
return self::SQLSRV_VERSION;
}

public function init(Runtime $runtime): void
{
parent::init($runtime);
if (!SqlsrvExtensionPolicy::advertisesBuiltins()) {
return;
}
VmSqlsrvConnection::registerClass($runtime->vmContext);
}

public function getFunctions(): array
{
if (!SqlsrvExtensionPolicy::advertisesBuiltins()) {
return [];
}

require_once __DIR__.'/sqlsrv_builtins.php';

return [
new sqlsrv_connect(),
new sqlsrv_close(),
new sqlsrv_query(),
new sqlsrv_fetch_array(),
new sqlsrv_errors(),
];
}
}
36 changes: 36 additions & 0 deletions ext/sqlsrv/SqlsrvExtensionPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\sqlsrv;

/**
* ext/sqlsrv surface advertisement — Microsoft sqlsrv (#6577).
*
* sqlsrv_* symbols are always registered so {@code function_exists('sqlsrv_connect')}
* matches enterprise apps that probe before connecting. Live SQL Server I/O requires the
* Microsoft ODBC driver / host ext/sqlsrv; without it, connect returns false and
* {@see sqlsrv_errors} reports structured entries (php-src ext/sqlsrv shared/core_conn.cpp).
*/
final class SqlsrvExtensionPolicy
{
public static function advertisesExtension(): bool
{
return true;
}

public static function advertisesBuiltins(): bool
{
return true;
}

public static function hasNativeDriver(): bool
{
return \function_exists('\\sqlsrv_connect');
}

public static function isSqlsrvComplianceCase(string $testFileName): bool
{
return str_contains($testFileName, 'sqlsrv');
}
}
85 changes: 85 additions & 0 deletions ext/sqlsrv/VmSqlsrvConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\sqlsrv;

use PHPCompiler\VM\ClassEntry;
use PHPCompiler\VM\Context;
use PHPCompiler\VM\ObjectEntry;
use PHPCompiler\VM\Variable;

/**
* Sqlsrv\Connection resource object (php-src ext/sqlsrv; #6577).
*/
final class VmSqlsrvConnection
{
public const CLASS_LC = 'sqlsrv\\connection';

public const CLASS_NAME = 'Sqlsrv\\Connection';

/** @var array<int, array{closed: bool, native: mixed}> */
private static array $state = [];

public static function registerClass(Context $ctx): void
{
if (isset($ctx->classes[self::CLASS_LC])) {
return;
}
$entry = new ClassEntry(self::CLASS_NAME);
$entry->isInternal = true;
$ctx->classes[self::CLASS_LC] = $entry;
}

/**
* @param mixed $native host sqlsrv resource when bridged
*/
public static function wrap(mixed $native, Context $ctx): Variable
{
self::registerClass($ctx);
$object = new ObjectEntry($ctx->classes[self::CLASS_LC]);
$object->constructed = true;
self::$state[$object->id] = [
'closed' => false,
'native' => $native,
];
$var = new Variable(Variable::TYPE_OBJECT);
$var->object($object);

return $var;
}

public static function isLive(ObjectEntry $object): bool
{
return isset(self::$state[$object->id]) && !self::$state[$object->id]['closed'];
}

public static function native(ObjectEntry $object): mixed
{
return self::$state[$object->id]['native'] ?? null;
}

public static function close(ObjectEntry $object): bool
{
if (!isset(self::$state[$object->id]) || self::$state[$object->id]['closed']) {
return false;
}
$native = self::$state[$object->id]['native'];
if (SqlsrvExtensionPolicy::hasNativeDriver() && \is_resource($native)) {
\sqlsrv_close($native);
}
self::$state[$object->id]['closed'] = true;
unset(self::$state[$object->id]);

return true;
}

public static function requireLive(ObjectEntry $object, string $fn): ObjectEntry
{
if (!self::isLive($object)) {
throw new \TypeError($fn.'(): supplied resource is not a valid sqlsrv connection resource');
}

return $object;
}
}
158 changes: 158 additions & 0 deletions ext/sqlsrv/VmSqlsrvCore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\sqlsrv;

use PHPCompiler\VM\Context;
use PHPCompiler\VM\HashTable;
use PHPCompiler\VM\Variable;

/**
* Shared sqlsrv_* semantics (php-src ext/sqlsrv; #6577).
*/
final class VmSqlsrvCore
{
/** @var list<array{state: string, code: int, message: string}> */
private static array $errors = [];

public static function clearErrors(): void
{
self::$errors = [];
}

public static function pushError(string $state, int $code, string $message): void
{
self::$errors[] = [
'state' => $state,
'code' => $code,
'message' => $message,
];
}

/**
* @return list<array{state: string, code: int, message: string}>
*/
public static function peekErrors(): array
{
return self::$errors;
}

public static function connect(string $serverName, array $connectionInfo, Context $ctx): Variable|false
{
self::clearErrors();
if (!SqlsrvExtensionPolicy::hasNativeDriver()) {
self::pushError(
'IMSSP',
-49,
'This extension requires the Microsoft ODBC Driver for SQL Server to communicate with SQL Server'
);

return false;
}

$native = \sqlsrv_connect($serverName, $connectionInfo);
if (false === $native) {
self::importHostErrors();

return false;
}

return VmSqlsrvConnection::wrap($native, $ctx);
}

public static function importHostErrors(): void
{
self::$errors = [];
$hostErrors = \sqlsrv_errors();
if (!\is_array($hostErrors)) {
return;
}
foreach ($hostErrors as $row) {
if (!\is_array($row)) {
continue;
}
$state = (string) ($row['SQLSTATE'] ?? $row[0] ?? 'HY000');
$code = (int) ($row['code'] ?? $row[1] ?? 0);
$message = (string) ($row['message'] ?? $row[2] ?? '');
self::pushError($state, $code, $message);
}
}

public static function buildErrorsVariable(Variable $returnVar): void
{
if ([] === self::$errors) {
$returnVar->null();

return;
}
$outer = new HashTable();
$index = 0;
foreach (self::$errors as $err) {
$row = new HashTable();
$v0 = new Variable();
$v0->string($err['state']);
$row->add('0', $v0);
$vState = new Variable();
$vState->string($err['state']);
$row->add('SQLSTATE', $vState);
$v1 = new Variable();
$v1->int($err['code']);
$row->add('1', $v1);
$vCode = new Variable();
$vCode->int($err['code']);
$row->add('code', $vCode);
$v2 = new Variable();
$v2->string($err['message']);
$row->add('2', $v2);
$vMsg = new Variable();
$vMsg->string($err['message']);
$row->add('message', $vMsg);
$slot = new Variable();
$slot->array($row);
$outer->add((string) $index, $slot);
++$index;
}
$returnVar->array($outer);
}

/**
* @param array<int|string, mixed> $connectionInfo
*/
public static function coerceConnectionInfo(Variable $var, string $fn): array
{
$var = $var->resolveIndirect();
if (Variable::TYPE_NULL === $var->type) {
return [];
}
if (Variable::TYPE_ARRAY !== $var->type) {
throw new \TypeError(\sprintf(
'%s(): Argument #2 ($connectionInfo) must be of type array, %s given',
$fn,
match ($var->type) {
Variable::TYPE_STRING => 'string',
Variable::TYPE_INTEGER => 'int',
Variable::TYPE_OBJECT => 'object',
default => 'mixed',
}
));
}
$out = [];
foreach ($var->toArray()->iterateKeyed(true) as [$keyVar, $slot]) {
$key = $keyVar->toString();
if (Variable::TYPE_STRING === $slot->type) {
$out[$key] = $slot->toString();
} elseif (Variable::TYPE_INTEGER === $slot->type) {
$out[$key] = $slot->toInt();
} elseif (Variable::TYPE_BOOLEAN === $slot->type) {
$out[$key] = $slot->toBool();
} elseif (Variable::TYPE_NULL === $slot->type) {
$out[$key] = null;
} else {
$out[$key] = $slot->toString();
}
}

return $out;
}
}
Loading
Loading