Skip to content

reinaldocrespo/php_advantage

Repository files navigation

php_ads

A PHP 8 extension that wraps the Advantage Database Server (ADS) Client Engine (ACE) SDK, exposing native ADS connectivity to PHP via six clean OOP classes.


Requirements

Component Version
PHP 8.0.x — ZTS VC16 x64 (Thread Safe)
Advantage Database Server 11.x (ACE SDK 11.10)
Visual Studio 2022 (v17.x, MSVC toolset 14.4x)
OS Windows x64 (Server 2016 / 2019 / 2022 or Windows 10/11)
PHP Dev Pack php-8.0.1-devel-vs16-x64

Note on VS2022 vs VS2019: PHP 8.0.1 was compiled with VS2019 (linker 14.28). The build script automatically patches the output DLL's PE linker version field from 14.4x → 14.28 so PHP's extension loader accepts it.


Directory layout

php_advantage/
├── src/
│   ├── php_ads.h           # Shared header — structs, macros, class-entry externs
│   ├── php_ads.c           # Module entry, MINIT/RINIT, shared helpers, constants
│   ├── ads_connection.c    # AdsConnection class
│   ├── ads_statement.c     # AdsStatement class
│   ├── ads_table.c         # AdsTable class
│   ├── ads_misc.c          # AdsTransaction + AdsDictionary classes
│   ├── ads_prepared.c      # AdsPreparedStatement class
│   └── ads_arginfo.h       # PHP 8 arginfo descriptors for all methods
├── acesdk/                 # Advantage Client Engine SDK (ace.h, ace64.lib, DLLs)
├── php-devpack/            # PHP 8.0.1 development headers and import library
├── bin/zts/                # Build output: php_ads.dll
├── Makefile.win            # NMake build file
├── build.bat               # One-shot build script (sets up VS2022 + NMake + patch)
└── patch_linker_ver.ps1    # PE linker version patcher (14.4x → 14.28)

Building from source

1. Prerequisites

  • Install Visual Studio 2022 (Community or Build Tools) with the Desktop development with C++ workload.
  • Install PHP 8.0.x ZTS x64 (e.g. C:\php\).
  • Place the PHP 8.0.1 Dev Pack under php-devpack\php-8.0.1-devel-vs16-x64\ (already included).
  • Place the ACE SDK files under acesdk\ (ace.h + ace64.lib + DLLs — already included).

2. Build

Open a regular Command Prompt (not a Developer Command Prompt — build.bat sets that up):

cd F:\php_advantage
build.bat

The script:

  1. Initialises the VS2022 x64 toolchain via vcvars64.bat.
  2. Runs nmake /f Makefile.win → produces bin\zts\php_ads.dll.
  3. Patches the PE linker version field from 14.4x to 14.28.

3. Install

nmake /f Makefile.win install

This copies php_ads.dll to C:\php\ext\php_ads.dll.

Alternatively, copy manually:

copy bin\zts\php_ads.dll C:\php\ext\php_ads.dll

4. Copy ACE runtime DLLs

The ACE runtime DLLs must be discoverable at the time PHP loads the extension. The simplest approach is to copy them alongside php.exe:

copy acesdk\ace64.dll     C:\php\
copy acesdk\adsloc64.dll  C:\php\
copy acesdk\axcws64.dll   C:\php\
copy acesdk\aicu64.dll    C:\php\

5. Enable the extension in php.ini

Add one line to C:\php\php.ini:

extension=php_ads

6. Verify

C:\php\php.exe -m | findstr ads

Expected output: ads


API reference

All errors throw AdsException (extends RuntimeException). Error codes in getCode() are native ACE error numbers.


AdsConnection

// Static factory
$conn = AdsConnection::connect([
    'path'       => '\\\\server:6262\\share\\mydb.add', // required
    'user'       => 'username',                          // optional
    'password'   => 'secret',                            // optional
    'serverType' => ADS_REMOTE_SERVER,                   // optional, default: LOCAL|REMOTE
    'options'    => 0,                                   // optional ulOptions bitmask
]);

