Wednesday 20 April 2022

MapGuide dev diary: An important breakthrough!

Recently I have gotten back into the grind of MapGuide development pushing to clear the final hurdle of having functional PHP bindings for the MapGuide API.

Since I last touched this code, several things have changed.

  • We originally wanted to target PHP 7.x, but this is no longer the latest. PHP 8.1.x is the latest. This release of PHP 8.1 for Windows is also built with the same MSVC compiler we use for MapGuide and FDO (MSVC 2019), which means the stars have aligned again where we can source official windows PHP binaries for bundling.
  • Our last attempt used SWIG 4.0.2 (the most recent stable release). This release unfortunately cannot generate functional bindings for PHP 8, so our hand has been forced and we are now using and assuming current git master of SWIG for generating PHP bindings. The latest git master also no longer generates a [PHP extension .dll + .php proxy script] combination and goes back to generating just a PHP extension .dll, which is great because that how our (now) legacy PHP binding was generated.
Given these environmental changes, we've re-activated the PHP binding work with a semi-clean slate. A lot of hacks and workarounds that were in place because the previous SWIG generated a [PHP extension .dll + .php proxy script] combination have been removed.

The PHP binding work has now advanced to the stage where we can run this basic sanity test script against a binary PHP 8.1.4 distribution on Windows with no errors or crashes.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<?php

echo "Initializing web tier";
try {
    MgInitializeWebTier("C:\\mg-4.0-install\\Web\\www\\webconfig_dev.ini");
} catch (MgException $initEx) {
    echo "Init failure!";
    die;
} catch (Exception $ex) {
    echo "[php]: Exception: " . $ex->getMessage() . "\n";
    die;
}
echo "[php]: Initialized\n";

// We haven't and shouldn't need to require/include constants.php
// they are now baked into the PHP extension along with the other
// MapGuide API proxy classes
echo "[php]: Testing some constants\n";
echo "  - " . MgMimeType::Agf . "\n";
echo "  - " . MgMimeType::Kml . "\n";
echo "  - " . MgMimeType::Mvt . "\n";
echo "  - " . MgImageFormats::Png . "\n";
echo "  - " . MgImageFormats::Gif . "\n";
echo "  - " . MgLogFileType::Authentication . "\n";
echo "  - " . MgServerInformationProperties::ClientOperationsQueueCount . "\n";

