├── .editorconfig
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UPGRADE.md
├── composer.json
├── config
└── validation.php
├── helpers.php
├── phpunit.xml
├── src
├── AbstractContainer.php
├── Adapter
│ ├── AbstractResourceAdapter.php
│ └── HydratesAttributesTrait.php
├── Authorizer
│ ├── AbstractAuthorizer.php
│ └── ReadOnlyAuthorizer.php
├── Contracts
│ ├── Adapter
│ │ ├── HasManyAdapterInterface.php
│ │ ├── RelationshipAdapterInterface.php
│ │ └── ResourceAdapterInterface.php
│ ├── Authorizer
│ │ └── AuthorizerInterface.php
│ ├── ContainerInterface.php
│ ├── Document
│ │ └── MutableErrorInterface.php
│ ├── Encoder
│ │ └── SerializerInterface.php
│ ├── Exceptions
│ │ ├── ErrorIdAllocatorInterface.php
│ │ └── ExceptionParserInterface.php
│ ├── Factories
│ │ └── FactoryInterface.php
│ ├── Http
│ │ ├── Client
│ │ │ └── ClientInterface.php
│ │ ├── Requests
│ │ │ ├── InboundRequestInterface.php
│ │ │ └── RequestInterface.php
│ │ └── Responses
│ │ │ ├── ErrorResponseInterface.php
│ │ │ └── ResponseInterface.php
│ ├── Object
│ │ ├── DocumentInterface.php
│ │ ├── MetaMemberInterface.php
│ │ ├── RelationshipInterface.php
│ │ ├── RelationshipsInterface.php
│ │ ├── ResourceIdentifierCollectionInterface.php
│ │ ├── ResourceIdentifierInterface.php
│ │ ├── ResourceObjectCollectionInterface.php
│ │ └── ResourceObjectInterface.php
│ ├── Pagination
│ │ └── PageInterface.php
│ ├── Repositories
│ │ ├── CodecMatcherRepositoryInterface.php
│ │ ├── ErrorRepositoryInterface.php
│ │ └── SchemasRepositoryInterface.php
│ ├── Resolver
│ │ └── ResolverInterface.php
│ ├── Store
│ │ ├── AdapterInterface.php
│ │ ├── StoreAwareInterface.php
│ │ └── StoreInterface.php
│ ├── Utils
│ │ ├── ConfigurableInterface.php
│ │ ├── ErrorReporterInterface.php
│ │ ├── ErrorsAwareInterface.php
│ │ └── ReplacerInterface.php
│ └── Validators
│ │ ├── AcceptRelatedResourceInterface.php
│ │ ├── AttributesValidatorInterface.php
│ │ ├── DocumentValidatorInterface.php
│ │ ├── QueryValidatorInterface.php
│ │ ├── RelationshipValidatorInterface.php
│ │ ├── RelationshipsValidatorInterface.php
│ │ ├── ResourceValidatorInterface.php
│ │ ├── ValidatorErrorFactoryInterface.php
│ │ ├── ValidatorFactoryInterface.php
│ │ └── ValidatorProviderInterface.php
├── Document
│ └── Error.php
├── Encoder
│ └── Encoder.php
├── Exceptions
│ ├── AuthorizationException.php
│ ├── DocumentRequiredException.php
│ ├── InvalidArgumentException.php
│ ├── InvalidJsonException.php
│ ├── MutableErrorCollection.php
│ ├── RecordNotFoundException.php
│ ├── RuntimeException.php
│ └── ValidationException.php
├── Factories
│ └── Factory.php
├── Http
│ ├── Client
│ │ ├── GuzzleClient.php
│ │ └── SendsRequestsTrait.php
│ ├── Middleware
│ │ ├── AuthorizesRequests.php
│ │ ├── NegotiatesContent.php
│ │ ├── ParsesServerRequests.php
│ │ └── ValidatesRequests.php
│ ├── Query
│ │ ├── ChecksQueryParameters.php
│ │ └── ValidationQueryChecker.php
│ ├── Requests
│ │ └── InboundRequest.php
│ └── Responses
│ │ ├── AbstractResponses.php
│ │ ├── ErrorResponse.php
│ │ └── Response.php
├── Object
│ ├── Document.php
│ ├── IdentifiableTrait.php
│ ├── MetaMemberTrait.php
│ ├── Relationship.php
│ ├── Relationships.php
│ ├── ResourceIdentifier.php
│ ├── ResourceIdentifierCollection.php
│ ├── ResourceObject.php
│ └── ResourceObjectCollection.php
├── Pagination
│ └── Page.php
├── Repositories
│ ├── CodecMatcherRepository.php
│ ├── ErrorRepository.php
│ └── SchemasRepository.php
├── Resolver
│ └── NamespaceResolver.php
├── Schema
│ └── ExtractsAttributesTrait.php
├── Store
│ ├── IdentityMap.php
│ ├── Store.php
│ └── StoreAwareTrait.php
├── Utils
│ ├── ErrorCreatorTrait.php
│ ├── ErrorsAwareTrait.php
│ ├── Http.php
│ ├── Pointer.php
│ ├── Replacer.php
│ └── Str.php
└── Validators
│ ├── AbstractRelationshipValidator.php
│ ├── AcceptImmutableRelationship.php
│ ├── AcceptRelatedResourceCallback.php
│ ├── HasManyValidator.php
│ ├── HasOneValidator.php
│ ├── RelationshipDocumentValidator.php
│ ├── RelationshipValidator.php
│ ├── RelationshipsValidator.php
│ ├── ResourceDocumentValidator.php
│ ├── ResourceValidator.php
│ ├── ValidatorErrorFactory.php
│ └── ValidatorFactory.php
└── tests
├── AbstractContainerTest.php
├── Adapter
├── AbstractResourceAdapterTest.php
└── TestAdapter.php
├── Authorizer
└── ReadOnlyAuthorizerTest.php
├── Document
└── ErrorTest.php
├── Encoder
└── EncoderTest.php
├── Exceptions
├── AuthorizationExceptionTest.php
├── MutableErrorCollectionTest.php
└── ValidationExceptionTest.php
├── HelpersTest.php
├── Http
├── Client
│ └── GuzzleClientTest.php
├── Middleware
│ └── ValidatesRequestsTest.php
├── Requests
│ └── InboundRequestTest.php
└── Responses
│ └── ErrorResponseTest.php
├── Object
├── DocumentTest.php
├── RelationshipTest.php
├── RelationshipsTest.php
├── ResourceIdentifierCollectionTest.php
├── ResourceIdentifierTest.php
├── ResourceObjectCollectionTest.php
└── ResourceObjectTest.php
├── Repositories
├── CodecMatcherRepositoryTest.php
└── SchemasRepositoryTest.php
├── Resolver
└── NamespaceResolverTest.php
├── Schema
├── SchemaTest.php
└── TestSchema.php
├── Store
└── StoreTest.php
├── TestCase.php
├── Utils
├── ErrorCreatorTraitTest.php
└── StrTest.php
└── Validators
├── HasManyDocumentValidatorTest.php
├── HasOneDocumentValidatorTest.php
├── ResourceDocumentValidatorTest.php
├── TestCase.php
└── TestContextValidator.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | indent_size = 4
10 |
11 | [*.{md,yml}]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | vendor/
3 | build/
4 | composer.lock
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - '5.6'
5 | - '7.0'
6 | - '7.1'
7 | - '7.2'
8 |
9 | before_script:
10 | - travis_retry composer install --no-interaction --prefer-dist
11 |
12 | script:
13 | - vendor/bin/phpunit
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/cloudcreativity/json-api)
2 |
3 | # DEPRECATED: cloudcreativity/json-api
4 |
5 | This package has been deprecated and is no longer supported. It was originally built as a framework agnostic
6 | extension of the [neomerx/json-api](https://github.com/neomerx/json-api) package. We built it in a framework
7 | agnostic way as we were using it in both Laravel and Zend 2 at the time. However, we now only use it in
8 | Laravel and have therefore deprecated this package to focus on our Laravel package.
9 |
10 | If you need to use JSON API in a Laravel application, check out our
11 | [cloudcreativity/laravel-json-api](https://github.com/cloudcreativity/laravel-json-api) package.
12 |
13 | ### License
14 |
15 | Apache License (Version 2.0). Please see [License File](LICENSE) for more information.
16 |
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cloudcreativity/json-api",
3 | "description": "Framework agnostic JSON API serialization and deserialization.",
4 | "keywords": [
5 | "jsonapi.org",
6 | "json-api",
7 | "jsonapi",
8 | "cloudcreativity",
9 | "json",
10 | "api"
11 | ],
12 | "homepage": "https://github.com/cloudcreativity/json-api",
13 | "support": {
14 | "issues": "https://github.com/cloudcreativity/json-api/issues"
15 | },
16 | "license": "Apache-2.0",
17 | "authors": [
18 | {
19 | "name": "Cloud Creativity Ltd",
20 | "email": "info@cloudcreativity.co.uk"
21 | }
22 | ],
23 | "require": {
24 | "php": "^5.6.4|^7.0",
25 | "cloudcreativity/utils-object": "^1.0",
26 | "neomerx/json-api": "^1.0.3"
27 | },
28 | "require-dev": {
29 | "guzzlehttp/guzzle": "^6.3",
30 | "phpunit/phpunit": "^5.7"
31 | },
32 | "suggest": {
33 | "guzzlehttp/guzzle": "Guzzle v6 required to use the JSON API client."
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "CloudCreativity\\JsonApi\\": "src/"
38 | },
39 | "files": [
40 | "helpers.php"
41 | ]
42 | },
43 | "autoload-dev": {
44 | "psr-4": {
45 | "CloudCreativity\\JsonApi\\": "tests/"
46 | }
47 | },
48 | "extra": {
49 | "branch-alias": {
50 | "dev-develop": "1.x-dev"
51 | }
52 | },
53 | "minimum-stability": "stable",
54 | "prefer-stable": true,
55 | "config": {
56 | "sort-packages": true
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/helpers.php:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | ./tests/
8 |
9 |
10 |
11 |
12 | src/
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Authorizer/AbstractAuthorizer.php:
--------------------------------------------------------------------------------
1 | errorRepository = $errorRepository;
49 | }
50 |
51 | /**
52 | * @inheritdoc
53 | */
54 | public function canReadRelatedResource($relationshipKey, $record, EncodingParametersInterface $parameters)
55 | {
56 | return $this->canRead($record, $parameters);
57 | }
58 |
59 | /**
60 | * @inheritdoc
61 | */
62 | public function canReadRelationship($relationshipKey, $record, EncodingParametersInterface $parameters)
63 | {
64 | return $this->canReadRelatedResource($relationshipKey, $record, $parameters);
65 | }
66 |
67 | /**
68 | * @inheritdoc
69 | */
70 | protected function getErrorRepository()
71 | {
72 | return $this->errorRepository;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/Authorizer/ReadOnlyAuthorizer.php:
--------------------------------------------------------------------------------
1 | ["1", "2"],
94 | * "bar" => ["99"]
95 | * ]
96 | * ```
97 | *
98 | * If the method call is provided with the an array `['foo' => 'FooModel', 'bar' => 'FoobarModel']`, then the
99 | * returned mapped array will be:
100 | *
101 | * ```
102 | * [
103 | * "FooModel" => ["1", "2"],
104 | * "FoobarModel" => ["99"]
105 | * ]
106 | * ```
107 | *
108 | * @param string[]|null $typeMap
109 | * if an array, map the identifier types to the supplied types.
110 | * @return mixed
111 | */
112 | public function map(array $typeMap = null);
113 | }
114 |
--------------------------------------------------------------------------------
/src/Contracts/Object/ResourceIdentifierInterface.php:
--------------------------------------------------------------------------------
1 | getHttpStatus($defaultHttpCode), $previous);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Exceptions/DocumentRequiredException.php:
--------------------------------------------------------------------------------
1 | jsonError = $jsonError;
68 | $this->jsonErrorMessage = $jsonErrorMessage;
69 | }
70 |
71 | /**
72 | * @return int|null
73 | */
74 | public function getJsonError()
75 | {
76 | return $this->jsonError;
77 | }
78 |
79 | /**
80 | * @return string|null
81 | */
82 | public function getJsonErrorMessage()
83 | {
84 | return $this->jsonErrorMessage;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Exceptions/RecordNotFoundException.php:
--------------------------------------------------------------------------------
1 | getType(), $identifier->getId());
50 | parent::__construct($message, $code, $previous);
51 | $this->identifier = $identifier;
52 | }
53 |
54 | /**
55 | * @return ResourceIdentifierInterface
56 | */
57 | public function getIdentifier()
58 | {
59 | return $this->identifier;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Exceptions/RuntimeException.php:
--------------------------------------------------------------------------------
1 | getHttpStatus($defaultHttpCode), $previous);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Http/Middleware/NegotiatesContent.php:
--------------------------------------------------------------------------------
1 | createHeaderParametersParser();
50 | $checker = $httpFactory->createHeadersChecker($codecMatcher);
51 |
52 | $checker->checkHeaders($parser->parse($request, http_contains_body($request)));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Http/Query/ValidationQueryChecker.php:
--------------------------------------------------------------------------------
1 | queryChecker = $checker;
53 | $this->queryValidator = $validator;
54 | }
55 |
56 | /**
57 | * @param EncodingParametersInterface $parameters
58 | */
59 | public function checkQuery(EncodingParametersInterface $parameters)
60 | {
61 | $this->queryChecker->checkQuery($parameters);
62 |
63 | if ($this->queryValidator) {
64 | $this->validateQuery($parameters);
65 | }
66 | }
67 |
68 | /**
69 | * @param EncodingParametersInterface $parameters
70 | * @return void
71 | */
72 | protected function validateQuery(EncodingParametersInterface $parameters)
73 | {
74 | if (!$this->queryValidator->isValid($parameters)) {
75 | throw new ValidationException(
76 | $this->queryValidator->getErrors(),
77 | ValidationException::HTTP_CODE_BAD_REQUEST
78 | );
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Http/Responses/ErrorResponse.php:
--------------------------------------------------------------------------------
1 | errors = Errors::cast($errors);
59 | $this->defaultHttpCode = $defaultHttpCode;
60 | $this->headers = $headers;
61 | }
62 |
63 | /**
64 | * @inheritdoc
65 | */
66 | public function getErrors()
67 | {
68 | return $this->errors;
69 | }
70 |
71 | /**
72 | * @inheritdoc
73 | */
74 | public function getHttpCode()
75 | {
76 | return $this->errors->getHttpStatus($this->defaultHttpCode);
77 | }
78 |
79 | /**
80 | * @inheritdoc
81 | */
82 | public function getHeaders()
83 | {
84 | return $this->headers;
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/Http/Responses/Response.php:
--------------------------------------------------------------------------------
1 | response = $response;
52 | $this->document = $document;
53 | }
54 |
55 | /**
56 | * @inheritdoc
57 | */
58 | public function getPsrResponse()
59 | {
60 | return $this->response;
61 | }
62 |
63 | /**
64 | * @inheritdoc
65 | */
66 | public function getDocument()
67 | {
68 | return $this->document;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Object/Document.php:
--------------------------------------------------------------------------------
1 | has(self::DATA)) {
43 | throw new RuntimeException('Data member is not present.');
44 | }
45 |
46 | $data = $this->get(self::DATA);
47 |
48 | if (is_array($data) || is_null($data)) {
49 | return $data;
50 | }
51 |
52 | if ($data instanceof StandardObjectInterface) {
53 | throw new RuntimeException('Data member is not an object or null.');
54 | }
55 |
56 | return $data;
57 | }
58 |
59 | /**
60 | * @inheritdoc
61 | */
62 | public function getResource()
63 | {
64 | $data = $this->{self::DATA};
65 |
66 | if (!is_object($data)) {
67 | throw new RuntimeException('Data member is not an object.');
68 | }
69 |
70 | return new ResourceObject($data);
71 | }
72 |
73 | /**
74 | * @inheritDoc
75 | */
76 | public function getResources()
77 | {
78 | $data = $this->get(self::DATA);
79 |
80 | if (!is_array($data)) {
81 | throw new RuntimeException('Data member is not an array.');
82 | }
83 |
84 | return ResourceObjectCollection::create($data);
85 | }
86 |
87 | /**
88 | * @inheritdoc
89 | */
90 | public function getRelationship()
91 | {
92 | return new Relationship($this->proxy);
93 | }
94 |
95 | /**
96 | * @inheritDoc
97 | */
98 | public function getIncluded()
99 | {
100 | if (!$this->has(self::INCLUDED)) {
101 | return null;
102 | }
103 |
104 | if (!is_array($data = $this->{self::INCLUDED})) {
105 | throw new RuntimeException('Included member is not an array.');
106 | }
107 |
108 | return ResourceObjectCollection::create($data);
109 | }
110 |
111 | /**
112 | * @inheritDoc
113 | */
114 | public function getErrors()
115 | {
116 | if (!$this->has(self::ERRORS)) {
117 | return null;
118 | }
119 |
120 | if (!is_array($data = $this->{self::ERRORS})) {
121 | throw new RuntimeException('Errors member is not an array.');
122 | }
123 |
124 | return Error::createMany($data);
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/Object/IdentifiableTrait.php:
--------------------------------------------------------------------------------
1 | has(DocumentInterface::KEYWORD_TYPE)) {
40 | throw new RuntimeException('Type member not present.');
41 | }
42 |
43 | $type = $this->get(DocumentInterface::KEYWORD_TYPE);
44 |
45 | if (!is_string($type) || empty($type)) {
46 | throw new RuntimeException('Type member is not a string, or is empty.');
47 | }
48 |
49 | return $type;
50 | }
51 |
52 | /**
53 | * @return bool
54 | */
55 | public function hasType()
56 | {
57 | return $this->has(DocumentInterface::KEYWORD_TYPE);
58 | }
59 |
60 | /**
61 | * @return string|int
62 | * @throws RuntimeException
63 | * if the id member is not present, or is not a string/int, or is an empty string.
64 | */
65 | public function getId()
66 | {
67 | if (!$this->has(DocumentInterface::KEYWORD_ID)) {
68 | throw new RuntimeException('Id member not present.');
69 | }
70 |
71 | $id = $this->get(DocumentInterface::KEYWORD_ID);
72 |
73 | if (!is_string($id)) {
74 | throw new RuntimeException('Id member is not a string.');
75 | }
76 |
77 | if (empty($id)) {
78 | throw new RuntimeException('Id member is an empty string.');
79 | }
80 |
81 | return $id;
82 | }
83 |
84 | /**
85 | * @return bool
86 | */
87 | public function hasId()
88 | {
89 | return $this->has(DocumentInterface::KEYWORD_ID);
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/Object/MetaMemberTrait.php:
--------------------------------------------------------------------------------
1 | hasMeta() ? $this->get(DocumentInterface::KEYWORD_META) : new StandardObject();
44 |
45 | if (!is_null($meta) && !$meta instanceof StandardObjectInterface) {
46 | throw new RuntimeException('Data member is not an object.');
47 | }
48 |
49 | return $meta;
50 | }
51 |
52 | /**
53 | * @return bool
54 | */
55 | public function hasMeta()
56 | {
57 | return $this->has(DocumentInterface::KEYWORD_META);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/Object/Relationship.php:
--------------------------------------------------------------------------------
1 | isHasMany()) {
41 | return $this->getIdentifiers();
42 | } elseif (!$this->isHasOne()) {
43 | throw new RuntimeException('No data member or data member is not a valid relationship.');
44 | }
45 |
46 | return $this->hasIdentifier() ? $this->getIdentifier() : null;
47 | }
48 |
49 |
50 | /**
51 | * @inheritdoc
52 | */
53 | public function getIdentifier()
54 | {
55 | if (!$this->isHasOne()) {
56 | throw new RuntimeException('No data member or data member is not a valid has-one relationship.');
57 | }
58 |
59 | $data = $this->{self::DATA};
60 |
61 | if (!$data) {
62 | throw new RuntimeException('No resource identifier - relationship is empty.');
63 | }
64 |
65 | return new ResourceIdentifier($data);
66 | }
67 |
68 | /**
69 | * @inheritdoc
70 | */
71 | public function hasIdentifier()
72 | {
73 | return is_object($this->{self::DATA});
74 | }
75 |
76 | /**
77 | * @inheritdoc
78 | */
79 | public function isHasOne()
80 | {
81 | if (!$this->has(self::DATA)) {
82 | return false;
83 | }
84 |
85 | $data = $this->{self::DATA};
86 |
87 | return is_null($data) || is_object($data);
88 | }
89 |
90 | /**
91 | * @inheritdoc
92 | */
93 | public function getIdentifiers()
94 | {
95 | if (!$this->isHasMany()) {
96 | throw new RuntimeException('No data member of data member is not a valid has-many relationship.');
97 | }
98 |
99 | return ResourceIdentifierCollection::create($this->{self::DATA});
100 | }
101 |
102 | /**
103 | * @inheritdoc
104 | */
105 | public function isHasMany()
106 | {
107 | return is_array($this->{self::DATA});
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Object/Relationships.php:
--------------------------------------------------------------------------------
1 | keys() as $key) {
39 | yield $key => $this->getRelationship($key);
40 | }
41 | }
42 |
43 | /**
44 | * @inheritdoc
45 | */
46 | public function getRelationship($key)
47 | {
48 | if (!$this->has($key)) {
49 | throw new RuntimeException("Relationship member '$key' is not present.");
50 | }
51 |
52 | $value = $this->{$key};
53 |
54 | if (!is_object($value)) {
55 | throw new RuntimeException("Relationship member '$key' is not an object.'");
56 | }
57 |
58 | return new Relationship($value);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/Object/ResourceIdentifier.php:
--------------------------------------------------------------------------------
1 | set(self::TYPE, $type)
46 | ->set(self::ID, $id);
47 |
48 | return $identifier;
49 | }
50 |
51 | /**
52 | * @inheritDoc
53 | */
54 | public function isType($typeOrTypes)
55 | {
56 | return in_array($this->get(self::TYPE), (array) $typeOrTypes, true);
57 | }
58 |
59 | /**
60 | * @inheritDoc
61 | */
62 | public function mapType(array $map)
63 | {
64 | $type = $this->getType();
65 |
66 | if (array_key_exists($type, $map)) {
67 | return $map[$type];
68 | }
69 |
70 | throw new RuntimeException(sprintf('Type "%s" is not in the supplied map.', $type));
71 | }
72 |
73 | /**
74 | * @inheritDoc
75 | */
76 | public function isComplete()
77 | {
78 | return $this->hasType() && $this->hasId();
79 | }
80 |
81 | /**
82 | * @inheritDoc
83 | */
84 | public function isSame(ResourceIdentifierInterface $identifier)
85 | {
86 | return $this->getType() === $identifier->getType() &&
87 | $this->getId() === $identifier->getId();
88 | }
89 |
90 | /**
91 | * @inheritDoc
92 | */
93 | public function toString()
94 | {
95 | return sprintf('%s:%s', $this->getType(), $this->getId());
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/Object/ResourceObject.php:
--------------------------------------------------------------------------------
1 | getType(), $this->getId());
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function getAttributes()
50 | {
51 | $attributes = $this->hasAttributes() ? $this->get(self::ATTRIBUTES) : new StandardObject();
52 |
53 | if (!$attributes instanceof StandardObjectInterface) {
54 | throw new RuntimeException('Attributes member is not an object.');
55 | }
56 |
57 | return $attributes;
58 | }
59 |
60 | /**
61 | * @inheritdoc
62 | */
63 | public function hasAttributes()
64 | {
65 | return $this->has(self::ATTRIBUTES);
66 | }
67 |
68 | /**
69 | * @inheritdoc
70 | */
71 | public function getRelationships()
72 | {
73 | $relationships = $this->hasRelationships() ? $this->{self::RELATIONSHIPS} : null;
74 |
75 | if (!is_null($relationships) && !is_object($relationships)) {
76 | throw new RuntimeException('Relationships member is not an object.');
77 | }
78 |
79 | return new Relationships($relationships);
80 | }
81 |
82 | /**
83 | * @inheritdoc
84 | */
85 | public function hasRelationships()
86 | {
87 | return $this->has(self::RELATIONSHIPS);
88 | }
89 |
90 | /**
91 | * @inheritDoc
92 | */
93 | public function getRelationship($key)
94 | {
95 | $relationships = $this->getRelationships();
96 |
97 | return $relationships->has($key) ? $relationships->getRelationship($key) : null;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/Pagination/Page.php:
--------------------------------------------------------------------------------
1 | data = $data;
88 | $this->first = $first;
89 | $this->previous = $previous;
90 | $this->next = $next;
91 | $this->last = $last;
92 | $this->meta = $meta;
93 | $this->metaKey = $metaKey;
94 | }
95 |
96 | /**
97 | * @inheritDoc
98 | */
99 | public function getData()
100 | {
101 | return $this->data;
102 | }
103 |
104 | /**
105 | * @inheritDoc
106 | */
107 | public function getFirstLink()
108 | {
109 | return $this->first;
110 | }
111 |
112 | /**
113 | * @inheritDoc
114 | */
115 | public function getPreviousLink()
116 | {
117 | return $this->previous;
118 | }
119 |
120 | /**
121 | * @inheritDoc
122 | */
123 | public function getNextLink()
124 | {
125 | return $this->next;
126 | }
127 |
128 | /**
129 | * @inheritDoc
130 | */
131 | public function getLastLink()
132 | {
133 | return $this->last;
134 | }
135 |
136 | /**
137 | * @inheritDoc
138 | */
139 | public function getMeta()
140 | {
141 | return $this->meta;
142 | }
143 |
144 | /**
145 | * @inheritDoc
146 | */
147 | public function getMetaKey()
148 | {
149 | return $this->metaKey;
150 | }
151 |
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/src/Repositories/ErrorRepository.php:
--------------------------------------------------------------------------------
1 | replacer = $replacer;
53 | }
54 |
55 | /**
56 | * Add error configuration.
57 | *
58 | * @param array $config
59 | * @return $this
60 | */
61 | public function configure(array $config)
62 | {
63 | $this->errors = array_merge($this->errors, $config);
64 |
65 | return $this;
66 | }
67 |
68 | /**
69 | * @inheritdoc
70 | */
71 | public function exists($key)
72 | {
73 | return isset($this->errors[$key]);
74 | }
75 |
76 | /**
77 | * @inheritDoc
78 | */
79 | public function errors(...$keys)
80 | {
81 | $errors = new MutableErrorCollection();
82 |
83 | foreach ($keys as $error) {
84 | if (is_string($error)) {
85 | $error = $this->error($error);
86 | }
87 |
88 | $errors->add($error);
89 | }
90 |
91 | return $errors;
92 | }
93 |
94 |
95 | /**
96 | * @inheritdoc
97 | */
98 | public function error($key, array $values = [])
99 | {
100 | return $this->make($key, $values);
101 | }
102 |
103 | /**
104 | * @inheritdoc
105 | */
106 | public function errorWithPointer($key, $pointer, array $values = [])
107 | {
108 | $error = $this->make($key, $values);
109 | $error->setSourcePointer($pointer);
110 |
111 | return $error;
112 | }
113 |
114 | /**
115 | * @inheritdoc
116 | */
117 | public function errorWithParameter($key, $parameter, array $values = [])
118 | {
119 | $error = $this->make($key, $values);
120 | $error->setSourceParameter($parameter);
121 |
122 | return $error;
123 | }
124 |
125 | /**
126 | * @param $key
127 | * @return array
128 | */
129 | protected function get($key)
130 | {
131 | return isset($this->errors[$key]) ? (array) $this->errors[$key] : [];
132 | }
133 |
134 | /**
135 | * @param $key
136 | * @param array $values
137 | * @return Error
138 | */
139 | protected function make($key, array $values)
140 | {
141 | $error = Error::create($this->get($key));
142 |
143 | if ($this->replacer && $error->hasDetail()) {
144 | $detail = $this->replacer->replace($error->getDetail(), $values);
145 | $error->setDetail($detail);
146 | }
147 |
148 | return $error;
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/Repositories/SchemasRepository.php:
--------------------------------------------------------------------------------
1 | [
37 | * 'Author' => 'AuthorSchema',
38 | * 'Post' => 'PostSchema',
39 | * ],
40 | * 'foo' => [
41 | * 'Comment' => 'CommentSchema',
42 | * ],
43 | * ]
44 | * ````
45 | *
46 | * If the 'foo' schema is requested, the return array will have Author, Schema and Comment in it.
47 | *
48 | * This repository also accepts non-namespaced schemas. I.e. if the config array does not have a 'defaults' key, it
49 | * will be loaded as the default schemas.
50 | */
51 | class SchemasRepository implements SchemasRepositoryInterface
52 | {
53 |
54 | /**
55 | * @var SchemaFactoryInterface
56 | */
57 | private $factory;
58 |
59 | /**
60 | * @var bool
61 | */
62 | private $namespaced = false;
63 |
64 | /**
65 | * @var array
66 | */
67 | private $schemas = [];
68 |
69 | /**
70 | * @param SchemaFactoryInterface|null $factory
71 | */
72 | public function __construct(SchemaFactoryInterface $factory = null)
73 | {
74 | $this->factory = $factory ?: new Factory();
75 | }
76 |
77 | /**
78 | * @param string $name
79 | * @return ContainerInterface
80 | */
81 | public function getSchemas($name = null)
82 | {
83 | $name = ($name) ?: static::DEFAULTS;
84 |
85 | if (static::DEFAULTS !== $name && !$this->namespaced) {
86 | throw new \RuntimeException(sprintf('Schemas configuration is not namespaced, so cannot get "%s".', $name));
87 | }
88 |
89 | $defaults = $this->get(static::DEFAULTS);
90 | $schemas = (static::DEFAULTS === $name) ? $defaults : array_merge($defaults, $this->get($name));
91 |
92 | return $this->factory->createContainer($schemas);
93 | }
94 |
95 | /**
96 | * @param array $config
97 | * @return $this
98 | */
99 | public function configure(array $config)
100 | {
101 | if (!isset($config[static::DEFAULTS])) {
102 | $config = [static::DEFAULTS => $config];
103 | $this->namespaced = false;
104 | } else {
105 | $this->namespaced = true;
106 | }
107 |
108 | $this->schemas = $config;
109 |
110 | return $this;
111 | }
112 |
113 | /**
114 | * @param $key
115 | * @return array
116 | */
117 | private function get($key)
118 | {
119 | return array_key_exists($key, $this->schemas) ? (array) $this->schemas[$key] : [];
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/Schema/ExtractsAttributesTrait.php:
--------------------------------------------------------------------------------
1 | attributeKeys($record) as $recordKey => $attributeKey) {
52 | if (is_numeric($recordKey)) {
53 | $recordKey = $attributeKey;
54 | $attributeKey = $this->keyForAttribute($attributeKey);
55 | }
56 |
57 | $value = $this->extractAttribute($record, $recordKey);
58 | $attributes[$attributeKey] = $this->serializeAttribute($value, $record, $recordKey);
59 | }
60 |
61 | return $attributes;
62 | }
63 |
64 | /**
65 | * Get a list of attributes that are to be extracted.
66 | *
67 | * @param object $record
68 | * @return array
69 | */
70 | protected function attributeKeys($record)
71 | {
72 | $keys = property_exists($this, 'attributes') ? $this->attributes : null;
73 |
74 | if (is_null($keys)) {
75 | return array_keys(get_object_vars($record));
76 | }
77 |
78 | return (array) $keys;
79 | }
80 |
81 | /**
82 | * Convert a record key into a resource attribute key.
83 | *
84 | * @param $recordKey
85 | * @return string
86 | */
87 | protected function keyForAttribute($recordKey)
88 | {
89 | $dasherized = property_exists($this, 'dasherize') ? $this->dasherize : true;
90 |
91 | return $dasherized ? Str::dasherize($recordKey) : $recordKey;
92 | }
93 |
94 | /**
95 | * @param $value
96 | * @param object $record
97 | * @param $recordKey
98 | * @return string
99 | */
100 | protected function serializeAttribute($value, $record, $recordKey)
101 | {
102 | if (method_exists($this, $method = $this->methodForSerializer($recordKey))) {
103 | $value = call_user_func([$this, $method], $value, $record);
104 | }
105 |
106 | if ($value instanceof DateTime) {
107 | $value = $this->serializeDateTime($value, $record);
108 | }
109 |
110 | return $value;
111 | }
112 |
113 | /**
114 | * @param DateTime $value
115 | * @param object $record
116 | * @return string
117 | */
118 | protected function serializeDateTime(DateTime $value, $record)
119 | {
120 | return $value->format($this->dateFormat());
121 | }
122 |
123 | /**
124 | * @return string
125 | */
126 | protected function dateFormat()
127 | {
128 | $format = property_exists($this, 'dateFormat') ? $this->dateFormat : null;
129 |
130 | return $format ?: DateTime::W3C;
131 | }
132 |
133 | /**
134 | * @param $recordKey
135 | * @return string
136 | */
137 | protected function methodForSerializer($recordKey)
138 | {
139 | return 'serialize' . Str::classify($recordKey) . 'Attribute';
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Store/IdentityMap.php:
--------------------------------------------------------------------------------
1 | lookup($identifier);
55 |
56 | if (is_object($existing) && is_bool($record)) {
57 | throw new InvalidArgumentException('Attempting to push a boolean into the map in place of an object.');
58 | }
59 |
60 | $this->map[$identifier->toString()] = $record;
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * Does the identity map know that ths supplied identifier exists?
67 | *
68 | * @param ResourceIdentifierInterface $identifier
69 | * @return bool|null
70 | * the answer, or null if the identity map does not know
71 | */
72 | public function exists(ResourceIdentifierInterface $identifier)
73 | {
74 | $record = $this->lookup($identifier);
75 |
76 | return is_object($record) ? true : $record;
77 | }
78 |
79 | /**
80 | * Get the record from the identity map.
81 | *
82 | * @param ResourceIdentifierInterface $identifier
83 | * @return object|bool|null
84 | * the record, false if it is known not to exist, or null if the identity map does not have the object.
85 | */
86 | public function find(ResourceIdentifierInterface $identifier)
87 | {
88 | $record = $this->lookup($identifier);
89 |
90 | if (false === $record) {
91 | return false;
92 | }
93 |
94 | return is_object($record) ? $record : null;
95 | }
96 |
97 | /**
98 | * @param ResourceIdentifierInterface $identifier
99 | * @return object|bool|null
100 | */
101 | private function lookup(ResourceIdentifierInterface $identifier)
102 | {
103 | $key = $identifier->toString();
104 |
105 | return isset($this->map[$key]) ? $this->map[$key] : null;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Store/StoreAwareTrait.php:
--------------------------------------------------------------------------------
1 | store = $store;
43 | }
44 |
45 | /**
46 | * @return StoreInterface
47 | */
48 | protected function store()
49 | {
50 | if (!$this->store) {
51 | throw new RuntimeException('No store injected.');
52 | }
53 |
54 | return $this->store;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Utils/ErrorCreatorTrait.php:
--------------------------------------------------------------------------------
1 | errors instanceof Errors) {
51 | $this->errors = new Errors();
52 | }
53 |
54 | return $this->errors;
55 | }
56 |
57 | /**
58 | * @param ErrorInterface|string $error
59 | * @param array $values
60 | * @return MutableErrorInterface
61 | * the error that was added.
62 | */
63 | public function addError($error, array $values = [])
64 | {
65 | if ($error instanceof ErrorInterface) {
66 | $error = Error::cast($error);
67 | } else {
68 | $error = $this->getErrorRepository()->error($error, $values);
69 | }
70 |
71 | $this->getErrors()->add($error);
72 |
73 | return $error;
74 | }
75 |
76 | /**
77 | * @param $error
78 | * @param $pointer
79 | * @param array $values
80 | * @return MutableErrorInterface
81 | */
82 | public function addErrorWithPointer($error, $pointer, array $values = [])
83 | {
84 | $error = $this->addError($error, $values);
85 | $error->setSourcePointer($pointer);
86 |
87 | return $error;
88 | }
89 |
90 | /**
91 | * @param $error
92 | * @param $parameter
93 | * @param array $values
94 | * @return MutableErrorInterface
95 | */
96 | public function addErrorWithParameter($error, $parameter, array $values = [])
97 | {
98 | $error = $this->addError($error, $values);
99 | $error->setSourceParameter($parameter);
100 |
101 | return $error;
102 | }
103 |
104 | /**
105 | * Clear all errors
106 | *
107 | * @return $this
108 | */
109 | protected function reset()
110 | {
111 | $this->errors = null;
112 |
113 | return $this;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Utils/ErrorsAwareTrait.php:
--------------------------------------------------------------------------------
1 | errors instanceof Errors) {
44 | $this->errors = new Errors();
45 | }
46 |
47 | return $this->errors;
48 | }
49 |
50 | /**
51 | * @param ErrorInterface $error
52 | * @return $this
53 | */
54 | protected function addError(ErrorInterface $error)
55 | {
56 | $this->getErrors()->add($error);
57 |
58 | return $this;
59 | }
60 |
61 | /**
62 | * @param ErrorCollection|ErrorInterface[] $errors
63 | * @return $this
64 | */
65 | protected function addErrors($errors)
66 | {
67 | /** @var ErrorInterface $error */
68 | foreach ($errors as $error) {
69 | $this->getErrors()->add($error);
70 | }
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * @return $this
77 | */
78 | protected function reset()
79 | {
80 | $this->errors = null;
81 |
82 | return $this;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Utils/Http.php:
--------------------------------------------------------------------------------
1 | hasHeader('Transfer-Encoding')) {
48 | return true;
49 | };
50 |
51 | if (!$contentLength = $request->getHeader('Content-Length')) {
52 | return false;
53 | }
54 |
55 | return 0 < $contentLength[0];
56 | }
57 |
58 | /**
59 | * Does the HTTP response contain body content?
60 | *
61 | * "For response messages, whether or not a message-body is included with a message is dependent
62 | * on both the request method and the response status code (section 6.1.1). All responses to the
63 | * HEAD request method MUST NOT include a message-body, even though the presence of entity-header
64 | * fields might lead one to believe they do. All 1xx (informational), 204 (no content), and 304
65 | * (not modified) responses MUST NOT include a message-body. All other responses do include a
66 | * message-body, although it MAY be of zero length."
67 | * https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
68 | *
69 | * @param RequestInterface $request
70 | * @param ResponseInterface $response
71 | * @return bool
72 | */
73 | public static function doesResponseHaveBody(RequestInterface $request, ResponseInterface $response)
74 | {
75 | if ('HEAD' === strtoupper($request->getMethod())) {
76 | return false;
77 | }
78 |
79 | $status = $response->getStatusCode();
80 |
81 | if ((100 <= $status && 200 > $status) || 204 === $status || 304 === $status) {
82 | return false;
83 | }
84 |
85 | if ($response->hasHeader('Transfer-Encoding')) {
86 | return true;
87 | };
88 |
89 | if (!$contentLength = $response->getHeader('Content-Length')) {
90 | return false;
91 | }
92 |
93 | return 0 < $contentLength[0];
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/Utils/Replacer.php:
--------------------------------------------------------------------------------
1 | $value) {
44 | $message = str_replace(
45 | $this->parseKey($key),
46 | $this->parseValue($value),
47 | $message
48 | );
49 | }
50 |
51 | return $message;
52 | }
53 |
54 | /**
55 | * @param $key
56 | * @return string
57 | */
58 | protected function parseKey($key)
59 | {
60 | return sprintf('{%s}', $key);
61 | }
62 |
63 | /**
64 | * @param $value
65 | * @return string
66 | */
67 | protected function parseValue($value)
68 | {
69 | if (is_object($value)) {
70 | return '