Skip to content

Commit 9be2fa2

Browse files
authored
Merge pull request #739 from akeneo/API-2008-api-documentation-integration
API-2008: Add new 'How to get PIM category tree' guided tutorial
2 parents 27d7fef + bb51369 commit 9be2fa2

File tree

4 files changed

+353
-1
lines changed

4 files changed

+353
-1
lines changed

content/tutorials/guides/how-to-collect-product-variations.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,17 @@ Parse and store the product model like in [**2.1. Parse and store the product mo
407407

408408
##### 2.2. Collect its product variants
409409

410-
Collect product variants the same way than in [**2.2. Collect its product variants**](/tutorials/how-to-collect-product-variations.html#22-collect-its-product-variants)
410+
Collect product variants the same way than in [**2.2. Collect its product variants**](/tutorials/how-to-collect-product-variations.html#22-collect-its-product-variants)
411+
412+
<div class="block-next-steps block-next-steps-alt">
413+
<img src="/img/illustrations/illus--Attribute.svg" width="140px" class="hidden-xs">
414+
<div class="block-next-steps-column">
415+
<div class="block-next-steps-title">Next Step</div>
416+
<div class="block-next-steps-text">Well done! Keep digging into the “App workflow” and follow the next tutorial!</div>
417+
<div>
418+
<ul>
419+
<li><a href="/tutorials/how-to-get-pim-category-tree.html">How to get PIM category tree</a></li>
420+
</ul>
421+
</div>
422+
</div>
423+
</div>
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
<a href="/tutorials/homepage.html" class="back-button">
2+
<button>
3+
<img src="/img/icons/icon--arrow-back.svg" style="margin-right: 10px;">
4+
All guided tutorials
5+
</button>
6+
</a>
7+
8+
# How to get PIM category tree
9+
10+
<table class="tag-container">
11+
<tr>
12+
<td>Use case:</td>
13+
<td>
14+
<div class="tag-not-selectable">
15+
<div class="tag-color tag-color-light-blue"></div>
16+
<div class="tag-label">App Workflow</div>
17+
</div>
18+
</td>
19+
</tr>
20+
<tr>
21+
<td>PIM Features:</td>
22+
<td>
23+
<div class="tag-not-selectable">
24+
<div class="tag-color tag-color-blue"></div>
25+
<div class="tag-label">Categories</div>
26+
</div>
27+
</td>
28+
</tr>
29+
</table>
30+
31+
<div class="endpoint-container">
32+
<div class="endpoint-text">REST API endpoint(s):</div>
33+
<a href="/api-reference.html#Category" class="endpoint-link" target="_blank" rel="noopener noreferrer">categories</a>
34+
</div>
35+
36+
<div class="block-requirements">
37+
<div class="block-requirements-headline">
38+
If you're starting building your App, make sure you previously followed:
39+
</div>
40+
<div class="block-requirements-row">
41+
<img src="/img/illustrations/illus--Attributegroup.svg" width="110px" class="hidden-xs">
42+
<div class="block-requirements-steps">
43+
<div>Step 1. <a href="how-to-get-your-app-token.html" target="_blank" rel="noopener noreferrer">Get your App token tutorial</a></div>
44+
<div>Step 2. <a href="how-to-retrieve-pim-structure.html" target="_blank" rel="noopener noreferrer">How to retrieve PIM structure</a></div>
45+
<div>Step 3. <a href="how-to-get-families-and-attributes.html" target="_blank" rel="noopener noreferrer">How to get families, family variants, and attributes</a></div>
46+
<div>Step 4. <a href="how-to-get-pim-product-information.html" target="_blank" rel="noopener noreferrer">How to get PIM product information</a></div>
47+
<div>Step 5. <a href="how-to-collect-product-variations.html" target="_blank" rel="noopener noreferrer">How to collect product variations</a></div>
48+
</div>
49+
</div>
50+
</div>
51+
52+
53+
## Context
54+
55+
A category tree is typically a graph where nodes are categories with or without sub-categories.
56+
The first node - the root of the graph - is the root category of the category tree.
57+
58+
![relationship schema](/img/getting-started/synchronize-pim-products/step-3-objects-relationship-schema.svg)
59+
60+
:::tips
61+
Get the big picture <a href="/getting-started/synchronize-pim-products-6x/step-0.html" target="_blank" rel="noopener noreferrer">here</a>.
62+
:::
63+
Fortunately, getting the whole category is as simple as querying the correct API endpoint.
64+
65+
## Fetch the category tree
66+
67+
### 0. Initialization
68+
69+
```php [activate:PHP]
70+
71+
function buildApiClient(): GuzzleHttp\Client
72+
{
73+
$pimUrl = 'https://url-of-your-pim.com';
74+
$appToken = 'your_app_token'; // Token provided during oAuth steps
75+
76+
// If you haven't done it yet,
77+
// please follow the Guzzle official documentation to install the client
78+
// https://docs.guzzlephp.org/en/stable/overview.html#installation
79+
80+
// Set your client for querying Akeneo API as follows
81+
$client = new \GuzzleHttp\Client([
82+
'base_uri' => $pimUrl,
83+
'headers' => ['Authorization' => 'Bearer ' . $appToken],
84+
]);
85+
}
86+
```
87+
88+
```javascript [activate:NodeJS]
89+
90+
// Install the node-fetch library by following the official documentation:
91+
// https://www.npmjs.com/package/node-fetch
92+
import fetch from 'node-fetch';
93+
94+
const pimUrl = 'https://url-of-your-pim.com';
95+
const accessToken = 'your_app_token'; // Token provided during oAuth steps
96+
97+
// Set your client for querying Akeneo API as follows
98+
async function get(url, accessToken) {
99+
return await fetch(url, {
100+
headers: {
101+
'Authorization': `Bearer ${accessToken}`
102+
}
103+
});
104+
}
105+
```
106+
107+
### 1. Get the PIM structure by fetching a channel from API
108+
109+
Workflow:
110+
111+
![synchronisation steps](/img/getting-started/synchronize-pim-products/step-3-steps-schema.svg)
112+
113+
Collect flat category tree from PIM API:
114+
115+
```php [activate:PHP]
116+
117+
function getCategories(): void
118+
{
119+
$client = buildApiClient();
120+
121+
// Get root category code from storage
122+
$rootCategoryCode = getRootCategoryCode();
123+
124+
$maxItems = 100;
125+
126+
$nextUrl = sprintf(
127+
'/api/rest/v1/categories'
128+
. '?with_position=true'
129+
. '&search={"parent":[{"operator":"=","value":"%s"}]}'
130+
. '&limit=%s',
131+
$rootCategoryCode,
132+
$maxItems
133+
);
134+
135+
$categoryPages = [];
136+
137+
do {
138+
$response = $client->get($nextUrl);
139+
$data = json_decode($response->getBody()->getContents(), true);
140+
$categoryPages[] = $data['_embedded']['items'];
141+
142+
$nextUrl = $data['_links']['next']['href'] ?? null;
143+
} while (
144+
$nextUrl
145+
);
146+
147+
// Adds root category to complete the list.
148+
$categoryPages[][] = getRootCategory($rootCategoryCode);
149+
150+
$categories = array_merge(...$categoryPages);
151+
152+
// Save categories into storage
153+
saveCategories($categories);
154+
}
155+
156+
function getRootCategory(string $code): array
157+
{
158+
$client = buildApiClient();
159+
160+
$response = $client->get('/api/rest/v1/categories/' . $code);
161+
$rootCategory = json_decode($response->getBody()->getContents(), true);
162+
163+
if ($rootCategory['parent'] !== null) {
164+
throw new \InvalidArgumentException(
165+
sprintf('Category %s is not root.', $code)
166+
);
167+
}
168+
169+
return $rootCategory;
170+
}
171+
```
172+
173+
```javascript [activate:NodeJS]
174+
175+
async function fetchCategories() {
176+
// Get root category code from storage
177+
const rootCategoryCode = await getRootCategoryCode();
178+
const maxItems = 100;
179+
180+
let nextUrl = `${pimUrl}/api/rest/v1/categories`
181+
+ '?with_position=true'
182+
+ `&search={"parent":[{"operator":"=","value":"${rootCategoryCode}"}]}`
183+
+ `&limit=${maxItems}`
184+
;
185+
186+
const categories = [];
187+
188+
do {
189+
const response = await get(nextUrl);
190+
const data = await response.json();
191+
const newCategories = data._embedded.items;
192+
categories.push(...newCategories);
193+
194+
nextUrl = data._links?.next?.href;
195+
} while (nextUrl)
196+
197+
// Adds root category to complete the list.
198+
const rootCategory = await getRootCategory(rootCategoryCode);
199+
categories.push(rootCategory);
200+
201+
// Save categories into storage
202+
saveCategories(categories);
203+
}
204+
205+
async function getRootCategory(rootCategoryCode) {
206+
const pimUrl = process.env.PIM_URL;
207+
208+
const response = await get(`${pimUrl}/api/rest/v1/categories/${rootCategoryCode}`);
209+
return await response.json();
210+
}
211+
```
212+
213+
The categories are retrieved in a flat array like this:
214+
215+
```php [activate:PHP]
216+
217+
var_export($categories);
218+
219+
// Output
220+
[
221+
[
222+
'code' => 'master',
223+
'parent' => null,
224+
'updated' => '2022-01-01T00:00:00+00:00',
225+
'labels' => [
226+
'en_US' => 'Master catalog',
227+
'fr_FR' => 'Catalogue principal',
228+
'de_DE' => 'Hauptkatalog',
229+
]
230+
],
231+
[
232+
'code' => 'tvs_projectors',
233+
'parent' => 'master',
234+
'updated' => '2022-11-22T14:40:43+00:00',
235+
'labels' => [
236+
'en_US' => 'TVs and projectors',
237+
'fr_FR' => 'Téléviseurs et projecteurs',
238+
'de_DE' => 'TVs und projectoren',
239+
]
240+
'position' => 1, // Brought by 'with_position=true' option
241+
],
242+
[
243+
'code' => 'cameras',
244+
'parent' => 'master',
245+
'updated' => '2022-11-22T14:40:43+00:00',
246+
'labels' => [
247+
'en_US' => 'Cameras',
248+
'fr_FR' => 'Cameras',
249+
'de_DE' => 'Cameras',
250+
]
251+
'position' => 2,
252+
],
253+
[
254+
'code' => 'digital_cameras',
255+
'parent' => 'cameras',
256+
'updated' => '2022-11-22T14:40:43+00:00',
257+
'labels' => [
258+
'en_US' => 'Digital cameras',
259+
'fr_FR' => 'Caméras digitales',
260+
'de_DE' => 'Digitale Kameras',
261+
]
262+
'position' => 1,
263+
],
264+
/* ... */
265+
]
266+
```
267+
268+
```javascript [activate:NodeJS]
269+
270+
console.log(categories);
271+
272+
// Output
273+
[
274+
{
275+
"code": "master",
276+
"parent": null,
277+
"updated": "2022-01-01T00:00:00+00:00",
278+
"labels": {
279+
"en_US": "Master catalog",
280+
"fr_FR": "Catalogue principal",
281+
"de_DE": "Hauptkatalog",
282+
},
283+
},
284+
{
285+
"code": "tvs_projectors",
286+
"parent": "master",
287+
"updated": "2022-11-22T14:40:43+00:00",
288+
"labels": {
289+
"en_US": "TVs and projectors",
290+
"fr_FR": "Téléviseurs et projecteurs",
291+
"de_DE": "TVs und projectoren",
292+
},
293+
"position": 1, // Brought by 'with_position=true' option
294+
},
295+
{
296+
"code": "cameras",
297+
"parent": "master",
298+
"updated": "2022-11-22T14:40:43+00:00",
299+
"labels": {
300+
"en_US": "Cameras",
301+
"fr_FR": "Caméras",
302+
"de_DE": "Cameras"
303+
},
304+
"position": 2
305+
},
306+
{
307+
"code": "digital_cameras",
308+
"parent": "cameras",
309+
"updated": "2022-11-22T14:40:43+00:00",
310+
"labels": {
311+
"en_US": "Digital cameras",
312+
"fr_FR": "Caméras digitales",
313+
"de_DE": "Digitale Kameras"
314+
},
315+
"position": 2
316+
},
317+
]
318+
````
319+
320+
Using parent and position attributes, the corresponding category tree can be determined:
321+
- master
322+
- tv_projectors
323+
- cameras
324+
- digital_cameras
325+
326+
:::info
327+
This is the end of the App workflow! Well done!
328+
Now that you have successfully synchronized the essential PIM data you can move on to your next app building task.
329+
If you need to sync the [Assets](/getting-started/synchronize-pim-products-6x/step-6.html) and [Reference Entities](/getting-started/synchronize-pim-products-6x/step-5.html) record stay tuned, more guided tutorials should follow up to assist you with this.
330+
:::

src/partials/layout.handlebars

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
<li><a href="/tutorials/how-to-get-families-and-attributes.html">How to get families, family variants, and attributes</a></li>
105105
<li><a href="/tutorials/how-to-get-pim-product-information.html">How to get PIM product information</a></li>
106106
<li><a href="/tutorials/how-to-collect-product-variations.html">How to collect product variations</a></li>
107+
<li><a href="/tutorials/how-to-get-pim-category-tree.html">How get PIM category tree</a></li>
107108
</ul>
108109
</li>
109110

tasks/build-doc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ gulp.task('build-tutorials-homepage', ['clean-dist','less'], function () {
826826
"how-to-get-families-and-attributes.md": "How to get families, family variants, and attributes",
827827
"how-to-get-pim-product-information.md": "How to get PIM product information",
828828
"how-to-collect-product-variations.md": "How to collect product variations",
829+
"how-to-get-pim-category-tree.md": "How to get PIM category tree",
829830
};
830831

831832
const useCases = [
@@ -873,6 +874,12 @@ gulp.task('build-tutorials-homepage', ['clean-dist','less'], function () {
873874
'features': [features[1], features[2]],
874875
'use_cases': useCases
875876
},
877+
{
878+
'title': 'How to get PIM category tree',
879+
'link': '/tutorials/how-to-get-pim-category-tree.html',
880+
'features': [features[5]],
881+
'use_cases': useCases
882+
},
876883
];
877884

878885
const isOnePage = false;
@@ -910,6 +917,7 @@ gulp.task('build-tutorials', ['clean-dist','less'], function () {
910917
"how-to-get-families-and-attributes.md": "How to get families, family variants, and attributes",
911918
"how-to-get-pim-product-information.md": "How to get PIM product information",
912919
"how-to-collect-product-variations.md": "How to collect product variations",
920+
"how-to-get-pim-category-tree.md": "How to get PIM category tree",
913921
};
914922

915923
const isOnePage = false;

0 commit comments

Comments
 (0)