$conn->close();
$conn->isAlive() : bool
$conn->query(string $sql)           : AdsStatement
$conn->execute(string $sql)         : bool                 // for INSERT / UPDATE / DELETE / DDL
$conn->prepare(string $sql)         : AdsPreparedStatement // parameterized query
$conn->beginTransaction()           : AdsTransaction

AdsConnection does not have a constructor — always use AdsConnection::connect().


AdsStatement

Returned by AdsConnection::query().

$stmt = $conn->query("SELECT PropertyID, Address FROM properties");

$stmt->fetchAssoc()   : array|false   // next row as associative array, false at EOF
$stmt->fetchRow()     : array|false   // next row as indexed array, false at EOF
$stmt->fetchAll()     : array         // all remaining rows as array of assoc arrays
$stmt->columnCount()  : int
$stmt->rowCount()     : int           // total records in result set
$stmt->close()        : void

AdsPreparedStatement

Returned by AdsConnection::prepare(). Use parameterized queries to safely pass user-supplied values without string interpolation or manual escaping.

Parameter syntax

  • Write :name placeholders in the SQL string.
  • Call a bind* method for each placeholder before calling execute().
  • The leading : is optional when calling bind methods — "status" and ":status" are equivalent.
  • ? positional placeholders are not supported; always use named :name markers.

Method reference

$prep->bindString   (string $name, string $value)             : void
$prep->bindInt      (string $name, int    $value)             : void
$prep->bindDouble   (string $name, float  $value)             : void
$prep->bindBool     (string $name, bool   $value)             : void
$prep->bindDate     (string $name, string $value)             : void  // "CCYYMMDD"
$prep->bindTimestamp(string $name, string $value)             : void  // "YYYY-MM-DD HH:MM:SS"
$prep->bindMoney    (string $name, int    $value)             : void  // SIGNED64 scaled integer
$prep->bindBinary   (string $name, string $data, int $type = ADS_BINARY) : void
$prep->bindNull     (string $name)                            : void
$prep->bind         (string $name, mixed  $value)             : void  // auto-detects PHP type
$prep->execute()                                              : AdsStatement|true
$prep->paramCount()                                           : int
$prep->close()                                                : void

bind() type mapping:

PHP type ACE call
null AdsSetNull
bool AdsSetLogical
int AdsSetLong
float AdsSetDouble
string AdsSetString

bindMoney() note: ADS_MONEY fields store values as a 64-bit scaled integer. The scale factor is set per-field in the table schema (commonly 4 decimal places, so $10.00 must be passed as 100000). Use bindMoney() rather than bindDouble() to avoid floating-point rounding errors.

bindString() and memo fields: bindString() works correctly for ADS_MEMO and ADS_NMEMO parameters. ACE treats memo columns as variable-length text; pass the full string including content longer than any VARCHAR limit.

bindBinary() note: Writes binary data to an ADS_BINARY (BLOB), ADS_IMAGE, or ADS_RAW parameter in a single call. The $type constant selects the ACE blob type: ADS_BINARY (6, default) for arbitrary binary data, ADS_IMAGE (7) for bitmap/image fields. Pass the raw bytes as a PHP string. Do not use bindBinary() for memo (ADS_MEMO) columns — use bindString() instead.

Return value of execute()

Query type Returns
SELECT AdsStatement — cursor positioned at first row
INSERT / UPDATE / DELETE / DDL true

After a SELECT execute(), the statement handle is transferred to the returned AdsStatement. The prepared object is marked closed; call prepare() again for a subsequent execution of the same SQL.


AdsTable

Direct (non-SQL) table access.

$tbl = AdsTable::open(
    $conn,
    'C:\\data\\mytable.adt',
    ADS_ADT,                   // table type
    ADS_COMPATIBLE_LOCKING,    // lock type
    ADS_ANSI,                  // character set
    ADS_SHARED                 // open mode
);

// Navigation
$tbl->gotoTop();
$tbl->gotoBottom();
$tbl->gotoRecord(int $n);
$tbl->skip(int $n = 1);
$tbl->atEOF()   : bool
$tbl->atBOF()   : bool

// Reading fields
$tbl->getString(string $field)  : string
$tbl->getLong(string $field)    : int
$tbl->getDouble(string $field)  : float
$tbl->getLogical(string $field) : bool
$tbl->getRecord()               : array   // all fields as assoc array