$user = new MgUserInformation("Anonymous", "");
// Basic set/get
$user->SetLocale("en");
echo "[php]: Locale is: " . $user->GetLocale() . "\n";
$conn = new MgSiteConnection();
$conn->Open($user);
// Create a session repository
$site = $conn->GetSite();
$sessionID = $site->CreateSession();
echo "[php]: Created session: $sessionID\n";
$user->SetMgSessionId($sessionID);
// Get an instance of the required services.
$resourceService = $conn->CreateService(MgServiceType::ResourceService);
echo "[php]: Created Resource Service\n";
$mappingService = $conn->CreateService(MgServiceType::MappingService);
echo "[php]: Created Mapping Service\n";
$resId = new MgResourceIdentifier("Library://");
echo "[php]: Enumeratin'\n";
$resources = $resourceService->EnumerateResources($resId, -1, "");
echo $resources->ToString() . "\n";
echo "[php]: Coordinate System\n";
$csFactory = new MgCoordinateSystemFactory();
echo "[php]: CS Catalog\n";
$catalog = $csFactory->GetCatalog();
echo "[php]: Category Dictionary\n";
$catDict = $catalog->GetCategoryDictionary();
echo "[php]: CS Dictionary\n";
$csDict = $catalog->GetCoordinateSystemDictionary();
echo "[php]: Datum Dictionary\n";
$datumDict = $catalog->GetDatumDictionary();
echo "[php]: Coordinate System - LL84\n";
$cs1 = $csFactory->CreateFromCode("LL84");
echo "[php]: Coordinate System - WGS84.PseudoMercator\n";
$cs2 = $csFactory->CreateFromCode("WGS84.PseudoMercator");
echo "[php]: Make xform\n";
$xform = $csFactory->GetTransform($cs1, $cs2);
echo "[php]: WKT Reader\n";
$wktRw = new MgWktReaderWriter();
echo "[php]: WKT Point\n";
$pt = $wktRw->Read("POINT (1 2)");
$coord = $pt->GetCoordinate();
echo "[php]: X: ".$coord->GetX().", Y: ".$coord->GetY()."\n";
$site->DestroySession($sessionID);
echo "[php]: Destroyed session $sessionID\n";
echo "[php]: Test byte reader\n";
$bs = new MgByteSource("abcd1234", 8);
$content = "";
$br = $bs->GetReader();
$buf = "";
while ($br->Read($buf, 2) > 0) {
    echo "Buffer: '$buf'\n";
    $content .= $buf;
}
echo "[php]: Test byte reader2\n";
$bs2 = new MgByteSource("abcd1234", 8);
$content = "";
$br2 = $bs2->GetReader();
$buf = "";
while ($br2->Read($buf, 3) > 0) {
    echo "Buffer: '$buf'\n";
    $content .= $buf;
}
$agfRw = new MgAgfReaderWriter();
echo "[php]: Trigger an exception\n";
try {
    $agfRw->Read(NULL);
} catch (MgException $ex) {
    echo "[php]: MgException caught\n";
    echo "[php]: MgException - Code: ".$ex->GetExceptionCode()."\n";
    echo "[php]: MgException - Message: ".$ex->GetExceptionMessage()."\n";
    echo "[php]: MgException - Details: ".$ex->GetDetails()."\n";
    echo "[php]: MgException - Stack: ".$ex->GetStackTrace()."\n";
    echo "Is standard PHP exception too? " . ($ex instanceof Exception) . "\n";
} catch (Exception $ex) {
    echo "[php]: Exception: " . $ex->getMessage() . "\n";
}
echo "[php]: Trigger another exception\n";
try {
    $r2 = new MgResourceIdentifier("");
} catch (MgException $ex2) {
    echo "[php]: MgException caught\n";
    echo "[php]: MgException - Code: ".$ex2->GetExceptionCode()."\n";
    echo "[php]: MgException - Message: ".$ex2->GetExceptionMessage()."\n";
    echo "[php]: MgException - Details: ".$ex2->GetDetails()."\n";
    echo "[php]: MgException - Stack: ".$ex2->GetStackTrace()."\n";
    echo "Is standard PHP exception too? " . ($ex2 instanceof Exception) . "\n";
} catch (Exception $ex) {
    echo "[php]: Exception: " . $ex->getMessage() . "\n";
}

echo "Press any key to exit\n";
readline();

?>

This script is a "sanity check" in that it is testing the key aspects of the MapGuide API that *must* function properly.
  • We can init the web tier
  • We can create a site connection
  • We can request instances of MgService-derived subclasses with proper downcasting (eg. if we ask for a resource service, we get a MgResourceService and not its parent MgService)
  • We can catch MgExceptions and inspect all relevant properties from it.
  • We can access constants. Notice we aren't require/include-ing constants.php? That's because we've found a way to get SWIG to bake all MapGuide constants into the PHP extension itself so this file is no longer necessary!
  • We can interact with MgByteReader contents properly
Currently the sanity check script is failing on the last point and I strongly suspect it is another case of SWIG typemaps that were relevant for PHP7 but no longer valid for PHP8. We also need to make sure that this script doesn't leak memory. Also I've yet to get this binding building on Linux nor have I ran this sanity check script on Linux.

Once this is taken care of, the long journey begins to make sure everything else is working in order of importance:
  1. Our PHP test suite passes
  2. Our PHP web tier applications (site administrator, schema report, AJAX viewer) are functional
  3. Fusion is functional
  4. We can build updated Apache and PHP end-to-end on Linux
  5. All of the above is functional in both IIS and Apache web server configurations on both Windows and Linux
Then, and only then we can start thinking about a new MapGuide Open Source 4.0 preview/beta release.

No comments: