etrepat / compleet
Redis-backed service for fast autocompleting
Installs: 1 287
Dependents: 1
Suggesters: 0
Security: 0
Stars: 3
Watchers: 3
Forks: 2
Open Issues: 0
Requires
- php: >=5.4.0
- predis/predis: >=0.8.0
Requires (Dev)
- phpunit/phpunit: ~4.0
This package is auto-updated.
Last update: 2024-12-23 04:13:02 UTC
README
Compleet is a PHP port of the awesome Soulmate ruby gem, which was written by Eric Waller. All credit should go to the original library author.
This library will help you solve the common problem of developing a fast autocomplete feature. It uses Redis's sorted sets to build an index of partially completed words and the corresponding top matching items, and provides a simple interface to query them. Compleet finishes your sentences.
Compleet requires PHP >= 5.4 (or HHVM >= 3.2) and its only dependencies are Composer itself and the fantastic Predis PHP client library for Redis.
Getting Started
Compleet is distributed as a composer package, so kick things off by adding it as a requirement to your composer.json
file:
{ "require": { "etrepat/compleet": "~1.0" } }
Then update your packages by running composer update
or install with composer install
.
Usage
Compleet is designed to be simple and fast, and its main features are:
- Provide suggestions for multiple types of items in a single query.
- Sort results by user-specified score, lexycographically otherwise.
- Store arbitrary metadata for each item. You may store url's, thumbail paths, etc.
An item is a simple JSON object that looks like:
{ "id": 3, "term": "Citi Field", "score": 81, "data": { "url": "/citi-field-tickets/", "subtitle": "Flushing, NY" } }
Where id
is a unique identifier (within the specific type), term
is the phrase you wish to provide completions for, score
is a user-specified ranking metric (redis will order things lexicographically for items with the same score), and data
is an optional container for metadata you'd like to return when this item is matched.
Managing items
Before being able to perform any autocomplete search we must first load some data into our redis database. To feed data into a Redis server instance for later querying, Compleet provides the Loader
class:
require __DIR__ . '/vendor/autoload.php'; $loader = new Compleet\Loader('venues');
The constructor parameter is the type of the items we are actually going to add/remove or load. We'll later use this same type for search. This is used so we can add several kinds of completeley differentiated data and index it into Redis separately.
By default a Compleet\Loader
object will instatiate a connection to a local redis instance as soon as it is needed. If you wish to change this behaviour, you may either provide a connection into the constructor:
require __DIR__ . '/vendor/autoload.php'; $redis = new Predis\Client('tcp://127.0.0.1/6379?database=0'); $loader = new Compleet\Loader('venues', $redis);
or use the setConnection
method:
require __DIR__ . '/vendor/autoload.php'; $redis = new Predis\Client('tcp://127.0.0.1/6379?database=0'); $loader = new Compleet\Loader('venues'); $loader->setConnection($redis);
Loading items
Now that we have a loader object in place we probably want to use it to load a bunch of items into our redis database. Imagine we have the following PHP item array (which follows the previous JSON structure):
$items = array( array( 'id' => 1, 'term' => 'Dodger Stadium', 'score' => 85, 'data' => array( 'url' => '/dodger-stadium/tickets', 'subtitle' => 'Los Angeles, CA' ) ), array( 'id' => 28, 'term' => 'Angel Stadium', 'score' => 85, 'data' => array( 'url' => '/angel-stadium- tickets/', 'subtitle' => 'Anaheim, CA' ) ) ... etc ... );
To load these items into Redis for later querying and previously clearing all existing data we have the load
method:
$loader->load($items);
Adding items
We may add a single item in a similar fashion with the add
method:
$item = array( 'id' => 30, 'term' => 'Chase Field', 'score' => 85, 'data' => array( 'url' => '/chase-field-ticket/', 'subtitle' => 'Phoenix, AZ' ) ); $loader->add($item);
The add
method appends items individually without previously clearing the index.
For both the load
and add
methods, each item must supply the id
and term
array keys. All other attributes are optional.
Removing items
Similarly if we need to remove an individual item, the remove
method will do just that:
$item = array( 'id' => 30, 'term' => 'Chase Field', 'score' => 85, 'data' => array( 'url' => '/chase-field-ticket/', 'subtitle' => 'Phoenix, AZ' ) ); $loader->remove($item);
Only the id
key will be used and is actually mandatory.
Clearing the index
To wipe out all of the previously indexed data we may use the clear
method:
$loader->clear();
Querying
Analogous to the Compleet\Loader
class, Compleet provides the Matcher
class which will allow us to query against previously indexed data. It works pretty similarly and accepts the same constructor arguments:
require __DIR__ . '/vendor/autoload.php'; $redis = new Predis\Client('tcp://127.0.0.1/6379?database=0'); $matcher = new Compleet\Matcher('venues', $redis); // or: // $matcher = new Compleet\Matcher('venues'); // $matcher->setConnection($redis);
Again, the first constructor parameter is the type of the items we are actually going query against.
Then, the matches
method will allow us to query the supplied term against the indexed data:
$results = $matcher->matches('stad');
This will perform a search against the index of partially completed words for the venues
type and it will return an array of matching items for the term stad
. The resulting array will be sorted by the supplied score or alphabetically if none was given.
The matches
method also supports passing an array of options as a second argument:
$queryOptions = array( // resultset size limit (defaults to 5) 'limit' => 5, // whether to cache the results or not (defaults to true) 'cache' => true // cache expiry time in seconds (defaults to 600) 'expiry' => 600 ); $results = $matcher->matches('stad', $queryOptions);
Setting the limit
option to 0
will return all results which match the provided term.
Matching a single term against multiple types of items should be easy enough:
$types = array('products', 'categories', 'brands'); $results = array(); foreach($types as $type) { $matcher = new Compleet\Matcher($type); $results[$type] = $matcher->matches('some term'); }
Working with the CLI
Compleet also provides a CLI utility, the compleet
script, for easy data indexing from JSON documents/files. It may also be used to conduct queries for testing purposes. Like many composer packages which supply a binary, it will probably be placed under the vendor/bin
folder of your project.
To load data into redis from the CLI, you can pipe items from a JSON file into the compleet load TYPE
command.
Here's a sample venues.json
(one JSON item per line):
{"id":1,"term":"Dodger Stadium","score":85,"data":{"url":"\/dodger-stadium-tickets\/","subtitle":"Los Angeles, CA"}} {"id":28,"term":"Angel Stadium","score":85,"data":{"url":"\/angel-stadium-tickets\/","subtitle":"Anaheim, CA"}} {"id":30,"term":"Chase Field ","score":85,"data":{"url":"\/chase-field-tickets\/","subtitle":"Phoenix, AZ"}} {"id":29,"term":"Sun Life Stadium","score":84,"data":{"url":"\/sun-life-stadium-tickets\/","subtitle":"Miami, FL"}} {"id":2,"term":"Turner Field","score":83,"data":{"url":"\/turner-field-tickets\/","subtitle":"Atlanta, GA"}}
And here's the load command (The compleet
utility will assume redis is running locally on the default port, or you can specify a redis connection string with the --redis
argument):
$ vendor/bin/compleet load venues < venues.json
Running compleet -h
will list all the operations which are supported by the CLI and its arguments. All operations that may be performed programatically can be run from the compleet
utility.
Using Compleet with your framework
Compleet is a standalone composer package and should work out of the box regardless of the PHP framework you use. In fact, none is needed.
Using Compleet with Laravel
A Compleet integration for Laravel >= 4.2 is provided in the Laravel Compleet package. It will help reduce the amount of boilerplate code needed to make the autocomplete functionality in your project useful and adds several nice integrations such as: global config, artisan commands, redis connection reuse, controller code, routes, etc.
Take a look at the Laravel Compleet project page for more information on how to use Compleet with your Laravel applications.
Contributing
Thinking of contributing? Maybe you've found some nasty bug? That's great news!
- Fork & clone the project:
git clone git@github.com:your-username/compleet.git
. - Run the tests and make sure that they pass with your setup:
phpunit
. - Create your bugfix/feature branch and code away your changes. Add tests for your changes.
- Make sure all the tests still pass:
phpunit
. - Push to your fork and submit new a pull request.
License
Compleet is licensed under the terms of the MIT License (See LICENSE file for details).
Coded by Estanislau Trepat (etrepat). I'm also @etrepat on twitter.