// Writing fields (call writeRecord() to flush)
$tbl->setString(string $field, string $value)
$tbl->setLong(string $field, int $value)
$tbl->setDouble(string $field, float $value)
$tbl->setLogical(string $field, bool $value)

// Record operations
$tbl->recordCount()     : int
$tbl->recordNum()       : int
$tbl->appendRecord()            // adds a blank record and locks it
$tbl->writeRecord()             // flush pending changes
$tbl->cancelUpdate()            // discard pending changes
$tbl->deleteRecord()            // mark current record deleted

$tbl->close();

AdsTransaction

$tx = $conn->beginTransaction();

$tx->isActive()  : bool
$tx->commit()    : void
$tx->rollback()  : void

AdsDictionary

Full CRUD access to the SAP ACE data dictionary. Wraps AdsDDxxx functions directly — no SQL, no ODBC.

// Open independently (creates its own connection to the .add file)
$dd = AdsDictionary::open(
    '\\\\server:6262\\share\\mydb.add', // path — required
    ADS_REMOTE_SERVER,                   // server type — optional
    'username',                          // user — optional
    'password'                           // password — optional
);

// Or borrow an existing connection (does not disconnect on close)
$dd = AdsDictionary::fromConnection($conn);

$dd->close();

Database

$dd->getDatabaseProperty(int $prop)                      : string
$dd->setDatabaseProperty(int $prop, string $value)       : void

Tables

$dd->addTable(string $alias, string $path,
              int $tableType = ADS_ADT, int $charType = ADS_ANSI,
              string $indexPath = '', string $comment = '')  : void
$dd->removeTable(string $alias, bool $deleteFiles = false)  : void
$dd->getTableProperty(string $table, int $prop)             : string
$dd->setTableProperty(string $table, int $prop, string $val): void

Fields

$dd->getFieldProperty(string $table, string $field, int $prop)            : string
$dd->setFieldProperty(string $table, string $field, int $prop, string $val): void

Indexes

$dd->addIndexFile(string $table, string $indexPath, string $comment = '') : void
$dd->removeIndexFile(string $table, string $indexPath, bool $del = false) : void
$dd->getIndexProperty(string $table, string $index, int $prop)            : string
$dd->setIndexProperty(string $table, string $index, int $prop, string $val): void

Users

$dd->createUser(string $user, string $password = '',
                string $group = '', string $desc = '')          : void
$dd->deleteUser(string $user)                                   : void
$dd->getUserProperty(string $user, int $prop)                   : string
$dd->setUserProperty(string $user, int $prop, string $val)      : void
$dd->addUserToGroup(string $user, string $group)                : void
$dd->removeUserFromGroup(string $user, string $group)           : void
$dd->getUserTableRights(string $table, string $user)            : int   // bitmask
$dd->setUserTableRights(string $table, string $user, int $rights): void // revoke-all then grant

Views

$dd->createView(string $name, string $sql, string $comment = '') : void
$dd->dropView(string $name)                                      : void
$dd->getViewProperty(string $view, int $prop)                    : string
$dd->setViewProperty(string $view, int $prop, string $val)       : void

Stored Procedures

$dd->createProcedure(string $name, string $container, string $procedure,
                     string $input = '', string $output = '',
                     string $comment = '')              : void
$dd->dropProcedure(string $name)                       : void
$dd->getProcProperty(string $name, int $prop)          : string
$dd->setProcProperty(string $name, int $prop, string $val): void

Triggers

// 7 required args; priority, comment, options are optional
$dd->createTrigger(string $name, string $table,
                   int $triggerType, int $eventTypes, int $containerType,
                   string $container, string $procedure,
                   int $priority = 1, string $comment = '', int $options = 0): void
$dd->dropTrigger(string $name)                          : void
$dd->getTriggerProperty(string $name, int $prop)        : string
$dd->setTriggerProperty(...)                            // throws — not supported by SAP ACE

Referential Integrity

// updateRule and deleteRule are optional (default 0)
$dd->createRefIntegrity(string $name, string $failTable,
                        string $parent, string $parentTag,
                        string $child,  string $childTag,
                        int $updateRule = 0, int $deleteRule = 0): void
$dd->removeRefIntegrity(string $name)                             : void

Links (cross-dictionary)

$dd->createLink(string $alias, string $path,
                string $user = '', string $password = '') : void
$dd->dropLink(string $alias)                             : void
$dd->modifyLink(string $alias, string $path = '',
                string $user = '', string $password = '') : void

Full example — create a table and configure it

<?php
$dd = AdsDictionary::open('\\\\srv\\data\\mydb.add', ADS_REMOTE_SERVER, 'admin', 'secret');

// Register an existing physical table in the dictionary
$dd->addTable('invoices', 'C:\\data\\invoices.adt', ADS_ADT, ADS_ANSI,
              'C:\\data\\invoices.cdx', 'Customer invoices');

// Set a display description
$dd->setTableProperty('invoices', ADS_DD_TABLE_DESCRIPTION, 'Customer invoice table');

// Create a view over it
$dd->createView('open_invoices',
    "SELECT id, customer_id, amount FROM invoices WHERE paid = FALSE",
    'Unpaid invoices');

// Create a stored procedure
$dd->createProcedure('sp_close_invoice', 'procs.dll', 'CloseInvoice',
    '@invoice_id INTEGER', '', 'Mark an invoice as paid');

// Add a user and grant read access to the table
$dd->createUser('reports_user', 'rp@ss', 'readers', 'Read-only reporting account');
$dd->setUserTableRights('invoices', 'reports_user', ADS_READ_RIGHT);

// Set up referential integrity: invoices.customer_id → customers.id
$dd->createRefIntegrity('ri_inv_cust', 'ri_errors',
    'customers', 'cust_pk', 'invoices', 'inv_fk',
    ADS_RI_RESTRICT, ADS_RI_RESTRICT);

$dd->close();

AdsException

try {
    $conn = AdsConnection::connect(['path' => '...']);
} catch (AdsException $e) {
    echo $e->getMessage();   // human-readable ACE message (+ last-error detail)
    echo $e->getCode();      // native ACE error code
}

Registered constants

Category Constants
Server type ADS_LOCAL_SERVER, ADS_REMOTE_SERVER
Table type ADS_NTX, ADS_CDX, ADS_ADT, ADS_VFP
Character set ADS_ANSI, ADS_OEM
Open mode ADS_SHARED, ADS_EXCLUSIVE
Locking ADS_COMPATIBLE_LOCKING, ADS_PROPRIETARY_LOCKING
Rights ADS_CHECKRIGHTS, ADS_IGNORERIGHTS
Filters ADS_RESPECTFILTERS, ADS_IGNOREFILTERS
String trim ADS_TRIM, ADS_LTRIM, ADS_RTRIM
Field types ADS_LOGICAL, ADS_NUMERIC, ADS_DATE, ADS_STRING, ADS_MEMO, ADS_BINARY, ADS_IMAGE, ADS_VARCHAR, ADS_DOUBLE, ADS_INTEGER, ADS_SHORTINT, ADS_TIME, ADS_TIMESTAMP, ADS_AUTOINC, ADS_RAW, ADS_CURDOUBLE, ADS_MONEY, ADS_ROWVERSION, ADS_MODTIME, ADS_NCHAR, ADS_NMEMO

Usage examples

Connect and run a SQL query

<?php
$conn = AdsConnection::connect([
    'path'       => '\\\\192.168.0.10:6262\\share\\mydb.add',
    'user'       => 'admin',
    'password'   => 'secret',
    'serverType' => ADS_REMOTE_SERVER,
]);

$stmt = $conn->query(
    "SELECT TOP 10 CustomerID, Name, Balance FROM customers ORDER BY Balance DESC"
);

echo $stmt->columnCount() . " columns, " . $stmt->rowCount() . " rows\n";

while (($row = $stmt->fetchAssoc()) !== false) {
    printf("%-6d  %-30s  %10.2f\n",
           $row['CustomerID'], trim($row['Name']), $row['Balance']);
}

$stmt->close();
$conn->close();

Transaction with rollback on error

<?php
$conn = AdsConnection::connect(['path' => '...']);
$tx   = $conn->beginTransaction();

try {
    $conn->execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $conn->execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    $tx->commit();
} catch (AdsException $e) {
    $tx->rollback();
    throw $e;
}

