Skip to content
This repository was archived by the owner on Dec 11, 2022. It is now read-only.

Entity Querying

Hunter Perrin edited this page Jul 24, 2017 · 30 revisions

The real power behind Nymph is the entity querying system. After all, what's the use of having complex data and relationships if you can't find them?

Factory Method

The simplest way to get an entity is usually its factory static method. The Entity class's factory method expects a GUID as an argument. However, the author of an entity's class can use whatever they like as arguments. For example, the User class in Tilmeld takes either a GUID or a username. The method will return a brand new entity if the queried entity is not found.

ℹ️ Tip: If you're using a user provided value as the GUID, casting it to an int will ensure it is evaluated as a GUID. Also, non-numeric values will be evaluated as 0, resulting in a new entity, since no entity has GUID 0. Then you can determine if it was found by checking that its guid is set.

Getting Entities Using the Factory Method in PHP
// Entities probably expect a GUID.
$baz = FoobarBaz::factory((int) $_REQUEST['id']);

if (!isset($baz->guid)) {
  echo 'The specified Foobar Baz cannot be found!';
  return;
}

// When selecting a user (which is a class that extends Entity) in Tilmeld, you can use a GUID or a username.
$cronUser = User::factory('cron');
if (!isset($cronUser->guid)) {
  echo 'Can\'t find the cron user!';
  return;
}

// If the script made it to this point, both $baz and $cronUser were found!
echo 'Hooray! I found the Foobar Baz you wanted and the cron user!';

Nymph's Query Language

The powerful way of querying entities is Nymph's getEntities and getEntity methods.

The first argument to these methods is an options parameter. It is an associative array in PHP or an object in JS. The options can be empty, in which case the Entity class will be used for returned entities. (Note that you can't use the Entity class directly through REST.) The following is a list of options which Nymph supports.

Entity Querying Options
Option Type Default Description
class string "Entity" The class used to create each entity. It must have a factory static method that returns a new instance.
limit int null The limit of entities to be returned. Not needed when using getEntity, as it always returns only one.
offset int 0 The offset from the first matching entity, in order, to start retrieving.
reverse bool false If true, entities will be retrieved from newest to oldest.
sort string "cdate" How to sort the entities. Accepts "guid", "cdate", and "mdate".

A plugin or Nymph driver is free to add extra options, but you should consider prepending its name to the name of the option. For example, if your plugin, EntityVersions, wants to add the option "date", it should use "entityVersionsDate".

Every argument following the options parameter is a selector. A selector is an associative array (object or array in JS) of criteria and/or nested selectors. An entity must match each selector's criteria to be returned. The first member of the array (the value at index 0, or the "type" entry in JS) is the type of selector, which determines how the criteria are matched against an entity's data.

Entity Selector Types
Type Name Description
& And All values in the selector must be true.
| Or At least one value in the selector must be true.
!& Not And All values in the selector must be false.
!| Not Or At least one value in the selector must be false.

The subsequent members of the array are either nested selectors or the criteria of the selector. Criteria must be in the form ['name' => $value], or ['name' => [$value1, $value2, ...]]. They can be negated by prepending a bang (!) to the name, such as ['!tag' => 'user'].

Entity Selector Criteria
Name Value Condition Example Selector Matching Entity
guid A GUID. True if the entity's GUID is equal. ['&', 'guid' => 12] $entity->guid = 12;
tag A tag. True if the entity has the tag. ['&', 'tag' => 'foobar'] $entity->addTag('foobar');
isset A name. True if the named variable exists and is not null. ['&', 'isset' => 'foo'] $entity->foo = 0;
data An array with a name, then value. True if the named variable is defined and equal. ['&', 'data' => ['foo', false]] $entity->foo = 0;
strict An array with a name, then value. True if the named variable is defined and identical. ['&', 'strict' => ['foo', 0]] $entity->foo = 0;
array An array with a name, then value. True if the named variable is an array containing the value. Uses inArray. ['&', 'array' => ['foo', 'bar']] $entity->foo = ['bar', 'baz'];
match An array with a name, then regular expression. True if the named variable matches. Uses preg_match. More powerful than "pmatch" but slower. Must be surrounded by "/" delimiters. ['&', 'match' => ['foo', '/(ba(r|z))+/']] $entity->foo = 'foobarbaz';
pmatch An array with a name, then regular expression. True if the named variable matches. Uses POSIX RegExp. Case sensitive. Faster than "match". Must not be surrounded by any delimiters. ['&', 'pmatch' => ['foo', 'bar.*z']] $entity->foo = 'foobarbaz';
like An array with a name, then pattern. True if the named variable matches. Uses % for variable length wildcard and _ for single character wildcard. ['&', 'like' => ['foo', 'f%bar_az']] $entity->foo = 'foobarbaz';
gt An array with a name, then value. True if the named variable is greater than the value. ['&', 'gt' => ['foo', 5]] $entity->foo = 6;
gte An array with a name, then value. True if the named variable is greater than or equal to the value. ['&', 'gte' => ['foo', 6]] $entity->foo = 6;
lt An array with a name, then value. True if the named variable is less than the value. ['&', 'lt' => ['foo', 7]] $entity->foo = 6;
lte An array with a name, then value. True if the named variable is less than or equal to the value. ['&', 'lte' => ['foo', 6]] $entity->foo = 6;
ref An array with a name, then either a entity, or a GUID. True if the named variable is the entity or an array containing the entity. ['&', 'ref' => ['foo', 12]] $entity->foo = Entity::factory(12);

Query Results

In PHP, your query will just return entities. In JavaScript, however, your query needs to be sent to the server, so the function will return a promise. You can use the then method on the returned promise for a single callback, or the subscribe method to have your callback called any time the query updates.

Handling a Query
Nymph.getEntities(/* entity query here */).then(entities => {
    // You now have successfully retrieved some entities.
    console.log(entities);
}, errObj => {
    // An error occurred, so you've been passed an errObj. It contains some useful info:
    console.log(errObj.status); // The HTTP status code of the AJAX response.
    console.log(errObj.textStatus); // The text of the AJAX response.
});

Querying Examples

So putting it all together, you can specify any of the options, and any number of selectors to find the exact entities you want.

Getting Entities by Querying, in JavaScript
import Nymph from "Nymph";

// Get the first FoobarBaz entity.
let entityPromise = Nymph.getEntity({'class': FoobarBaz.class});

// Get the latest FoobarBaz entity.
let entityPromise = Nymph.getEntity({'class': FoobarBaz.class, 'reverse': true});

// Get all baz tagged entities, using the FoobarBaz class.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz'
    }
  );
// or
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    ['&',
      {'tag': 'baz'}
    ]
  );

// Get the five newest bar and baz tagged entities.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class, 'reverse': true, 'limit': 5},
    {'type': '&',
      'tag': ['bar', 'baz']
    }
  );

// Get baz tagged entities with names.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz',
      'isset': 'name'
    }
  );

// Get baz tagged entities without names.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz',
      '!isset': 'name'
    }
  );

// Get baz tagged entities without names or bar tagged entities with names.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '|',
      '1': {'type': '&',
        'tag': 'baz',
        '!isset': 'name'
      },
      '2': {'type': '&',
        'tag': 'bar',
        'isset': 'name'
      }
    }
  );
// or
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    ['|',
      ['&',
        {'tag': 'baz',
        '!isset': 'name'}
      ],
      ['&',
        {'tag': 'bar',
        'isset': 'name'}
      ]
    ]
  );

// Get baz tagged entities with either first names or last names.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz'
    },
    {'type': '|',
      'isset': ['first_name', 'last_name']
    }
  );

// Get baz tagged entities created since yesterday.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz',
      'gt': ['cdate', Math.round((new Date()).getTime() / 1000) - 86400]
    }
  );

// Get baz tagged entities with names, who either make not greater than
// 8 dollars pay or are under 22.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz',
      'isset': 'name'
    },
    {'type': '!|', // at least one must be false
      'gte': ['age', 22],
      'gt': ['pay', 8]
    }
  );

// Get baz tagged entities named Clark, James, Chris, Christopher,
// Jake, or Jacob.
let entitiesPromise = Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz'
    },
    {'type': '|',
      'strict': [
        ['name', 'Clark'],
        ['name', 'James']
      ],
      'pmatch': [
        ['name', 'Chris(topher)?'],
        ['name', 'Ja(ke|cob)']
      ]
    }
  );
Getting Entities by Querying, in PHP
use Nymph\Nymph as Nymph;

// Get the first FoobarBaz entity.
$entity = Nymph::getEntity(['class' => FoobarBaz]);

// Get the latest FoobarBaz entity.
$entity = Nymph::getEntity(['class' => FoobarBaz, 'reverse' => true]);

// Get all baz tagged entities, using the FoobarBaz class.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz'
    ]
  );

// Get the five newest bar and baz tagged entities.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz, 'reverse' => true, 'limit' => 5],
    ['&',
      'tag' => ['bar', 'baz']
    ]
  );

// Get baz tagged entities with names.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz',
      'isset' => 'name'
    ]
  );

// Get baz tagged entities without names.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz',
      '!isset' => 'name'
    ]
  );

// Get baz tagged entities without names or bar tagged entities with names.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['|',
      ['&',
        'tag' => 'baz',
        '!isset' => 'name'
      ],
      ['&',
        'tag' => 'bar',
        'isset' => 'name'
      ]
    ]
  );

// Get baz tagged entities with either first names or last names.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz'
    ],
    ['|',
      'isset' => ['first_name', 'last_name']
    ]
  );

// Get baz tagged entities created since yesterday.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz',
      'gt' => ['cdate', strtotime('-1 day')]
    ]
  );

// Get baz tagged entities with names, who either make not greater
// than 8 dollars pay or are under 22.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz',
      'isset' => 'name'
    ],
    ['!|', // at least one must be false
      'gte' => ['age', 22],
      'gt' => ['pay', 8]
    ]
  );

// Get baz tagged entities named Clark, James, Chris, Christopher,
// Jake, or Jacob.
$entities = Nymph::getEntities(
    ['class' => FoobarBaz],
    ['&',
      'tag' => 'baz'
    ],
    ['|',
      'strict' => [
        ['name', 'Clark'],
        ['name', 'James']
      ],
      'pmatch' => [
        ['name', 'Chris(topher)?'],
        ['name', 'Ja(ke|cob)']
      ]
    ]
  );

Clone this wiki locally