/**
     @param $scanRectangleMap [scanUrn => [rectangle]]
     @param $user User, see auth/user.php
     @param $type int, see AreaOfInterestType
     @param $typeText String || null, see AreaOfInterestType
     @return $aoi AreaOfInterest || Exception
     Tries to create a new AreaOfInterest.
     Constraints:
     $scanUrn keys of $scanRectangleMap must be valid URNs for scans table.
     $scanRectangleMap must pass AreaOfInterestUrn::createUrn().
     $type must pass AreaOfInterestType::validType().
     $typeText may only be !== null, if AreaOfInterestType::hasText($type).
     Created urn may only have maximum length of 250.
 */
 public static function createAOI($scanRectangleMap, $user, $type, $typeText = null)
 {
     $fail = function ($reason) {
         //Fail helper
         return new Exception("Problem in AreaOfInterest::createAOI(…). Reason: {$reason}");
     };
     //Checking some constraints:
     if (!is_array($scanRectangleMap)) {
         return $fail('$scanRectangleMap must be an array.');
     }
     if (!$user instanceof User) {
         return $fail('$user must be instance of auth/user.php.');
     }
     if (!AreaOfInterestType::validType($type)) {
         return $fail('$type has invalid/unexpected value.');
     }
     if ($typeText !== null) {
         if (is_string($typeText)) {
             if (!AreaOfInterestType::hasText($type)) {
                 return $fail('$typeText given when it was not allowed.');
             } else {
                 //Storing empty string in db instead of null:
                 $typeText = '';
             }
         } else {
             return $fail('Invalid value for $typeText: ' . $typeText);
         }
     }
     //Checking scan URNs:
     $scanUrns = array_keys($scanRectangleMap);
     if (!OmekaFile::validateUrns($scanUrns)) {
         return $fail('At least one urn was invalid. $urns: ' . json_encode($scanUrns));
     }
     //Trying to create URN:
     $urn = AreaOfInterestUrn::createUrn($scanRectangleMap);
     if ($urn instanceof Exception) {
         $msg = $urn->getMessage();
         return $fail('Could not create URN. ' . $msg);
     }
     if (strlen($urn) > 250) {
         return $fail("\$urn is longer than 250 chars: '{$urn}'.");
     }
     //Input valid, can create AreaOfInterest:
     $q = 'INSERT INTO areasOfInterest (urn,userId,typeEnum,typeText) VALUES (?,?,?,?)';
     $stmt = Config::getDB()->prepare($q);
     $stmt->bind_param('siis', $urn, $user->getUserId(), $type, $typeText);
     $stmt->execute();
     $stmt->close();
     //Creating entries for scanAoiMap:
     $q = 'INSERT INTO scanAoiMap (aoiUrn, scanUrn) VALUES (?,?)';
     foreach ($scanUrns as $scanUrn) {
         $stmt = Config::getDB()->prepare($q);
         $stmt->bind_param('ss', $urn, $scanUrn);
         $stmt->execute();
         $stmt->close;
     }
     //Return AOI, iff possible:
     return self::getAOIFromUrn($urn);
 }
<?php

require_once 'areaOfInterestUrn.php';
$urns = array('urn:cite:olg:leiden_vlf123_0001.tif' . '@.2,0,.53,.4' . '+@0,0,.1,.001' . '+leiden_vlf123_0002.tif' . '@1,1,0,0', 'urn:cite:olg:leiden_vlf123_0001.tif' . '@1,2,0.53,.4' . '+@0,0,7.1,.001' . '+leiden_vlf123_0002.tif' . '@1,5,1,1', 'urn:cite:olg:leiden_vlf123_0001.tif' . '@@1,0,0.53,.4' . '++@0,0,.1,.001' . '+leiden_vlf123_0002.tif' . '@1,1,1,1', '¬URN');
echo "Testing some URN cases:\n";
foreach ($urns as $k => $urn) {
    $x = AreaOfInterestUrn::parseUrn($urn);
    if (is_array($x)) {
        $x = json_encode($x);
    }
    echo "{$urn} =>\n{$x}\n\n";
}
<?php

require_once 'areaOfInterestUrn.php';
$urn = 'urn:cite:olg:leiden_vlf123_0001.tif' . '@.2,0,.53,.4' . '+@0,0,.1,.001' . '+leiden_vlf123_0002.tif' . '@1,1,0,0';
$map = AreaOfInterestUrn::parseUrn($urn);
if (is_string($map)) {
    echo "Premature exit:\n{$map}\n";
} else {
    $_urn = AreaOfInterestUrn::createUrn($map);
    if ($urn === $_urn) {
        echo "Sucessfully handled '{$urn}'!\n";
    } else {
        echo "URNs turned out differently:\n'{$urn}'\n'{$_urn}'\n";
    }
}