Message-ID: <1999954665.3076.1485851878705.JavaMail.confluence@ip-10-127-227-164> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_3075_1228312055.1485851878705" ------=_Part_3075_1228312055.1485851878705 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
We will start by going through the various ways to find and retr= ieve content from eZ Publish using the API. While this will be covered in f= urther dedicated documentation, it is necessary to explain a few basic conc= epts of the Public API. In the following recipes, you will learn about the = general principles of the API as they are introduced in individual recipes.=
In this recipe, we will see how to fetch a Content instance from the rep= ository, and obtain its Field's content.
Let's first see the full code. You can see the Command line version at&n= bsp;https:= //github.com/ezsystems/CookbookBundle/blob/master/Command/ViewContentComman= d.php.
$repository =3D $this->getContainer()->get( 'ezpublish.api.rep= ository' ); $contentService =3D $repository->getContentService(); $contentTypeService =3D $repository->getContentTypeService(); $fieldTypeService =3D $repository->getFieldTypeService(); try { $content =3D $contentService->loadContent( 66 ); $contentType =3D $contentTypeService->loadContentType( $content->= contentInfo->contentTypeId ); // iterate over the field definitions of the content type and print out= each field's identifier and value foreach( $contentType->fieldDefinitions as $fieldDefinition ) { $output->write( $fieldDefinition->identifier . ": " ); $fieldType =3D $fieldTypeService->getFieldType( $fieldDefinition= ->fieldTypeIdentifier ); $field =3D $content->getField( $fieldDefinition->identifier )= ; =09=09// We use the Field's toHash() method to get readable content out of = the Field $valueHash =3D $fieldType->toHash( $field->value ); $output->writeln( $valueHash ); } } catch( \eZ\Publish\API\Repository\Exceptions\NotFoundException $e ) { // if the id is not found $output->writeln( "No content with id $contentId" ); } catch( \eZ\Publish\API\Repository\Exceptions\UnauthorizedException $e ) { // not allowed to read this content $output->writeln( "Anonymous users are not allowed to read content w= ith id $contentId" ); }=20
Let's analyze this code block by block.
$repository =3D $this->getContainer()->get( 'ezpublish.api.rep= ository' ); $contentService =3D $repository->getContentService(); $contentTypeService =3D $repository->getContentTypeService(); $fieldTypeService =3D $repository->getFieldTypeService();=20
This is the initialization part. As explained above, everything in the P=
ublic API goes through the repository via dedicated services. We get the re=
pository from the service container, using the method get() of our container, obtained via
$this->getContainer()<=
/code>. Using our $repository variable, we fetch the two services we will n=
eed using
getContentService()
and getFiel=
dTypeService()
.
try { // iterate over the field definitions of the content type and print out= each field's identifier and value $content =3D $contentService->loadContent( 66 );=20
Everything starting from line 5 is about getting our Content and iterati=
ng over its Fields. You can see that the whole logic is part of a
The first thing we do is use the Content Service to load a Content item =
using its ID, 66: $contentService->loadContent
( 66 )
. As you can see on the API doc page, this method exp=
ects a Content ID, and returns a Content Value Object.
foreach( $contentType->fieldDefinitions as $fieldDefinition ) { // ignore ezpage if( $fieldDefinition->fieldTypeIdentifier =3D=3D 'ezpage' ) continue; $output->write( $fieldDefinition->identifier . ": " ); $fieldType =3D $fieldTypeService->getFieldType( $fieldDefinition->= ;fieldTypeIdentifier ); $fieldValue =3D $content->getFieldValue( $fieldDefinition->identi= fier ); $valueHash =3D $fieldType->toHash( $fieldValue ); $output->writeln( $valueHash ); }=20
This block is the one that actually displays the value.
It iterates over the Content's (Content Object) fields using the Content=
Type's (Content Class) FieldDefinitions (Content Class Attribute) ($c=
ontentType->fieldDefinitions
).
For each Field Definition (Content Class Attribute), we start by display=
ing its identifier ($fieldDefinition->identifier
). We then =
get the FieldType (Datatype) instance using the FieldType Service ($f=
ieldTypeService->getFieldType( $fieldDefinition->fieldTypeIdentifier =
)
). This method expects the requested FieldType's identifier, as a s=
tring (ezstring, ezxmltext...), and returns an eZ\Publish\API\Re=
pository\FieldType
object.
The Field Value object (Content Object Attribute) is obtained using the&=
nbsp;getFieldValue()
method of the Content Value Object w=
hich we obtained using ContentService::loadContent()
.
Using the FieldType object, we can convert the Field Value to a hash usi=
ng the toHash()
method, provided by every FieldType.=
This method returns a primitive type (string, hash) out of a Field instanc=
e.
With this example, you should get a first idea on how you interact with = the API. Everything is done through services, each service being responsibl= e for a specific part of the repository (Content, FieldType, etc).
Loading Content in different languages
Since we didn't specify any language code, our Field object is returned =
in the given Content item's main language. If you'd prefer it to fall back =
to the SiteAccess language(s), then take advantage of TranslationHelpers. O=
r if you want to use an altogether different language, you can specify a la=
nguage code in the getField()
call:
$content->getFieldValue( $fieldDefinition->identifier, 'fre-F= R' )=20
catch ( \eZ\Publish\API\Repository\Exceptions\NotFoundException $e = ) { $output->writeln( "<error>No content with id $contentId found&= lt;/error>" ); } catch ( \eZ\Publish\API\Repository\Exceptions\UnauthorizedException $e ) { $output->writeln( "<error>Permission denied on content with id= $contentId</error>" ); }=20
As said earlier, the Public API uses Exceptions to han=
dle errors. Each method of the API may throw different exceptions, dependin=
g on what it does. Which exceptions can be thrown is usually documented for=
each method. In our case, loadContent()
may throw t=
wo types of exceptions: NotFoundException
, if the request=
ed ID isn't found, and UnauthorizedException
if the =
currently logged in user isn't allowed to view the requested content.
It is a good practice to cover each exception you expect to happen. In t= his case, since our Command takes the content ID as a parameter, this ID ma= y either not exist, or the referenced content may not be visible to our use= r. Both cases are covered with explicit error messages.
This recipe will show how to traverse a Location's subtree. The full cod= e implements a command that takes a Location ID as an argument, and recursi= vely prints this location's subtree.
Full code
In this code, we introduce the LocationService. This service is used to interac=
t with locations (eZ Publish 4 nodes). We use two methods from this service=
: loadLocation()
, and load=
LocationChildren()
.
try { // load the starting location and browse $location =3D $this->locationService->loadLocation( $locationId )= ; $this->browseLocation( $location, $output ); } catch ( \eZ\Publish\API\Repository\Exceptions\NotFoundException $e ) { $output->writeln( "<error>No location found with id $locationI= d</error>" ); } catch( \eZ\Publish\API\Repository\Exceptions\UnauthorizedException $e ) { $output->writeln( "<error>Current users are not allowed to rea= d location with id $locationId</error>" ); }=20
As for the ContentService, loadLocation()
returns a V=
alue Object, here a Location
. Errors are handled with excepti=
ons: NotFoundException
if the Location ID couldn't be f=
ound, and UnauthorizedException
if the current =
repository user isn't allowed to view this location.
private function browseLocation( Location $location, OutputInterfac= e $output, $depth =3D 0 ) { $childLocationList =3D $this->locationService->loadLocationChildr= en( $location, $offset =3D 0, $limit =3D -1 ); // If offset and limit had been specified to something else then "all",= then $childLocationList->totalCount contains the total count for iterat= ion use foreach ( $childLocationList->locations as $childLocation ) { $this->browseLocation( $childLocation, $output, $depth + 1 ); } }=20
LocationService::loadLocationChildren()
returns a =
;LocationList Value Objects that we can iterate=
over.
Note that unlike loadLocation(), we don't need to care for permissions h= ere: the currently logged in user's permissions will be respected when load= ing children, and locations that can't be viewed won't be returned at all.<= /p>
Full code
Should you need more advanced children fetching methods, the =
SearchService=
is what you are looking for.
Content is a central piece in the Public API. You will often need to sta=
rt from a Content item, and dig in from its metadata. Basic content metadat=
a is made available through ContentInfo
objects. Th=
is Value Object mostly provides primitive fields: contentTypeId
, publishedDate
or mainLocationId
. But it is als=
o used to request further Content related Value Objects from various servic=
es.
The full example implements an ezpublish:cookbook:view_content_met=
adata
command that prints out all the available metadata, given a co=
ntent ID.
Full code
We introduce here several new services: URLAliasService
, UserService
and <=
code>SectionService. The concept should be familiar to you now.<=
/p>
/** @var $repository \eZ\Publish\API\Repository\Repository */ $repository =3D $this->getContainer()->get( 'ezpublish.api.repository= ' ); $contentService =3D $repository->getContentService(); $locationService =3D $repository->getLocationService(); $urlAliasService =3D $repository->getURLAliasService(); $sectionService =3D $repository->getSectionService(); $userService =3D $repository->getUserService();=20
In a command line script, the repository runs as if executed by the anon=
ymous user. In order to identify it as a different user, you need to use th=
e UserSer=
vice
as follows:
$administratorUser =3D $userService->loadUser( 14 ); $repository->setCurrentUser( $administratorUser );=20
This may be crucial when writing maintenance or synchronization scripts.=
This is of course not required in template functions or controller code,= as the HTTP layer will take care of identifying the user, and automaticall= y set it in the repository.
We will now load a ContentInfo
object using the provided ID=
and use it to get our Content's meta data
$contentInfo =3D $contentService->loadContentInfo( $contentId );==20
// show all locations of the content $locations =3D $locationService->loadLocations( $contentInfo ); $output->writeln( "<info>LOCATIONS</info>" ); foreach ( $locations as $location ) { $urlAlias =3D $urlAliasService->reverseLookup( $location ); $output->writeln( " $location->pathString ($urlAlias->path)"= ); }=20
We first use LocationService::loadLocations()
t=
o get the Locations for our Content=
Info
. This method returns an array of Location
Value O=
bjects. In this example, we print out the Location's path string (/path/to/=
content). We also use URLAliasService::reverseLookup() to get=
the location's main URLAlias.
Limitations with multi-location
(version <=3D 5.2)
When Content has multiple Locations, note that sorting on Location prope= rties cannot work correctly. It's a limitation by design : SortClause= \LocationPriority will not be limited to the locations under Criterion\Pare= ntLocationId.
Location Search in 5.3
This limitation does not exist in Location Search. Migrating to version =
5.3 adds the Location Search feature, and removes this limitation.
With =
Location Search, usage of Location Criterions and Sort Clauses with Content=
Search is deprecated. If search by Location properties is needed Location =
Search should be used.
We now want to list relations from and to our Content. Since relations a=
re versioned, we need to feed the ContentService::loadRelati=
ons()
with a VersionInfo
object. We can get the=
current version's VersionInfo
using ContentService::loadVersionInfo()
. If we had been looking=
for an archived version, we could have specified the version number as the=
second argument to this method.
// show all relations of the current version $versionInfo =3D $contentService->loadVersionInfo( $contentInfo ); $relations =3D $contentService->loadRelations( $versionInfo ); if ( count( $relations ) ) { $output->writeln( "<info>RELATIONS</info>" ); foreach ( $relations as $relation ) { $name =3D $relation->destinationContentInfo->name; $output->write( " Relation of type " . $this->outputRelation= Type( $relation->type ) . " to content $name" ); } }=20
We can iterate over the Relation objects array we got from loadRelation= s(), and use these Value Objects to get data about our relations. It has tw= o main properties: destinationContentInfo, and sourceContentInfo. They also= hold the relation type (embed, common...), and the optional Field this rel= ations is made with.
We can of course get our Content's metadata by using the Value Object's = properties.
// show meta data $output->writeln( "\n<info>METADATA</info>" ); $output->writeln( " <info>Name:</info> " . $contentInfo->= ;name ); $output->writeln( " <info>Type:</info> " . $contentType->= ;identifier ); $output->writeln( " <info>Last modified:</info> " . $conten= tInfo->modificationDate->format( 'Y-m-d' ) ); $output->writeln( " <info>Published:</info> ". $contentInfo= ->publishedDate->format( 'Y-m-d' ) ); $output->writeln( " <info>RemoteId:</info> $contentInfo->= ;remoteId" ); $output->writeln( " <info>Main Language:</info> $contentInf= o->mainLanguageCode" ); $output->writeln( " <info>Always available:</info> " . ( $c= ontentInfo->alwaysAvailable ? 'Yes' : 'No' ) );=20
We can use UserService::loadUser()
with content =
ownerId
property of our ContentInfo
to load the C=
ontent
's owner as a User
Value Object.
$owner =3D $userService->loadUser( $contentInfo->ownerId ); $output->writeln( " <info>Owner:</info> " . $owner->cont= entInfo->name );=20
To get the current version's creator, and not the content's owner, you n=
eed to use the creatorId
property from the current version's <=
code>VersionInfo object.
The section's ID can be found in the sectionId
property of =
the ContentInfo
object. To get the matching Section Value Obje=
ct, you need to use the SectionService::loadSection() method.
$section =3D $sectionService->loadSection( $contentInfo->sect= ionId ); $output->writeln( " <info>Section:</info> $section->name= " );=20
To conclude we can also iterate over the Content's version, as Ver=
sionInfo
Value Objects.
$versionInfoArray =3D $contentService->loadVersions( $contentInf= o ); if ( count( $versionInfoArray ) ) { $output->writeln( "\n<info>VERSIONS</info>" ); foreach ( $versionInfoArray as $versionInfo ) { $creator =3D $userService->loadUser( $versionInfo->creatorId = ); $output->write( " Version $versionInfo->versionNo " ); $output->write( " by " . $creator->contentInfo->name ); $output->writeln( " " . $this->outputStatus( $versionInfo->= ;status ) . " " . $versionInfo->initialLanguageCode ); } }=20
We use the ContentService::loadVersions()
metho=
d, and get an array of VersionInfo
objects.
In this section we will cover how the SearchService
can be=
used to search for Content, by using a Query
and a combi=
nations of Criteria
you will get a =
SearchResult
object back containing list of Content and cou=
nt of total hits. In the future this object will also include facets, spell=
checking and "more like this" when running on a backend that supports it (=
for instance Solr).
Full code
In this recipe, we will run a simple full text search over every compati= ble attribute.
We introduce here a new object: Query
. It is used to b=
uild up a Content query based on a set of Criterion
objects.=
p>
$query =3D new \eZ\Publish\API\Repository\Values\Content\Query(); $query->criterion =3D new Query\Criterion\FullText( $text );=20
$query =3D new \eZ\Publish\API\Repository\Values\Content\Query(); $query->filter =3D new Query\Criterion\FullText( $text );=20
Multiple criteria can be grouped together using "logical criteria", such= as LogicalAnd or LogicalOr. Since in = this case we only want to run a text search, we simply use a <= code>FullText criterion object.
The full list of criteria can be found on your installation in the follo= wing directory vendor/ezsystems/ezpublish-kernel/eZ/= Publish/API/Repository/Values/Content/Query/Criterion. Additionally you= may look at integration tests like vendor/ezsystems/ezpubli= sh-kernel/eZ/Publish/API/Repository/Tests/SearchServiceTest.php for mor= e details on how these are used.
NB: Be aware that the links point to code in the upcoming ve=
rsion (master) and might not represent the criteria
Criterion independence before 5.3
Note the criterion independance : Criterion are not related to each othe= r by design and this can lead to a limitation in Content Search. See an example in the Criteri= on Independance example page.
The Query
object is given as an argument to Sea=
rchService::findContent()
. This method returns a SearchRes=
ult
object. This object provides you with various information about =
the search operation (number of results, time taken, spelling suggestions, =
or facets, as well as of course, the results themselves.
$result =3D $searchService->findContent( $query ); $output->writeln( 'Found ' . $result->totalCount . ' items' ); foreach ( $result->searchHits as $searchHit ) { $output->writeln( $searchHit->valueObject->contentInfo->nam= e ); }=20
The searchHits
properties of the SearchResult
=
object is an array of SearchHit objects. In valueObject
proper=
ty of SearchHit
, you will find the Content
&n=
bsp;object that match the given Query
.
Tip: If you you are searching using a unique identifier, for in=
stance using the content id or content remote id criterion, then =
you can use SearchService::findSingle()
, this ta=
kes a Criterion and returns a single Content item, or throws NotFound excep=
tion if none is found.
Full code
As explained in the previous chapter, Criterion objects are grouped toge=
ther using Logical criteria. We will now see how multiple criteria objects =
can be combined into a fine grained search Query
.
use eZ\Publish\API\Repository\Values\Content\Query\Criterion; use eZ\Publish\API\Repository\Values\Content; // [...] $query =3D new Query(); $criterion1 =3D new Criterion\Subtree( $locationService->loadLocation( 2= )->pathString ); $criterion2 =3D new Criterion\ContentTypeIdentifier( 'folder' ); $query->criterion =3D new Criterion\LogicalAnd( array( $criterion1, $criterion2 ) ); $result =3D $searchService->findContent( $query );=20
A Subtree
criterion limits the search to th=
e subtree with pathString, which looks like: /1/2/
. A ContentTypeId
Criterion to limit the search=
to Content of ContentType 1. Those two criteria are grouped with a L=
ogicalAnd
operator. The query is executed as before, with Sear=
chService::findContent()
.
Full code
A search isn't only meant for searching, it also provides the future int= erface for what you in eZ Publish 4.x would know as a content "fetch". And = as this is totally backend agnostic, in future eZ Publish 5.x versions= this will be powered by either Solr or ElasticSearch meaning it also repla= ces "ezfind" fetch functions.
Following the examples above we now change it a bit to combine several c= riteria with both a AND and a OR condition.
use eZ\Publish\API\Repository\Values\Content\Query\Criterion; use eZ\Publish\API\Repository\Values\Content; // [...] $query =3D new Query(); $query->criterion =3D new Criterion\LogicalAnd( array( new Criterion\ParentLocationId( 2 ), new Criterion\LogicalOr( array( new Criterion\ContentTypeIdentifier( 'folder' ), new Criterion\ContentTypeId( 2 ) ) ) ) ); $result =3D $searchService->findContent( $query );=20
A ParentLocationId
crite=
rion limits the search to the children of location 2. An array of "Co=
ntentTypeId"
Criteria to limit the search to Content of Content=
Type's with id 1 or 2 grouped in a LogicalOr
operato=
r. Those two criteria are grouped with a LogicalAnd
=
operator. As always the query is executed as before, with Search=
Service::findContent()
.
Tip: Want to do a subtree filter ( in 4.x: fetch( 'content', 't= ree' ) )? Change the location filter to use the Subtree criterion filt= er as shown in the advanced search example above.
The above example is fine, but it can be optimized a bit by taking advan= tage of the fact that all filter criteria support being given an array of v= alues (IN operator) instead of as single value (EQ operator).
If your on eZ Publish 5.1+ you can use the new ContentTypeIdentifier
Criterion:
use eZ\Publish\API\Repository\Values\Content\Query\Criterion; use eZ\Publish\API\Repository\Values\Content; // [...] $query =3D new Query(); $query->criterion =3D new Criterion\LogicalAnd( array( new Criterion\ParentLocationId( 2 ), new Criterion\ContentTypeIdentifier( array( 'article', 'folder' ) ) ) ); $result =3D $searchService->findContent( $query );=20
Tip: All filter criteria are capable of doing an "IN" selection= , the ParentLocationId above could f.e. have been provided "array( 2, 43 )" to includ= e second level children in both your content tree (2) and your media tree (= 43).
In many cases you=
might need the number of Content items matching a search, but with no need=
to do anything else with the results.
Thanks to the fact that the "searchHits" property of t=
he SearchResult
object always refer=
s to the total amount, it =
is enough to run a standard search and set $limit to 0. This way no results=
will be retrieved, and the search will not be slowed down, even when the n=
umber of matching results is huge.
use eZ\Publish\API\Repository\Values\Content\Query; // [...] $query =3D new Query(); $query->limit =3D 0; // [...] ( Add criteria as shown above ) $resultCount =3D $searchService->findContent( $query )->totalCount;= pre>=20