Skip to content

Commit e38189d

Browse files
committed
Added support for user properties. Automatically split Batch with more than 10k requests to multiple Batch requests. Added cascadeCreate parameter to Set item/user values.
1 parent 508166e commit e38189d

45 files changed

Lines changed: 708 additions & 122 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ or
1717
```
1818
{
1919
"require": {
20-
"recombee/php-api-client": ">=1.2.5"
20+
"recombee/php-api-client": ">=1.3"
2121
}
2222
}
2323
```
@@ -30,52 +30,36 @@ use Recombee\RecommApi\Client;
3030
use Recombee\RecommApi\Requests as Reqs;
3131
use Recombee\RecommApi\Exceptions as Ex;
3232

33-
const NUM = 100;
34-
const PROBABILITY_PURCHASED = 0.01;
35-
36-
// Prepare some users and items
37-
$my_user_ids = array();
38-
$my_item_ids = array();
39-
for($i=0; $i < NUM; $i++) {
40-
array_push($my_user_ids, "user-{$i}");
41-
array_push($my_item_ids, "item-{$i}");
42-
}
43-
44-
// Generate some random purchases of items by users
45-
$my_purchases = array();
46-
foreach ($my_user_ids as $user_id) {
47-
foreach ($my_item_ids as $item_id) {
48-
if(mt_rand() / mt_getrandmax() < PROBABILITY_PURCHASED)
49-
array_push($my_purchases, ['user'=>$user_id, 'item'=>$item_id]);
50-
}
51-
}
52-
53-
// Use Recombee recommender
5433
$client = new Client('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L');
5534

35+
const NUM = 100;
36+
const PROBABILITY_PURCHASED = 0.1;
37+
5638
try
5739
{
58-
$client->send(new Reqs\ResetDatabase()); //Clear everything from the database
59-
60-
//Create requests and send the data to Recombee, use Batch for faster processing
61-
echo "Send users\n";
62-
$user_requests = array_map(function($userId) {return new Reqs\AddUser($userId);}, $my_user_ids);
63-
$client->send(new Reqs\Batch($user_requests));
64-
65-
echo "Send items\n";
66-
$item_requests = array_map(function($itemId) {return new Reqs\AddItem($itemId);}, $my_item_ids);
67-
$client->send(new Reqs\Batch($item_requests));
68-
40+
// Generate some random purchases of items by users
41+
$purchase_requests = array();
42+
for($i=0; $i < NUM; $i++) {
43+
for($j=0; $j < NUM; $j++) {
44+
if(mt_rand() / mt_getrandmax() < PROBABILITY_PURCHASED) {
45+
46+
$request = new Reqs\AddPurchase("user-{$i}", "item-{$j}",
47+
['cascadeCreate' => true] // Use cascadeCreate to create the
48+
// yet non-existing users and items
49+
);
50+
array_push($purchase_requests, $request);
51+
}
52+
}
53+
}
6954
echo "Send purchases\n";
70-
$purchase_requests = array_map(function($tuple)
71-
{return new Reqs\AddPurchase($tuple['user'], $tuple['item']);}, $my_purchases);
72-
$client->send(new Reqs\Batch($purchase_requests));
55+
$res = $client->send(new Reqs\Batch($purchase_requests)); //Use Batch for faster processing of larger data
7356

7457
// Get 5 recommendations for user 'user-25'
75-
$recommended = $client->send(new Reqs\UserBasedRecommendation('user-25', 5, ['rotationRate' => 0]));
58+
$recommended = $client->send(new Reqs\UserBasedRecommendation('user-25', 5));
59+
7660
echo 'Recommended items: ' . implode(',',$recommended) . "\n";
7761
}
78-
catch(Ex\ApiException $e)
62+
catch(Ex\ApiTimeoutException $e)
7963
{
8064
//use fallback
8165
}
@@ -105,6 +89,7 @@ Computers have three properties
10589
$client->send(new Reqs\AddItemProperty('price', 'double'));
10690
$client->send(new Reqs\AddItemProperty('num-cores', 'int'));
10791
$client->send(new Reqs\AddItemProperty('description', 'string'));
92+
$client->send(new Reqs\AddItemProperty('in_stock_from', 'timestamp'));
10893

10994
# Prepare requests for setting a catalog of computers
11095
$requests = array();
@@ -117,15 +102,18 @@ for($i=0; $i<NUM; $i++)
117102
'price' => rand(15000, 25000),
118103
'num-cores' => rand(1, 8),
119104
'description' => 'Great computer',
120-
'!cascadeCreate' => true // Use !cascadeCreate for creating item
121-
// with given itemId, if it doesn't exist
122-
]
105+
'in_stock_from' => new DateTime('NOW')
106+
],
107+
//optional parameters:
108+
['cascadeCreate' => true] // Use cascadeCreate for creating item
109+
// with given itemId, if it doesn't exist]
123110
);
124111
array_push($requests, $r);
125112
}
126113

127114
// Send catalog to the recommender system
128-
$client->send(new Reqs\Batch($requests));
115+
$result = $client->send(new Reqs\Batch($requests));
116+
var_dump($result);
129117

130118
// Generate some random purchases of items by users
131119
$requests = array();
@@ -137,23 +125,21 @@ for($i=0; $i<NUM; $i++)
137125
$r = new Reqs\AddPurchase("user-{$i}", "computer-{$j}", ['cascadeCreate' => true]);
138126
array_push($requests, $r);
139127
}
140-
128+
141129
// Send purchases to the recommender system
142130
$client->send(new Reqs\Batch($requests));
143131

144-
// Get 5 recommendations for user-42, who is currently viewing computer-6
132+
// Get 5 items related to item computer-6. Personalize them for user-42, who is currently viewing that item.
145133
$recommended = $client->send(new Reqs\ItemBasedRecommendation('computer-6', 5, ['targetUserId' => 'user-42']));
146134
echo 'Recommended items: ' . implode(',',$recommended) . "\n";
147135

148-
// Get 5 recommendations for user-42, but recommend only computers that
149-
// have at least 3 cores
136+
// Recommend only computers that have at least 3 cores
150137
$recommended = $client->send(
151138
new Reqs\ItemBasedRecommendation('computer-6', 5, ['targetUserId' => 'user-42', 'filter' => "'num-cores'>=3"])
152139
);
153140
echo 'Recommended items with at least 3 processor cores: ' . implode(',',$recommended) . "\n";
154141

155-
// Get 5 recommendations for user-42, but recommend only items that
156-
// are more expensive then currently viewed item (up-sell)
142+
// Recommend only items that are more expensive then currently viewed item computer-6 (up-sell)
157143
$recommended = $client->send(
158144
new Reqs\ItemBasedRecommendation('computer-6', 5,
159145
['targetUserId' => 'user-42', 'filter' => "'price' > context_item[\"price\"]"])

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
],
1212
"require": {
1313
"php": ">=5.4.0",
14-
"rmccue/requests": ">=1.0"
14+
"rmccue/requests": ">=1.7"
1515
},
1616
"require-dev": {
1717
"phpunit/phpunit": ">=4",

phpdoc.dist.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
2-
<phpdoc>
2+
<phpdocumentor>
33
<title>Recombee API client</title>
44
<transformations>
55
<template name="responsive-twig"/>
@@ -13,4 +13,4 @@
1313
<files>
1414
<directory>src</directory>
1515
</files>
16-
</phpdoc>
16+
</phpdocumentor>

src/RecommApi/Client.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class Client{
2727
*/
2828
const BASE_URI = 'rapi.recombee.com';
2929

30+
/**
31+
* @ignore
32+
*/
33+
const BATCH_MAX_SIZE = 10000; //Maximal number of requests within one batch request
34+
3035
/**
3136
* Create the client
3237
* @param string $account Name of your account at Recombee
@@ -49,6 +54,10 @@ public function __construct($account, $token, $protocol = 'http') {
4954
* @throws Exceptions\ApiTimeoutException ApiTimeoutException if the request takes too long
5055
*/
5156
public function send(Requests\Request $request) {
57+
58+
if($request instanceof Requests\Batch && count($request->requests) > Client::BATCH_MAX_SIZE)
59+
return $this->sendMultipartBatch($request);
60+
5261
$this->request = $request;
5362
$path = Util::sliceDbName($request->getPath());
5463
$request_url = $path . $this->paramsToUrl($request->getQueryParameters());
@@ -81,35 +90,35 @@ public function send(Requests\Request $request) {
8190
catch(\Requests_Exception $e)
8291
{
8392
if(strpos($e->getMessage(), 'cURL error 28') !== false) throw new ApiTimeoutException($request);
93+
if(strpos($e->getMessage(), 'fsocket timed out') !== false) throw new ApiTimeoutException($request);
8494
throw $e;
8595
}
8696

8797
return $result;
8898
}
8999

90100
/* ----------------------- Send requests ----------------------- */
91-
//TODO remove ceils when Requests 1.7 is released as it support timeout in milliseconds
92101
protected function put($uri, $timeout) {
93-
$response = \Requests::put($uri, array(), ['timeout' => ceil($timeout)]);
102+
$response = \Requests::put($uri, array(), ['timeout' => $timeout]);
94103
$this->checkErrors($response);
95104
return $response->body;
96105
}
97106

98107
protected function get($uri, $timeout) {
99-
$response = \Requests::get($uri, array(), ['timeout' => ceil($timeout)]);
108+
$response = \Requests::get($uri, array(), ['timeout' => $timeout]);
100109
$this->checkErrors($response);
101110
return json_decode($response->body, true);
102111
}
103112

104113
protected function delete($uri, $timeout) {
105-
$response = \Requests::delete($uri, array(), ['timeout' => ceil($timeout)]);
114+
$response = \Requests::delete($uri, array(), ['timeout' => $timeout]);
106115
$this->checkErrors($response);
107116
return $response->body;
108117
}
109118

110119
protected function post($uri, $timeout, $body) {
111120
$headers = array('Content-Type' => 'application/json');
112-
$response = \Requests::post($uri, $headers, $body, ['timeout' => ceil($timeout)]);
121+
$response = \Requests::post($uri, $headers, $body, ['timeout' => $timeout]);
113122
$this->checkErrors($response);
114123

115124
$json = json_decode($response->body, true);
@@ -125,6 +134,15 @@ protected function checkErrors($response) {
125134
throw new ResponseException($this->request, $status_code, $response->body);
126135
}
127136

137+
protected function sendMultipartBatch($request) {
138+
$chunks = array_chunk($request->requests, Client::BATCH_MAX_SIZE);
139+
$responses = array();
140+
foreach ($chunks as $rqs) {
141+
array_push($responses, $this->send(new Requests\Batch($rqs)));
142+
}
143+
return array_reduce($responses, 'array_merge', array());
144+
}
145+
128146
/* ----------------------- HMAC ----------------------- */
129147

130148
protected function signUrl($uri) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
/*
3+
This file is auto-generated, do not edit
4+
*/
5+
6+
/**
7+
* AddUserProperty request
8+
*/
9+
namespace Recombee\RecommApi\Requests;
10+
use Recombee\RecommApi\Exceptions\UnknownOptionalParameterException;
11+
12+
/**
13+
* Adding an user property is somehow equivalent to adding a column to the table of users. The users may be characterized by various properties of different types.
14+
*/
15+
class AddUserProperty extends Request {
16+
17+
/**
18+
* @var string $property_name Name of the user property to be created. Currently, the following names are reserved:`id`, `userid`, case insensitively. Also, the length of the property name must not exceed 63 characters.
19+
*/
20+
protected $property_name;
21+
/**
22+
* @var string $type Value type of the user property to be created. One of: `int`, `double`, `string`, `boolean`, `timestamp`, `set`
23+
*/
24+
protected $type;
25+
26+
/**
27+
* Construct the request
28+
* @param string $property_name Name of the user property to be created. Currently, the following names are reserved:`id`, `userid`, case insensitively. Also, the length of the property name must not exceed 63 characters.
29+
* @param string $type Value type of the user property to be created. One of: `int`, `double`, `string`, `boolean`, `timestamp`, `set`
30+
*/
31+
public function __construct($property_name, $type) {
32+
$this->property_name = $property_name;
33+
$this->type = $type;
34+
$this->timeout = 1000;
35+
$this->ensure_https = false;
36+
}
37+
38+
/**
39+
* Get used HTTP method
40+
* @return static Used HTTP method
41+
*/
42+
public function getMethod() {
43+
return Request::HTTP_PUT;
44+
}
45+
46+
/**
47+
* Get URI to the endpoint
48+
* @return string URI to the endpoint
49+
*/
50+
public function getPath() {
51+
return "/{databaseId}/users/properties/{$this->property_name}";
52+
}
53+
54+
/**
55+
* Get query parameters
56+
* @return array Values of query parameters (name of parameter => value of the parameter)
57+
*/
58+
public function getQueryParameters() {
59+
$params = array();
60+
$params['type'] = $this->type;
61+
return $params;
62+
}
63+
64+
/**
65+
* Get body parameters
66+
* @return array Values of body parameters (name of parameter => value of the parameter)
67+
*/
68+
public function getBodyParameters() {
69+
$p = array();
70+
return $p;
71+
}
72+
73+
}
74+
?>

src/RecommApi/Requests/Batch.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* - executing the requests in a batch is equivalent as if they were executed one-by-one individually; there are, however, many optimizations to make batch execution as fast as possible,
1717
* - the status code of the batch request itself is 200 even if the individual requests result in error – you have to inspect the code values in the resulting array,
1818
* - if the status code of the whole batch is not 200, then there is an error in the batch request itself; in such a case, the error message returned should help you to resolve the problem,
19-
* - currently, batch size is limited to **10,000** requests; if you wish to execute even larger number of requests, please split the batch into multiple parts.
2019
*/
2120
class Batch extends Request {
2221

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/*
3+
This file is auto-generated, do not edit
4+
*/
5+
6+
/**
7+
* DeleteUserProperty request
8+
*/
9+
namespace Recombee\RecommApi\Requests;
10+
use Recombee\RecommApi\Exceptions\UnknownOptionalParameterException;
11+
12+
/**
13+
* Deleting an user property is roughly equivalent to removing a column from the table of users.
14+
*/
15+
class DeleteUserProperty extends Request {
16+
17+
/**
18+
* @var string $property_name Name of the property to be deleted.
19+
*/
20+
protected $property_name;
21+
22+
/**
23+
* Construct the request
24+
* @param string $property_name Name of the property to be deleted.
25+
*/
26+
public function __construct($property_name) {
27+
$this->property_name = $property_name;
28+
$this->timeout = 1000;
29+
$this->ensure_https = false;
30+
}
31+
32+
/**
33+
* Get used HTTP method
34+
* @return static Used HTTP method
35+
*/
36+
public function getMethod() {
37+
return Request::HTTP_DELETE;
38+
}
39+
40+
/**
41+
* Get URI to the endpoint
42+
* @return string URI to the endpoint
43+
*/
44+
public function getPath() {
45+
return "/{databaseId}/users/properties/{$this->property_name}";
46+
}
47+
48+
/**
49+
* Get query parameters
50+
* @return array Values of query parameters (name of parameter => value of the parameter)
51+
*/
52+
public function getQueryParameters() {
53+
$params = array();
54+
return $params;
55+
}
56+
57+
/**
58+
* Get body parameters
59+
* @return array Values of body parameters (name of parameter => value of the parameter)
60+
*/
61+
public function getBodyParameters() {
62+
$p = array();
63+
return $p;
64+
}
65+
66+
}
67+
?>

0 commit comments

Comments
 (0)