مكتبة PHP لدعم تنفيذ التمثيلات لخدمات الويب HATEOAS REST.
الطريقة الموصى بها لتثبيت Hateoas هي من خلال Composer. اطلب الحزمة willdurand/hateoas عن طريق تشغيل الأمر التالي:
composer require willdurand/hateoasسيؤدي هذا إلى حل أحدث إصدار ثابت.
بخلاف ذلك، قم بتثبيت المكتبة وإعداد أداة التحميل التلقائي بنفسك.
إذا كنت تريد استخدام التعليقات التوضيحية للتكوين، فأنت بحاجة إلى تثبيت حزمة doctrine/annotations :
composer require doctrine/annotationsإذا كان تطبيقك يستخدم PHP 8.1 أو أعلى، فمن المستحسن استخدام سمات PHP الأصلية. في هذه الحالة لن تحتاج إلى تثبيت حزمة Doctrine.
هناك حزمة لذلك! قم بتثبيت BazingaHateoasBundle، واستمتع!
مهم:
بالنسبة لأولئك الذين يستخدمون الإصدار
1.0، يمكنك الانتقال إلى صفحة الوثائق هذه.بالنسبة لأولئك الذين يستخدمون الإصدار
2.0، يمكنك الانتقال إلى صفحة الوثائق هذه.تمت كتابة الوثائق التالية لـ Hateoas 3.0 وما فوق.
تستفيد Hateoas من مكتبة Serializer لتوفير طريقة رائعة لإنشاء خدمات الويب HATEOAS REST. يرمز HATEOAS إلى Hypermedia باعتباره محرك حالة التطبيق ، ويضيف روابط الوسائط التشعبية إلى تمثيلاتك (أي استجابات واجهة برمجة التطبيقات (API) الخاصة بك). HATEOAS يدور حول إمكانية اكتشاف الإجراءات على المورد.
على سبيل المثال، لنفترض أن لديك واجهة برمجة تطبيقات للمستخدم والتي تُرجع تمثيلاً لمستخدم واحد على النحو التالي:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe "
}
} لإخبار مستهلكي واجهة برمجة التطبيقات (API) الخاصة بك بكيفية استرداد البيانات لهذا المستخدم المحدد، يجب عليك إضافة رابطك الأول إلى هذا التمثيل، دعنا نسميه self لأنه URI لهذا المستخدم المحدد:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " }
}
}
}دعونا نحفر في Hateoas الآن.
في مصطلحات هاتيوا، يُنظر إلى الروابط على أنها علاقات مضافة إلى الموارد. ومن الجدير بالذكر أن العلاقات تشير أيضًا إلى الموارد المضمنة أيضًا، ولكن سيتم تناول هذا الموضوع في قسم تضمين الموارد.
الرابط عبارة عن علاقة يتم تحديدها بواسطة name (على سبيل المثال، self ) ولها معلمة href :
use JMS Serializer Annotation as Serializer ;
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Serializer XmlRoot ( "user" )
*
* @ Hateoas Relation ( "self" , href = "expr('/api/users/' ~ object.getId())" )
* /
class User
{
/ * * @ Serializer XmlAttribute * /
private $ id ;
private $ firstName ;
private $ lastName ;
public function getId () {}
} use JMS Serializer Annotation as Serializer ;
use Hateoas Configuration Annotation as Hateoas ;
#[ Serializer XmlRoot( ' user ' )]
#[ Hateoas Relation( ' self ' , href: " expr('/api/users/' ~ object.getId()) " )]
class User
{
#[ Serializer XmlAttribute]
private $ id ;
private $ firstName ;
private $ lastName ;
public function getId () {}
} في المثال أعلاه، قمنا بتكوين علاقة self تكون عبارة عن رابط بسبب المعلمة href . سيتم تناول قيمتها، التي قد تبدو غريبة للوهلة الأولى، بشكل موسع في قسم لغة التعبير. يتم استخدام هذه القيمة الخاصة لإنشاء URI.
في هذا القسم، يتم استخدام التعليقات التوضيحية/السمات لتكوين Hateoas. كما يتم دعم تنسيقات XML و YAML . إذا كنت ترغب في ذلك، يمكنك استخدام PHP عادي أيضًا.
هام: يجب عليك تكوين كل من Serializer وHateoas بنفس الطريقة. على سبيل المثال، إذا كنت تستخدم YAML لتكوين Serializer، استخدم YAML لتكوين Hateoas.
أسهل طريقة لتجربة HATEOAS هي باستخدام HateoasBuilder . يمتلك المنشئ طرقًا عديدة لتكوين مُسلسِل Hateoas، لكننا لن نتعمق فيها الآن (راجع The HateoasBuilder). كل شيء يعمل بشكل جيد خارج الصندوق:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()-> build ();
$ user = new User ( 42 , ' Adrien ' , ' Brault ' );
$ json = $ hateoas -> serialize ( $ user , ' json ' );
$ xml = $ hateoas -> serialize ( $ user , ' xml ' ); الكائن $hateoas هو مثيل لـ JMSSerializerSerializerInterface ، قادم من مكتبة Serializer. لا يأتي Hateoas مزودًا ببرنامج تسلسلي خاص به، بل يتم ربطه ببرنامج JMS Serializer.
بشكل افتراضي، يستخدم Hateoas لغة تطبيق النص التشعبي (HAL) لإجراء تسلسل JSON. يحدد هذا بنية الاستجابة (على سبيل المثال، يجب أن تكون "الروابط" موجودة تحت مفتاح _links ):
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
}بالنسبة لـ XML، يتم استخدام Atom Links بشكل افتراضي:
< user id = " 42 " >
< first_name > <![CDATA[ Adrien ]]> </ first_name >
< last_name > <![CDATA[ Brault ]]> </ last_name >
< link rel = " self " href = " /api/users/42 " />
</ user >ومن الجدير بالذكر أن هذه التنسيقات هي التنسيقات الافتراضية وليست الوحيدة المتوفرة. يمكنك استخدام تنسيقات مختلفة من خلال برامج تسلسل مختلفة، وحتى إضافة التنسيقات الخاصة بك.
الآن بعد أن عرفت كيفية إضافة الروابط ، فلنرى كيفية إضافة الموارد المضمنة .
في بعض الأحيان، يكون تضمين الموارد ذات الصلة أكثر فعالية بدلاً من الارتباط بها، حيث يمنع العملاء من الاضطرار إلى تقديم طلبات إضافية لجلب تلك الموارد.
المورد المضمن هو علاقة مسماة تحتوي على بيانات، يتم تمثيلها بواسطة المعلمة embedded .
use JMS Serializer Annotation as Serializer ;
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* ...
*
* @ Hateoas Relation (
* "manager" ,
* href = "expr('/api/users/' ~ object.getManager().getId())" ,
* embedded = "expr(object.getManager())" ,
* exclusion = @ Hateoas Exclusion ( excludeIf = "expr(object.getManager() === null)" )
* )
* /
class User
{
...
/ * * @ Serializer Exclude * /
private $ manager ;
} use JMS Serializer Annotation as Serializer ;
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
' manager ' ,
href: " expr('/api/users/' ~ object.getManager().getId()) " ,
embedded: " expr(object.getManager()) " ,
exclusion: new Hateoas Exclusion (excludeif: " expr(object.getManager() === null) " ),
)]
class User
{
...
#[ Serializer Exclude]
private $ manager ;
} ملحوظة: ستحتاج إلى استبعاد خاصية المدير من عملية التسلسل، وإلا سيقوم كل من المُسلسِل وHateoas بإجراء تسلسل لها. سيكون عليك أيضًا استبعاد علاقة المدير عندما يكون المدير null ، وإلا سيحدث خطأ عند إنشاء رابط href (استدعاء getId() على null ).
نصيحة: إذا كانت خاصية المدير عبارة عن كائن يحتوي بالفعل على رابط _self ، فيمكنك إعادة استخدام هذه القيمة لـ href بدلاً من تكرارها هنا. راجع مساعد الارتباط.
$ hateoas = HateoasBuilder:: create ()-> build ();
$ user = new User ( 42 , ' Adrien ' , ' Brault ' , new User ( 23 , ' Will ' , ' Durand ' ));
$ json = $ hateoas -> serialize ( $ user , ' json ' );
$ xml = $ hateoas -> serialize ( $ user , ' xml ' ); بالنسبة إلى json ، يضع تمثيل HAL هذه العلاقات المضمنة داخل مفتاح _embedded :
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
},
"manager" : {
"href" : " /api/users/23 "
}
},
"_embedded" : {
"manager" : {
"id" : 23 ,
"first_name" : " Will " ,
"last_name" : " Durand " ,
"_links" : {
"self" : {
"href" : " /api/users/23 "
}
}
}
}
} في XML، سيؤدي إجراء تسلسل للعلاقات embedded إلى إنشاء عناصر جديدة:
< user id = " 42 " >
< first_name > <![CDATA[ Adrien ]]> </ first_name >
< last_name > <![CDATA[ Brault ]]> </ last_name >
< link rel = " self " href = " /api/users/42 " />
< link rel = " manager " href = " /api/users/23 " />
< manager rel = " manager " id = " 23 " >
< first_name > <![CDATA[ Will ]]> </ first_name >
< last_name > <![CDATA[ Durand ]]> </ last_name >
< link rel = " self " href = " /api/users/23 " />
</ manager >
</ user > يتم استنتاج اسم العلامة للمورد المضمن من التعليق التوضيحي @XmlRoot ( xml_root_name في YAML، xml-root-name في XML) القادم من تكوين Serializer.
توفر المكتبة عدة فئات في مساحة الاسم HateoasRepresentation* لمساعدتك في المهام الشائعة. هذه فئات بسيطة تم تكوينها باستخدام التعليقات التوضيحية للمكتبة.
من المحتمل أن تكون فئات PaginatedRepresentation و OffsetRepresentation و CollectionRepresentation هي الأكثر إثارة للاهتمام. تكون هذه مفيدة عندما يكون المورد الخاص بك عبارة عن مجموعة من الموارد (على سبيل المثال /users عبارة عن مجموعة من المستخدمين). تساعدك هذه على تمثيل المجموعة وإضافة ترقيم الصفحات والحدود:
use Hateoas Representation PaginatedRepresentation ;
use Hateoas Representation CollectionRepresentation ;
$ paginatedCollection = new PaginatedRepresentation (
new CollectionRepresentation ( array ( $ user1 , $ user2 , ...)),
' user_list ' , // route
array (), // route parameters
1 , // page number
20 , // limit
4 , // total pages
' page ' , // page route parameter name , optional , defaults to 'page'
' limit ' , // limit route parameter name , optional , defaults to 'limit'
false , // generate relative URIs , optional , defaults to `false`
75 // total collection size , optional , defaults to `null`
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' ); يقدم CollectionRepresentation تمثيلاً أساسيًا لمجموعة مضمنة.
تم تصميم PaginatedRepresentation لإضافة الروابط self first والروابط last next previous عندما يكون ذلك ممكنًا.
يعمل OffsetRepresentation تمامًا مثل PaginatedRepresentation ولكنه يكون مفيدًا عندما يتم التعبير عن ترقيم الصفحات offset limit total .
يضيف RouteAwareRepresentation علاقة self بناءً على مسار معين.
يمكنك إنشاء عناوين URI مطلقة عن طريق تعيين المعلمة absolute على true في كل من PaginatedRepresentation و RouteAwareRepresentation .
توفر مكتبة Hateoas أيضًا PagerfantaFactory لإنشاء PaginatedRepresentation بسهولة من مثيل Pagerfanta. إذا كنت تستخدم مكتبة Pagerfanta، فهذه طريقة أسهل لإنشاء تمثيلات المجموعة:
use Hateoas Configuration Route ;
use Hateoas Representation Factory PagerfantaFactory ;
$ pagerfantaFactory = new PagerfantaFactory (); // you can pass the page ,
// and limit parameters name
$ paginatedCollection = $ pagerfantaFactory -> createRepresentation (
$ pager ,
new Route ( ' user_list ' , array ())
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' );ستحصل على محتوى JSON التالي:
{
"page" : 1 ,
"limit" : 10 ,
"pages" : 1 ,
"_links" : {
"self" : {
"href" : " /api/users?page=1&limit=10 "
},
"first" : {
"href" : " /api/users?page=1&limit=10 "
},
"last" : {
"href" : " /api/users?page=1&limit=10 "
}
},
"_embedded" : {
"items" : [
{ "id" : 123 },
{ "id" : 456 }
]
}
}ومحتوى XML التالي:
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< collection page = " 1 " limit = " 10 " pages = " 1 " >
< entry id = " 123 " ></ entry >
< entry id = " 456 " ></ entry >
< link rel = " self " href = " /api/users?page=1 & limit=10 " />
< link rel = " first " href = " /api/users?page=1 & limit=10 " />
< link rel = " last " href = " /api/users?page=1 & limit=10 " />
</ collection > إذا كنت تريد تخصيص CollectionRepresentation المضمن، فمرر واحدًا كوسيطة ثالثة للأسلوب createRepresentation() :
use Hateoas Representation Factory PagerfantaFactory ;
$ pagerfantaFactory = new PagerfantaFactory (); // you can pass the page and limit parameters name
$ paginatedCollection = $ pagerfantaFactory -> createRepresentation (
$ pager ,
new Route ( ' user_list ' , array ()),
new CollectionRepresentation ( $ pager -> getCurrentPageResults ())
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' );إذا كنت تريد تغيير اسم جذر XML للمجموعة، فقم بإنشاء فئة جديدة مع تكوين جذر XML واستخدم الآلية المضمنة:
use JMS Serializer Annotation as Serializer ;
/ * *
* @ Serializer XmlRoot ( "users" )
* /
class UsersRepresentation
{
/ * *
* @ Serializer Inline
* /
private $ inline ;
public function __construct ( $ inline )
{
$ this -> inline = $ inline ;
}
}
$ paginatedCollection = . . . ;
$ paginatedCollection = new UsersRepresentation ( $ paginatedCollection ); use JMS Serializer Annotation as Serializer ;
#[ Serializer XmlRoot( ' users ' )]
class UsersRepresentation
{
#[ Serializer Inline]
private $ inline ;
public function __construct ( $ inline )
{
$ this -> inline = $ inline ;
}
}
$ paginatedCollection = . . . ;
$ paginatedCollection = new UsersRepresentation ( $ paginatedCollection );كما ذكرنا في القسم السابق، التمثيلات عبارة عن فئات تم تكوينها باستخدام التعليقات التوضيحية للمكتبة لمساعدتك في المهام الشائعة. تم وصف تمثيلات المجموعة في التعامل مع المجموعة.
يسمح لك VndErrorRepresentation بوصف استجابة الخطأ باتباع مواصفات vnd.error .
$ error = new VndErrorRepresentation (
' Validation failed ' ,
42 ,
' http://.../ ' ,
' http://.../ '
);إن إجراء تسلسل لمثل هذا التمثيل في XML وJSON سيمنحك المخرجات التالية:
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< resource logref = " 42 " >
< message > <![CDATA[ Validation failed ]]> </ message >
< link rel = " help " href = " http://.../ " />
< link rel = " describes " href = " http://.../ " />
</ resource >{
"message" : " Validation failed " ,
"logref" : 42 ,
"_links" : {
"help" : {
"href" : " http://.../ "
},
"describes" : {
"href" : " http://.../ "
}
}
} تلميح: يوصى بإنشاء فئات الأخطاء الخاصة بك والتي تعمل على توسيع فئة VndErrorRepresentation .
يعتمد Hateoas على مكون Symfony ExpressionLanguage القوي لاسترداد القيم مثل الروابط أو المعرفات أو الكائنات المراد تضمينها.
في كل مرة تقوم فيها بملء قيمة (على سبيل المثال، Relation href في التعليقات التوضيحية أو YAML)، يمكنك إما تمرير قيمة مشفرة أو تعبير . من أجل استخدام لغة التعبير، عليك استخدام تدوين expr() :
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation ( "self" , href = "expr('/api/users/' ~ object.getId())" )
* / use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation( ' self ' , href: " expr('/api/users/' ~ object.getId()) " )]يمكنك معرفة المزيد حول بناء جملة التعبير من خلال قراءة الوثائق الرسمية: بناء جملة التعبير.
بشكل أصلي، يتوفر متغير خاص مسمى object في كل تعبير، ويمثل الكائن الحالي:
expr(object.getId())
نحن نسمي مثل هذا المتغير متغير السياق .
يمكنك إضافة متغيرات السياق الخاصة بك إلى سياق لغة التعبير عن طريق إضافتها إلى مقيم التعبير.
باستخدام HateoasBuilder ، قم باستدعاء الأسلوب setExpressionContextVariable() لإضافة متغيرات سياق جديدة:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setExpressionContextVariable ( ' foo ' , new Foo ())
-> build (); المتغير foo متوفر الآن:
expr(foo !== null)
لمزيد من المعلومات حول كيفية إضافة وظائف إلى لغة التعبير، يرجى الرجوع إلى https://symfony.com/doc/current/components/expression_language/extending.html
نظرًا لأنه يمكنك استخدام لغة التعبير لتحديد روابط العلاقات (مفتاح href )، فيمكنك القيام بالكثير بشكل افتراضي. ومع ذلك، إذا كنت تستخدم إطار عمل، فمن المحتمل أنك سوف ترغب في استخدام المسارات لبناء الروابط.
ستحتاج أولاً إلى تكوين UrlGenerator على المنشئ. يمكنك إما تنفيذ HateoasUrlGeneratorUrlGeneratorInterface أو استخدام HateoasUrlGeneratorCallableUrlGenerator :
use Hateoas UrlGenerator CallableUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator (
null , // By default all links uses the generator configured with the null name
new CallableUrlGenerator ( function ( $ route , array $ parameters , $ absolute ) use ( $ myFramework ) {
return $ myFramework -> generateTheUrl ( $ route , $ parameters , $ absolute );
})
)
-> build ()
;ستتمكن بعد ذلك من استخدام التعليق التوضيحي @Route:
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = {
* "id" = "expr(object.getId())"
* }
* )
* )
* /
class User use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
)
)]
class User{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
} لاحظ أن المكتبة تأتي مع SymfonyUrlGenerator . على سبيل المثال، لاستخدامه في سيليكس:
use Hateoas UrlGenerator SymfonyUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator ( null , new SymfonyUrlGenerator ( $ app [ ' url_generator ' ]))
-> build ()
;يوفر Hateoas مجموعة من المساعدين لتسهيل عملية بناء واجهات برمجة التطبيقات.
توفر فئة LinkHelper طريقة getLinkHref($object, $rel, $absolute = false) التي تسمح لك بالحصول على قيمة href لأي كائن، لأي اسم علاقة محدد. إنه قادر على إنشاء URI (إما مطلق أو نسبي) من أي علاقة ارتباط :
$ user = new User ( 123 , ' William ' , ' Durand ' );
$ linkHelper -> getLinkHref ( $ user , ' self ' );
// / api / users / 123
$ linkHelper -> getLinkHref ( $ user , ' self ' , true );
// http : // example . com / api / users / 123 link الميزة أعلاه متاحة أيضًا في تعبيراتك (راجع لغة التعبير) من خلال وظيفة link(object, rel, absolute) :
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route ( "post_get" , parameters = { "id" = "expr(object.getId())" })
* )
* /
class Post {}
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route ( "user_get" , parameters = { "id" = "expr(object.getId())" })
* )
* @ Hateoas Relation (
* "post" ,
* href = "expr(link(object.getPost(), 'self', true))"
* )
* @ Hateoas Relation (
* "relative" ,
* href = "expr(link(object.getRelativePost(), 'self'))"
* )
* /
class User
{
...
public function getPost ()
{
return new Post ( 456 );
}
public function getRelativePost ()
{
return new Post ( 789 );
}
}#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' post_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
),
)]
class Post {}
#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
),
)]
#[ Hateoas Relation(
' post ' ,
href: " expr(link(object.getPost(), 'self', true)) " ,
)]
#[ Hateoas Relation(
' relative ' ,
href: " expr(link(object.getRelativePost(), 'self')) " ,
)]
class User
{
...
public function getPost ()
{
return new Post ( 456 );
}
public function getRelativePost ()
{
return new Post ( 789 );
}
} انتبه إلى تعبيرات href الخاصة post والعلاقات relative ، بالإضافة إلى القيم المقابلة لها في محتوى JSON التالي:
{
"user" : {
"id" : 123 ,
"first_name" : " William " ,
"last_name" : " Durand " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " },
"post" : { "href" : " http://example.com/api/posts/456 " },
"relative" : { "href" : " /api/posts/789 " }
}
}
} تجدر الإشارة إلى أنه يمكنك فرض ما إذا كنت تريد معرف URI مطلقًا أو نسبيًا باستخدام الوسيطة الثالثة في كل من أسلوب getLinkHref() ووظيفة link .
هام: بشكل افتراضي، ستكون جميع عناوين URI نسبية ، حتى تلك التي تم تعريفها على أنها مطلقة في تكوينها.
$ linkHelper -> getLinkHref ( $ user , ' post ' );
// / api / posts / 456
$ linkHelper -> getLinkHref ( $ user , ' post ' , true );
// http : // example . com / api / posts / 456
$ linkHelper -> getLinkHref ( $ user , ' relative ' );
// / api / posts / 789
$ linkHelper -> getLinkHref ( $ user , ' relative ' , true );
// http : // example . com / api / posts / 789يوفر Hateoas أيضًا مجموعة من ملحقات Twig.
يسمح لك LinkExtension باستخدام LinkHelper في قوالب Twig الخاصة بك، بحيث يمكنك إنشاء روابط في قوالب HTML الخاصة بك على سبيل المثال.
يعرض هذا الامتداد طريقة المساعد getLinkHref() من خلال وظيفة link_href Twig:
{{ link_href(user, 'self') }}
{# will generate: /users/123 #}
{{ link_href(will, 'self', false) }}
{# will generate: /users/123 #}
{{ link_href(will, 'self', true) }}
{# will generate: http://example.com/users/123 #}يوفر Hateoas مجموعة من برامج التسلسل . يتيح لك كل برنامج تسلسل إنشاء محتوى XML أو JSON باتباع تنسيق معين، مثل HAL أو Atom Links على سبيل المثال.
يتيح لك JsonHalSerializer إنشاء علاقات متوافقة مع HAL في JSON. إنه مُسلسل JSON الافتراضي في Hateoas.
يوفر HAL إمكانية الارتباط الخاصة به من خلال اتفاقية تنص على أن كائن المورد له خاصية محجوزة تسمى _links . هذه الخاصية عبارة عن كائن يحتوي على روابط. يتم مفتاح هذه الروابط من خلال علاقة الارتباط الخاصة بها.
يصف HAL أيضًا اتفاقية أخرى تنص على أن المورد قد يكون له خاصية محجوزة أخرى تسمى _embedded . تشبه هذه الخاصية _links حيث يتم تحديد الموارد المضمنة حسب اسم العلاقة. والفرق الرئيسي هو أنه بدلاً من أن تكون روابط، فإن القيم هي كائنات موارد.
{
"message" : " Hello, World! " ,
"_links" : {
"self" : {
"href" : " /notes/0 "
}
},
"_embedded" : {
"associated_events" : [
{
"name" : " SymfonyCon " ,
"date" : " 2013-12-12T00:00:00+0100 "
}
]
}
} يتيح لك XmlSerializer إنشاء روابط Atom في مستندات XML الخاصة بك. إنه مُسلسل XML الافتراضي.
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< note >
< message > <![CDATA[ Hello, World! ]]> </ message >
< link rel = " self " href = " /notes/0 " />
< events rel = " associated_events " >
< event >
< name > <![CDATA[ SymfonyCon ]]> </ name >
< date > <![CDATA[ 2013-12-12T00:00:00+0100 ]]> </ date >
</ event >
</ events >
</ note > يسمح لك XmlHalSerializer بإنشاء علاقات متوافقة مع HAL في XML.
يشبه HAL في XML HAL في JSON، بمعنى أنه يصف علامات link وعلامات resource .
ملاحظة: ستصبح العلاقة self في الواقع سمة للمورد الرئيسي بدلاً من أن تكون علامة link . سيتم إنشاء روابط أخرى كعلامات link .
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< note href = " /notes/0 " >
< message > <![CDATA[ Hello, World! ]]> </ message >
< resource rel = " associated_events " >
< name > <![CDATA[ SymfonyCon ]]> </ name >
< date > <![CDATA[ 2013-12-12T00:00:00+0100 ]]> </ date >
</ resource >
</ note > يجب عليك تطبيق SerializerInterface الذي يصف طريقتين لتسلسل الروابط والعلاقات المضمنة .
يتم استخدام فئة HateoasBuilder لتكوين Hateoas بسهولة بفضل واجهة برمجة التطبيقات (API) القوية والسلسة.
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setCacheDir ( ' /path/to/cache/dir ' )
-> setDebug ( $ trueOrFalse )
-> setDefaultXmlSerializer ()
. . .
-> build ();تقوم جميع الطرق أدناه بإرجاع المنشئ الحالي، بحيث يمكنك ربطه.
setXmlSerializer(SerializerInterface $xmlSerializer) : يضبط مُسلسل XML المراد استخدامه. الافتراضي هو: XmlSerializer ؛setDefaultXmlSerializer() : يقوم بتعيين مُسلسل XML الافتراضي ( XmlSerializer ). setJsonSerializer(SerializerInterface $jsonSerializer) : يضبط مُسلسل JSON المراد استخدامه. الافتراضي هو: JsonHalSerializer ؛setDefaultJsonSerializer() : يقوم بتعيين مُسلسِل JSON الافتراضي ( JsonHalSerializer ). setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator) : يضيف منشئ URL مسمى جديد. إذا كان $name null ، فسيكون منشئ عنوان URL هو المنشئ الافتراضي. setExpressionContextVariable($name, $value) : يضيف متغير سياق تعبير جديد؛setExpressionLanguage(ExpressionLanguage $expressionLanguage) ; includeInterfaceMetadata($include) : ما إذا كان سيتم تضمين البيانات التعريفية من الواجهات؛setMetadataDirs(array $namespacePrefixToDirMap) : يعين خريطة لبادئات مساحة الاسم إلى الدلائل. تتجاوز هذه الطريقة أي أدلة محددة مسبقًا؛addMetadataDir($dir, $namespacePrefix = '') : يضيف دليلاً حيث سيبحث المُسلسل عن البيانات التعريفية للفئة؛addMetadataDirs(array $namespacePrefixToDirMap) : يضيف خريطة لبادئات مساحة الاسم إلى الدلائل؛replaceMetadataDir($dir, $namespacePrefix = '') : يشبه addMetadataDir() ، ولكنه يتجاوز الإدخال الموجود.يرجى قراءة وثائق التسلسل الرسمية لمزيد من التفاصيل.
setDebug($debug) : تمكين أو تعطيل وضع التصحيح؛setCacheDir($dir) : يضبط دليل ذاكرة التخزين المؤقت.تقوم كل من أداة التسلسل ومكتبات Hateoas بجمع بيانات التعريف حول الكائنات الخاصة بك من مصادر مختلفة مثل YML أو XML أو التعليقات التوضيحية. من أجل جعل هذه العملية فعالة قدر الإمكان، فمن المستحسن أن تسمح لمكتبة Hateoas بتخزين هذه المعلومات مؤقتًا. للقيام بذلك، قم بتكوين دليل ذاكرة التخزين المؤقت:
$ builder = Hateoas HateoasBuilder:: create ();
$ hateoas = $ builder
-> setCacheDir ( $ someWritableDir )
-> build ();يدعم Hateoas العديد من مصادر البيانات الوصفية. بشكل افتراضي، يستخدم التعليقات التوضيحية Doctrine (PHP < 8.1) أو سمات PHP الأصلية (PHP >= 8.1)، ولكن يمكنك أيضًا تخزين البيانات التعريفية في ملفات XML أو YAML. بالنسبة للأخيرة، من الضروري تكوين دليل البيانات الوصفية حيث توجد هذه الملفات:
$ hateoas = Hateoas HateoasBuilder:: create ()
-> addMetadataDir ( $ someDir )
-> build (); يتوقع Hateoas أن يتم تسمية ملفات البيانات التعريفية مثل أسماء الفئات المؤهلة بالكامل حيث يتم استبدال all بـ . . إذا تم تسمية فصلك باسم VendorPackageFoo فيجب أن يكون ملف البيانات التعريفية موجودًا في $someDir/Vendor.Package.Foo.(xml|yml) .
يسمح Hateoas للأطر بإضافة علاقات إلى الفئات ديناميكيًا من خلال توفير نقطة امتداد على مستوى التكوين. يمكن أن تكون هذه الميزة مفيدة لأولئك الذين يريدون إنشاء طبقة جديدة أعلى Hateoas، أو إضافة علاقات "عامة" بدلاً من نسخ نفس التكوين في كل فئة.
من أجل الاستفادة من هذه الآلية، يجب تنفيذ واجهة ConfigurationExtensionInterface :
use Hateoas Configuration Metadata ConfigurationExtensionInterface ;
use Hateoas Configuration Metadata ClassMetadataInterface ;
use Hateoas Configuration Relation ;
class AcmeFooConfigurationExtension implements ConfigurationExtensionInterface
{
/ * *
* {@ inheritDoc }
* /
public function decorate ( ClassMetadataInterface $ classMetadata ): void
{
if ( 0 === strpos ( ' AcmeFooModel ' , $ classMetadata -> getName ())) {
// Add a "root" relation to all classes in the `AcmeFooModel` namespace
$ classMetadata -> addRelation (
new Relation (
' root ' ,
' / '
)
);
}
}
} يمكنك الوصول إلى العلاقات الموجودة المحملة من التعليقات التوضيحية أو XML أو YAML باستخدام $classMetadata->getRelations() .
إذا كانت $classMetadata تحتوي على علاقات، أو إذا قمت بإضافة علاقات إليها، فسيتم تخزين علاقاتها مؤقتًا. لذا، إذا قرأت ملفات التكوين (التعليقات التوضيحية، أو XML، أو YAML)، فتأكد من الرجوع إليها في البيانات التعريفية للفئة:
$ classMetadata -> fileResources [] = $ file ;<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< serializer >
< class name = " AcmeDemoRepresentationUser " h : providers = " Class::getRelations expr(sevice('foo').getMyAdditionalRelations()) " xmlns : h = " https://github.com/willdurand/Hateoas " >
< h : relation rel = " self " >
< h : href uri = " http://acme.com/foo/1 " />
</ h : relation >
< h : relation rel = " friends " >
< h : href route = " user_friends " generator = " my_custom_generator " >
< h : parameter name = " id " value = " expr(object.getId()) " />
< h : parameter name = " page " value = " 1 " />
</ h : ref >
< h : embedded xml-element-name = " users " >
< h : content >expr(object.getFriends())</ h : content >
< h : exclusion ... />
</ h : embedded >
< h : exclusion groups = " Default, user_full " since-version = " 1.0 " until-version = " 2.2 " exclude-if = " expr(object.getFriends() === null) " />
</ h : relation >
</ class >
</ serializer > راجع ملف hateoas.xsd لمزيد من التفاصيل.
AcmeDemoRepresentationUser :
relations :
-
rel : self
href : http://acme.com/foo/1
-
rel : friends
href :
route : user_friends
parameters :
id : expr(object.getId())
page : 1
generator : my_custom_generator
absolute : false
embedded :
content : expr(object.getFriends())
xmlElementName : users
exclusion : ...
exclusion :
groups : [Default, user_full]
since_version : 1.0
until_version : 2.2
exclude_if : expr(object.getFriends() === null)
relation_providers : [ "Class::getRelations", "expr(sevice('foo').getMyAdditionalRelations())" ]يمكن تعريف هذا التعليق التوضيحي في الفصل الدراسي.
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "self" ,
* href = "http://hello" ,
* embedded = "expr(object.getHello())" ,
* attributes = { "foo" = "bar" },
* exclusion = ...,
* )
* / use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' self ' ,
href: ' http://hello ' ,
embedded: ' expr(object.getHello()) ' ,
attributes: [ ' foo ' => ' bar ' ],
exclusion: ' ... ' ,
)]| ملكية | مطلوب | محتوى | لغة التعبير |
|---|---|---|---|
| اسم | نعم | خيط | لا |
| href | إذا لم يتم تعيين المضمنة | سلسلة / @Route | نعم |
| مغروس | إذا لم يتم تعيين href | سلسلة / @Embedded | نعم |
| صفات | لا | صفيف | نعم على القيم |
| الاستبعاد | لا | @ الاستبعاد | لا يوجد |
هام: يتم استخدام attributes فقط في علاقات الارتباط (أي يتم دمجها مع خاصية href ، وليس مع الخاصية embedded ).
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "self" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = { "id" = "expr(object.getId())" },
* absolute = true ,
* generator = "my_custom_generator"
* )
* )
* / use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [ ' id ' = ' expr (object. getId ())'],
absolute: true ,
generator: ' my_custom_generator ' ,
),
)]يمكن تعريف هذا التعليق التوضيحي في خاصية href الخاصة بالتعليق التوضيحي @Relation. يتيح لك هذا إنشاء عنوان URL الخاص بك، إذا قمت بتكوينه.
| ملكية | مطلوب | محتوى | لغة التعبير |
|---|---|---|---|
| اسم | نعم | خيط | لا |
| حدود | الإعدادات الافتراضية للصفيف () | صفيف / سلسلة | نعم (سلسلة + قيم الصفيف) |
| مطلق | الافتراضيات كاذبة | منطقية / سلسلة | نعم |
| مولد | لا | سلسلة / فارغة | لا |
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "friends" ,
* embedded = @ Hateoas Embedded (
* "expr(object.getFriends())" ,
* exclusion = ...,
* xmlElementName = "users"
* )
* )
* / use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' friends ' ,
embedded: new Hateoas Embedded (
' expr(object.getFriends()) ' ,
exclusion: ' ... ' ,
xmlElementName: ' users ' ,
),
)] يمكن تعريف هذا التعليق التوضيحي في الخاصية المضمنة للتعليق التوضيحي @Relation. يكون ذلك مفيدًا إذا كنت بحاجة إلى تكوين exclusion أو خيارات xmlElementName للمورد المضمن.
| ملكية | مطلوب | محتوى | لغة التعبير |
|---|---|---|---|
| محتوى | نعم | سلسلة / صفيف | نعم (سلسلة) |
| الاستبعاد | الإعدادات الافتراضية للصفيف () | @ الاستبعاد | لا يوجد |
| xmlElementName | الإعدادات الافتراضية للصفيف () | خيط | لا |
يمكن تعريف هذا التعليق التوضيحي في خاصية الاستبعاد لكل من التعليقات التوضيحية @Relation و @Embedded.
| ملكية | مطلوب | محتوى | لغة التعبير |
|---|---|---|---|
| المجموعات | لا | صفيف | لا |
| منذ الإصدار | لا | خيط | لا |
| untilVersion | لا | خيط | لا |
| أقصى عمق | لا | عدد صحيح | لا |
| استبعادإذا | لا | سلسلة / منطقية | نعم |
تعمل جميع القيم، باستثناء excludeIf بنفس الطريقة التي يتم بها استخدامها مباشرةً على الخصائص العادية باستخدام المُسلسِل.
excludeIf يتوقع قيمة منطقية ويكون مفيدًا عندما يفشل تعبير آخر في بعض الظروف. في هذا المثال، إذا كانت طريقة getManager null ، فيجب عليك استبعادها لمنع فشل إنشاء عنوان URL:
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* "manager" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = { "id" = "expr(object.getManager().getId())" }
* ),
* exclusion = @ Hateoas Exclusion ( excludeIf = "expr(null === object.getManager())" )
* )
* /
class User
{
public function getId () {}
/ * *
* @ return User | null
* /
public function getManager () {}
} use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' manager ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [ ' id ' => ' expr(object.getManager().getId()) ' ],
),
exclusion: new Hateoas Exclusion (excludeIf: ' expr(null === object.getManager()) ' )
)]
class User
{
public function getId () {}
public function getManager (): ? User {}
}يمكن تعريف هذا التعليق التوضيحي في الفصل الدراسي. يكون ذلك مفيدًا إذا كنت ترغب في إجراء تسلسل للعلاقات (الروابط) المتعددة. كمثال:
{
"_links": {
"relation_name": [
{"href": "link1"},
{"href": "link2"},
{"href": "link3"}
]
}
}
| ملكية | مطلوب | محتوى | لغة التعبير |
|---|---|---|---|
| اسم | نعم | خيط | نعم |
يمكن أن يكون "الاسم":
my_funcMyClass::getExtraRelationsexpr(service('user.rel_provider').getExtraRelations())هنا ومثال باستخدام لغة التعبير:
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas RelationProvider ( "expr(service('user.rel_provider').getExtraRelations())" )
* /
class User
{
...
} use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas RelationProvider( " expr(service('user.rel_provider').getExtraRelations()) " )]
class User
{
...
} هنا فئة UserRelPrvider :
use Hateoas Configuration Relation ;
use Hateoas Configuration Route ;
class UserRelPrvider
{
private $ evaluator ;
public function __construct ( CompilableExpressionEvaluatorInterface $ evaluator )
{
$ this -> evaluator = $ evaluator ;
}
/ * *
* @ return Relation []
* /
public function getExtraRelations (): array
{
// You need to return the relations
return array (
new Relation (
' self ' ,
new Route (
' foo_get ' ,
[ ' id ' => $ this -> evaluator -> parse ( ' object.getId() ' , [ ' object ' ])]
)
)
);
}
} يتم استخدام $this->evaluator الذي ينفذ CompilableExpressionEvaluatorInterface لتحليل لغة التعبير في نموذج يمكن تخزينه مؤقتًا وحفظه لاستخدامه لاحقًا. إذا كنت لا تحتاج إلى لغة التعبير في علاقاتك، فلا حاجة لهذه الخدمة.
يتم تعريف خدمة user.rel_provider على النحو التالي:
user.rel_provider :
class : UserRelPrvider
arguments :
- ' @jms_serializer.expression_evaluator ' في هذه الحالة، تعد jms_serializer.expression_evaluator خدمة تطبق CompilableExpressionEvaluatorInterface .
يشير هذا القسم إلى الأجزاء الداخلية لـ Hateoas، ويقدم وثائق حول الأجزاء المخفية من هذه المكتبة. لا يكون هذا مناسبًا دائمًا للمستخدمين النهائيين، ولكنه مثير للاهتمام للمطورين أو الأشخاص المهتمين بمعرفة كيفية عمل الأشياء تحت الغطاء.
willdurand/hateoas يتبع الإصدار الدلالي.
اعتبارًا من أكتوبر 2013، لم يعد الإصداران 1.x و 0.x مدعومين رسميًا (لاحظ أنه لم يتم إصدار 1.x مطلقًا).
الإصدار 3.x هو الإصدار المستقر الرئيسي الحالي.
يتم الحفاظ على الإصدار 2.x فقط لإصلاح الأخطاء الأمنية والمشكلات الرئيسية التي قد تحدث.
انظر ملف المساهمة.
تثبيت تبعيات dev Composer:
php composer.phar install --dev
ثم قم بتشغيل مجموعة الاختبار باستخدام PHPUnit:
bin/phpunit
تم إصدار Hateoas بموجب ترخيص MIT. راجع ملف الترخيص المرفق للحصول على التفاصيل.