Entity Querying

Hunter Perrin edited this page May 7, 2018 · 28 revisions

The real power behind Nymph is the entity querying system.

Factory Method

The Entity class's factory method can take 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 new entity if the queried entity is not found. You can determine if it was found by checking that its GUID is set.

Getting Entities Using the Factory Method in PHP
// FoobarBaz expects a GUID.
$baz = FoobarBaz::factory((int) $_REQUEST['id']);
if (!isset($baz->guid)) {
  die('Can\'t find the Foobar Baz!');
}

// Tilmeld's User class expects a GUID or a username.
$cronUser = \Tilmeld\Entities\User::factory('cron');
if (!isset($cronUser->guid)) {
  die('Can\'t find the cron user!');
}

Nymph's Query Language

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

The first argument is an options parameter, as an array in PHP or an object in JS.

Entity Querying Options
Option Type Default Description
class string '\Nymph\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'.
return string 'entity' What to return. 'entity' or 'guid'.
source string null Will be 'client' if the query came from a REST call, 'pubsub' from the PubSub server.
skip_ac bool null If true, Tilmeld will not filter returned entities according to access controls. (If Tilmeld is installed.) (This is Always set to false by the REST endpoint.)

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 "EntityVersions_date".

Every argument following the options parameter is a selector, as an array in PHP or and object or array in JS. They contain clauses and/or nested selectors (called selector clauses). An entity must match each selector to be returned. The first member of the selector (or the 'type' entry in JS) is the type of selector.

Entity Selector Types
Type Name Description
& And All clauses in the selector must match.
| Or At least one clause in the selector must match.
!& Not And All clauses in the selector must not match.
!| Not Or At least one clause in the selector must not match.

The subsequent members of the array are either selector clauses or regular clauses of the selector. Regular clauses use the form 'name' => $value, or 'name' => [$value1, $value2, ...]. They can be negated by prepending a bang (!) to the name, such as '!tag' => 'user'. A clause that has multiple values is considered as multiple clauses in terms of matching for "or" selectors.

Entity Selector Clauses
guid - A GUID.
The entity's GUID is equal.
Selector: ['&', 'guid' => 12]
Works on: $entity->guid = 12;
tag - A tag.
The entity has the tag.
Selector: ['&', 'tag' => 'foobar']
Works on: $entity->addTag('foobar');
isset - A name.
The named property exists and is not null.
Selector: ['&', 'isset' => 'foo']
Works on: $entity->foo = 0;
equal - An array with a name, then value.
The named property is defined and equal.
Selector: ['&', 'data' => ['foo', false]]
Works on: $entity->foo = 0;
data (deprecated) - An alias for equal.
This has been replaced with "equal".
strict - An array with a name, then value.
The named property is defined and identical.
Selector: ['&', 'strict' => ['foo', 0]]
Works on: $entity->foo = 0;
array - An array with a name, then value.
The named property is an array containing the value. Uses `in_array()`.
Selector: ['&', 'array' => ['foo', 'bar']]
Works on: $entity->foo = ['bar', 'baz'];
match - An array with a name, then regular expression.
The named property matches. Uses `preg_match`. More powerful than "pmatch" but much slower. Must be surrounded by "/" delimiters.
Selector: ['&', 'match' => ['foo', '/(ba(r\|z))+/']]
Works on: $entity->foo = 'foobarbaz';
pmatch - An array with a name, then regular expression.
The named property matches. Uses POSIX RegExp. Case sensitive. Faster than "match". Must *not* be surrounded by any delimiters.
Selector: ['&', 'pmatch' => ['foo', 'bar.*z']]
Works on: $entity->foo = 'foobarbaz';
ipmatch - An array with a name, then regular expression.
The named property matches. Uses POSIX RegExp. Case insensitive. Faster than "match". Must *not* be surrounded by any delimiters.
Selector: ['&', 'ipmatch' => ['foo', 'BaR.*Z']]
Works on: $entity->foo = 'foobarbaz';
like - An array with a name, then pattern.
The named property matches. Uses % for variable length wildcard and _ for single character wildcard. Case sensitive.
Selector: ['&', 'like' => ['foo', 'f%bar_az']]
Works on: $entity->foo = 'foobarbaz';
ilike - An array with a name, then pattern.
The named property matches. Uses % for variable length wildcard and _ for single character wildcard. Case insensitive.
Selector: ['&', 'ilike' => ['foo', 'F%bAr_aZ']]
Works on: $entity->foo = 'foobarbaz';
gt - An array with a name, then value.
The named property is greater than the value.
Selector: ['&', 'gt' => ['foo', 5]]
Works on: $entity->foo = 6;
gte - An array with a name, then value.
The named property is greater than or equal to the value.
Selector: ['&', 'gte' => ['foo', 6]]
Works on: $entity->foo = 6;
lt - An array with a name, then value.
The named property is less than the value.
Selector: ['&', 'lt' => ['foo', 7]]
Works on: $entity->foo = 6;
lte - An array with a name, then value.
The named property is less than or equal to the value.
Selector: ['&', 'lte' => ['foo', 6]]
Works on: $entity->foo = 6;
ref - An array with a name, then either an entity, or a GUID.
The named property is the entity or an array containing the entity.
Selector: ['&', 'ref' => ['foo', 12]]
Works on: $entity->foo = Entity::factory(12);

Any clause that accepts an array of name and value can also accept a third element. If value is null and the third element is a string, the third element will be used with PHP's strtotime function to set value to a relative timestamp. For example, the following selector will look for all entities that were created in the last day:

['&',
  'gte' => ['cdate', null, '-1 day']
]

Query Results

In PHP, your query will just return entities. In the JavaScript client, your query needs to be sent to the server, so the function will return a promise. You can use the then method or the await keyword 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 response.
    console.log(errObj.textStatus); // The status text of the response.
    console.log(errObj.exception); // The exception class, if any.
    console.log(errObj.code); // The exception code, if any.
    console.log(errObj.message); // The exception message, if any.
});

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-client';
let entity, entities;

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

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

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

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

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

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

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

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

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

// Get baz tagged entities created since yesterday.
entities = await Nymph.getEntities(
    {'class': FoobarBaz.class},
    {'type': '&',
      'tag': 'baz',
      'gt': ['cdate', null, '-1 day']
    }
  );

// Get baz tagged entities with names, who either make not greater than
// 8 dollars pay or are under 22.
entities = await 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.
entities = await 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 last created bar and baz tagged entities.
$entities = Nymph::getEntities(
    ['class' => 'FoobarBaz', 'reverse' => true, 'limit' => 5],
    ['&',
      'tag' => ['bar', 'baz']
    ]
  );

// Get the five last modified bar and baz tagged entities.
$entities = Nymph::getEntities(
    ['class' => 'FoobarBaz', 'reverse' => true, 'limit' => 5, 'sort' => 'mdate'],
    ['&',
      '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', null, '-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)']
      ]
    ]
  );
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.