$conn->close();

Direct table access

<?php
$conn = AdsConnection::connect(['path' => '...']);

$tbl = AdsTable::open($conn, 'C:\\data\\inventory.adt', ADS_ADT,
                      ADS_COMPATIBLE_LOCKING, ADS_ANSI, ADS_SHARED);

$tbl->gotoTop();
while (!$tbl->atEOF()) {
    $row = $tbl->getRecord();
    echo $row['SKU'] . ': ' . $row['Description'] . "\n";
    $tbl->skip();
}

$tbl->close();
$conn->close();

Append a new record

<?php
$conn = AdsConnection::connect(['path' => '...']);
$tbl  = AdsTable::open($conn, 'C:\\data\\inventory.adt', ADS_ADT,
                       ADS_COMPATIBLE_LOCKING, ADS_ANSI, ADS_EXCLUSIVE);

$tbl->appendRecord();
$tbl->setString('SKU',         'WIDGET-42');
$tbl->setString('Description', 'Blue Widget');
$tbl->setDouble('Price',       9.99);
$tbl->setLong  ('Stock',       100);
$tbl->setLogical('Active',     true);
$tbl->writeRecord();

$tbl->close();
$conn->close();

Parameterized SELECT query

Use prepare() instead of query() whenever query criteria come from user input or application variables. Parameters are never interpreted as SQL, so there is no risk of SQL injection.

<?php
$conn = AdsConnection::connect([
    'path'       => '\\\\192.168.0.10:6262\\share\\mydb.add',
    'user'       => 'admin',
    'password'   => 'secret',
    'serverType' => ADS_REMOTE_SERVER,
]);

// Prepare once, bind, execute
$prep = $conn->prepare(
    "SELECT CustomerID, Name, Balance
     FROM   customers
     WHERE  Status = :status
     AND    Balance > :minbal
     ORDER  BY Balance DESC"
);

$prep->bindString('status', 'ACTIVE');
$prep->bindDouble('minbal', 1000.00);

$stmt = $prep->execute();   // returns AdsStatement

echo $stmt->rowCount() . " customers found\n";

while (($row = $stmt->fetchAssoc()) !== false) {
    printf("%6d  %-30s  %10.2f\n",
           $row['CustomerID'], trim($row['Name']), $row['Balance']);
}

$stmt->close();
$conn->close();

Parameterized INSERT

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);

$prep = $conn->prepare(
    "INSERT INTO products (SKU, Description, Price, Stock, Active, CreatedDate)
     VALUES (:sku, :desc, :price, :stock, :active, :created)"
);

$prep->bindString   ('sku',     'WIDGET-99');
$prep->bindString   ('desc',    'Deluxe Widget');
$prep->bindDouble   ('price',   24.99);
$prep->bindInt      ('stock',   250);
$prep->bindBool     ('active',  true);
$prep->bindDate     ('created', '20260521');   // CCYYMMDD format

$prep->execute();   // INSERT → returns true

echo "Product inserted.\n";

$conn->close();

Batch INSERT using a loop

Prepare the statement once, then re-bind and re-execute for each row. This is significantly faster than issuing individual execute() calls with concatenated SQL strings.

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);
$tx   = $conn->beginTransaction();

$products = [
    ['SKU' => 'AAA-01', 'Description' => 'Alpha Widget', 'Price' => 9.99,  'Stock' => 100],
    ['SKU' => 'BBB-02', 'Description' => 'Beta Widget',  'Price' => 14.99, 'Stock' => 50],
    ['SKU' => 'CCC-03', 'Description' => 'Gamma Widget', 'Price' => 4.99,  'Stock' => 200],
];

try {
    // Prepare the INSERT once outside the loop
    $prep = $conn->prepare(
        "INSERT INTO products (SKU, Description, Price, Stock)
         VALUES (:sku, :desc, :price, :stock)"
    );

    foreach ($products as $p) {
        $prep->bindString('sku',   $p['SKU']);
        $prep->bindString('desc',  $p['Description']);
        $prep->bindDouble('price', $p['Price']);
        $prep->bindInt   ('stock', $p['Stock']);
        $prep->execute();

        // Re-prepare after each execute() to reset the handle for the next iteration
        $prep = $conn->prepare(
            "INSERT INTO products (SKU, Description, Price, Stock)
             VALUES (:sku, :desc, :price, :stock)"
        );
    }

    $tx->commit();
    echo count($products) . " products inserted.\n";

} catch (AdsException $e) {
    $tx->rollback();
    echo "Batch failed: " . $e->getMessage() . "\n";
}

$conn->close();

Note: After execute() the prepared handle is consumed (transferred to the returned AdsStatement for SELECT, or released for DML). Call prepare() again before the next iteration. For INSERT/UPDATE/DELETE loops, wrapping in a transaction also provides a substantial speed improvement over auto-commit.

Parameterized UPDATE with transaction

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);
$tx   = $conn->beginTransaction();

try {
    // Deactivate all records in a category
    $disable = $conn->prepare(
        "UPDATE products SET Active = :flag WHERE CategoryID = :catid"
    );
    $disable->bindBool('flag',  false);
    $disable->bindInt ('catid', 7);
    $disable->execute();

    // Record the change in an audit log
    $audit = $conn->prepare(
        "INSERT INTO audit_log (Action, TableName, RecordID, ChangedAt)
         VALUES (:action, :tbl, :recid, :ts)"
    );
    $audit->bindString   ('action', 'DEACTIVATE');
    $audit->bindString   ('tbl',    'products');
    $audit->bindInt      ('recid',  7);
    $audit->bindTimestamp('ts',     date('Y-m-d H:i:s'));
    $audit->execute();

    $tx->commit();
    echo "Category deactivated and audit entry written.\n";

} catch (AdsException $e) {
    $tx->rollback();
    echo "Update failed: " . $e->getMessage() . "\n";
    throw $e;
}

$conn->close();

Using NULL parameters and auto-detect bind

bind() automatically selects the correct ACE setter based on the PHP type of the value, including null. Use it when building generic data-access layers where the type is determined at runtime.

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);

$prep = $conn->prepare(
    "UPDATE customers
     SET    Phone    = :phone,
            FaxNo    = :fax,
            CreditLimit = :limit,
            Active   = :active
     WHERE  CustomerID = :id"
);

// $fax may be null if the customer has no fax number
$phone = '+1-555-0100';
$fax   = null;            // will bind as SQL NULL
$limit = 5000.00;
$active = true;
$customerId = 42;

$prep->bind('phone',  $phone);     // string  → AdsSetString
$prep->bind('fax',    $fax);       // null    → AdsSetNull
$prep->bind('limit',  $limit);     // float   → AdsSetDouble
$prep->bind('active', $active);    // bool    → AdsSetLogical
$prep->bind('id',     $customerId); // int     → AdsSetLong

$prep->execute();

echo "Customer updated.\n";
$conn->close();

Storing money values with bindMoney()

ADS_MONEY fields are stored internally as a 64-bit scaled integer, not a floating-point number. The scale (number of implied decimal places) is defined per-field in the table schema — commonly 4, meaning the value is stored in units of 0.0001 of the currency. Always use bindMoney() for money fields; bindDouble() will corrupt values through floating-point rounding.

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);

// Helper: convert a decimal dollar amount to the ADS_MONEY integer
// representation with 4 implied decimal places.
function moneyToScaled(float $dollars): int
{
    return (int) round($dollars * 10000);
}

$prep = $conn->prepare(
    "INSERT INTO invoices (CustomerID, InvoiceDate, Amount, Tax, Total)
     VALUES (:cid, :invdate, :amount, :tax, :total)"
);

$amount = 1234.56;   // $1,234.56
$tax    = $amount * 0.08;
$total  = $amount + $tax;

$prep->bindInt   ('cid',     42);
$prep->bindDate  ('invdate', date('Ymd'));          // CCYYMMDD
$prep->bindMoney ('amount',  moneyToScaled($amount));  // 12345600
$prep->bindMoney ('tax',     moneyToScaled($tax));     // 988448  (≈ $98.8448)
$prep->bindMoney ('total',   moneyToScaled($total));   // 13334048

$prep->execute();
echo "Invoice inserted.\n";

// Reading money back: divide by scale factor to get decimal value
$prep2 = $conn->prepare(
    "SELECT Amount, Tax, Total FROM invoices WHERE CustomerID = :cid ORDER BY InvoiceDate DESC"
);
$prep2->bindInt('cid', 42);
$stmt = $prep2->execute();

while (($row = $stmt->fetchAssoc()) !== false) {
    // ADS returns money fields as strings when fetched via AdsGetString
    printf("Amount: %s  Tax: %s  Total: %s\n",
           $row['Amount'], $row['Tax'], $row['Total']);
}
$stmt->close();

$conn->close();

Storing and retrieving binary data with bindBinary()

Use bindBinary() for ADS_BINARY (BLOB), ADS_IMAGE, and ADS_RAW fields. Pass the raw bytes as a PHP string. For image fields, pass ADS_IMAGE as the $type argument; for arbitrary binary data, use the default ADS_BINARY.

<?php
$conn = AdsConnection::connect(['path' => '...', 'serverType' => ADS_REMOTE_SERVER]);

// ---- Store an image from disk ----
$imageData = file_get_contents('C:\\images\\product_42.jpg');
if ($imageData === false) {
    die("Could not read image file.\n");
}

$prep = $conn->prepare(
    "UPDATE products
     SET    Photo       = :photo,
            PhotoSize   = :size,
            PhotoFormat = :fmt
     WHERE  ProductID   = :pid"
);

$prep->bindBinary('photo', $imageData, ADS_IMAGE);   // store as image BLOB
$prep->bindInt   ('size',  strlen($imageData));
$prep->bindString('fmt',   'JPEG');
$prep->bindInt   ('pid',   42);

$prep->execute();
echo "Photo stored (" . strlen($imageData) . " bytes).\n";

// ---- Store a raw binary document (PDF, etc.) ----
$pdfData = file_get_contents('C:\\docs\\contract_7.pdf');

$prep2 = $conn->prepare(
    "INSERT INTO documents (CustomerID, DocType, Content, FileSize)
     VALUES (:cid, :dtype, :content, :sz)"
);

$prep2->bindInt   ('cid',     100);
$prep2->bindString('dtype',   'PDF');
$prep2->bindBinary('content', $pdfData);   // ADS_BINARY default
$prep2->bindInt   ('sz',      strlen($pdfData));

$prep2->execute();
echo "Document stored (" . strlen($pdfData) . " bytes).\n";

$conn->close();

Apache / mod_php setup

If running under Apache with mod_php:

  1. Install the extension as above (C:\php\ext\php_ads.dll).
  2. Make sure C:\php\ (where the ACE DLLs live) is in the system PATH, not just the user PATH — Apache runs as a service under SYSTEM.
  3. Restart Apache: httpd -k restart or via Services.
# C:\Apache24\conf\httpd.conf
LoadModule php_module "C:/php/php8apache2_4.dll"
PHPIniDir "C:/php"
AddType application/x-httpd-php .php

Troubleshooting

Symptom Cause Fix
PHP Warning: PHP Startup: Unable to load dynamic library 'php_ads' DLL not found or ACE DLLs missing Check C:\php\ext\php_ads.dll exists; copy ACE DLLs to C:\php\
Extension loads but AdsConnection::connect() throws immediately ACE DLLs not on PATH at load time Copy ace64.dll, adsloc64.dll, axcws64.dll, aicu64.dll to the php.exe directory
Build fails with C1083: Cannot open include file: 'php.h' Dev pack path wrong Check PHP_DEVPACK in Makefile.win
Extension loads but crashes (0xC0000005) ZTS TSRM cache not initialised Ensure ZEND_TSRMLS_CACHE_EXTERN() is in php_ads.h and ZEND_TSRMLS_CACHE_UPDATE() is called in both MINIT and RINIT
AdsException [5091] or similar ACE error at connect Server not reachable or wrong path Verify ADS server is running and the path uses the correct UNC format

License

This extension is provided as-is for use with licensed copies of Advantage Database Server. The ACE SDK headers and libraries (acesdk/) are copyright SAP / iAnywhere Solutions and are included under the terms of the ADS developer license — they must not be redistributed separately.


See also

About

php extension for SAP ADS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages