neomerx/json-api

repository·master·Indexed 20 days ago

https://github.com/neomerx/json-api

A framework-agnostic PHP implementation of the JSON API v1.1 specification. It provides tools for resource management, document structure, HTTP compliance (RFC 7231), and query parameter parsing. The library includes an Encoder for converting domain objects to JSON API compliant strings via custom resource schemas, as well as specialized handling for errors, metadata, and media types.

Tokens
8K
Snippets
26
Records
33
Agent score
73%

What's inside neomerx/json-api

  1. Overview of neomerx/json-api

    master

    The neomerx/json-api package is a framework-agnostic PHP implementation of the JSON API specification version v1.1. It helps developers focus on core application logic by handling the complexities of the protocol, including:

    • Resource Management: Handling attributes, relationships, and polymorphic resource data.
    • Document Structure: Supporting compound documents with included related resources (including circular references) and meta information.
    • HTTP Compliance: Parsing Accept and Content-Type headers (RFC 7231) and automatically responding with 415 Unsupported Media Type or 406 Not Acceptable for invalid requests.
    • Query Parameters: Parsing pagination, sorting, sparse fieldsets, and customized included paths.
    • Error Handling: Standardized error responses according to the specification.

    It is production-ready with 100% test coverage.

  2. Set up the performance test suite

    master

    The performance test suite uses Blackfire for profiling. To set it up, ensure you have Docker and Docker Compose installed, then configure your Blackfire credentials.

    1. Prerequisites:

    2. Configuration:

      • Copy the sample environment file to the active environment file: cp blackfire.io.env.sample blackfire.io.env
      • Open blackfire.io.env and populate the following fields with your credentials from the Blackfire.io credentials page:
        • Client ID
        • Client Token
        • Server ID
        • Server Token
    cp blackfire.io.env.sample blackfire.io.env
  3. Profile performance with Blackfire

    master

    Run the performance profiling suite using Docker Compose. This command executes the sample script through Blackfire to generate performance metrics.

    Note: The first execution will be slow as it must download the necessary Docker images. Subsequent runs will be faster.

    To run the profile:

    docker-compose run --rm cli_php blackfire run php -d zend.assertions=-1 /app/sample/sample.php -t=100

    The output provides basic performance information and a URL that links to a detailed profiling graph on Blackfire.io.

  4. Run performance tests

    master

    The sample application includes built-in performance testing capabilities. You can run tests using the -t flag.

    Default execution: Run with default parameters:

    $ php sample.php -t

    Custom iterations: Run with a specific number of iterations (e.g., 10,000) and measure execution time using the system time command:

    $ time php sample.php -t=10000

    Docker-based testing: If you have docker-compose installed, you can run performance tests across different PHP versions (7.1, 7.2, 7.3, and 7.4) using the provided Composer scripts:

    $ composer perf-test-php-7-1
    $ composer perf-test-php-7-2
    $ composer perf-test-php-7-3
    $ composer perf-test-php-7-4
  5. Filter parsed relationships using paths

    master

    When calling Parser::parse($data, $paths), you can provide an array of strings to $paths to control which relationships are traversed.

    Paths are normalized using the DocumentInterface::PATH_SEPARATOR (typically .). If you provide a path like a.b.c, the parser internally treats it as requesting a, a.b, and a.b.c.

    A relationship is only parsed if:

    1. Its path is present in the requested $paths.
    2. The relationship contains data (it is not empty/null).

    This mechanism prevents the parser from unnecessarily traversing deep relationship trees that were not explicitly requested.

  6. How the Parser handles different data types

    master

    The Parser::parse() method determines how to process the input based on the type of $data provided and its corresponding schema:

    • Resource: If the data has a schema in the SchemaContainerInterface, it is parsed as a resource. The parser will then recursively parse its relationships if they are requested in the $paths argument.
    • Identifier: If the data is an instance of SchemaIdentifierInterface, it is parsed as an identifier.
    • Collection: If the data is an array or Traversable and contains items that have schemas or are identifiers, it is parsed as a collection of resources or identifiers.
    • Null: If the data is null, it is parsed as null document data.
    • Error: If no schema can be found for the provided data type, an InvalidArgumentException is thrown with the message: No Schema found for top-level resource ‘%s‘.
  7. Run PHP CLI environments via Docker Compose

    master

    The project provides a docker-compose.yml file in the sample/ directory to spin up various PHP CLI environments for development or testing. Each service maps the project root to /app inside the container and provides a TTY for interactive use.

    Available services include:

    • cli_7_1_php (PHP 7.1)
    • cli_7_2_php (PHP 7.2)
    • cli_7_3_php (PHP 7.3)
    • cli_7_4_php (PHP 7.4)
    cli_7_4_php:
      image: php:7.4-cli
      container_name: cli_php_7_4_json_api
      volumes:
        - ./..:/app
      working_dir: /app
      tty: true
  8. Run the JSON API sample application

    master

    Run the sample application using the PHP CLI. The application demonstrates various JSON API encoding scenarios, including:

    • Encoding a resource with no relationships.
    • Encoding a resource with included relationships.
    • Demonstrating sparse fieldset and filter usage.
    • Demonstrating top-level links and meta information usage.
    $ php sample.php
  9. Encode data to JSON API using Encoder

    master

    To convert a domain object into a JSON API compliant string, use the Encoder class. You must provide a mapping between your object classes and their corresponding BaseSchema implementations. You can also configure a URL prefix for generated links and pass standard PHP JSON encoding options.

    $encoder = Encoder::instance([
            Author::class => AuthorSchema::class,
        ])
        ->withUrlPrefix('http://example.com/api/v1')
        ->withEncodeOptions(JSON_PRETTY_PRINT);
    
    echo $encoder->encodeData($author) . PHP_EOL;
  10. Implement a custom resource schema

    master

    To define how a domain object is represented in JSON API, extend BaseSchema. You must implement methods to define the resource type, its unique identifier, its attributes, and its relationships.

    Key methods to implement:

    • getType(): Returns the string resource type (e.g., 'people').
    • getId($resource): Returns the unique identifier for the resource.
    • getAttributes($resource, ContextInterface $context): Returns an iterable of key-value pairs representing the resource's attributes.
    • getRelationships($resource, ContextInterface $context): Returns an iterable defining relationships. You can control link visibility using constants like self::RELATIONSHIP_LINKS_SELF and self::RELATIONSHIP_LINKS_RELATED.
    class AuthorSchema extends BaseSchema
    {
        public function getType(): string
        {
            return 'people';
        }
    
        public function getId($author): ?string
        {
            return $author->authorId;
        }
    
        public function getAttributes($author, ContextInterface $context): iterable
        {
            return [
                'first-name' => $author->firstName,
                'last-name'  => $author->lastName,
            ];
        }
    
        public function getRelationships($author, ContextInterface $context): iterable
        {
            return [
                'comments' => [
                    self::RELATIONSHIP_LINKS_SELF    => false,
                    self::RELATIONSHIP_LINKS_RELATED => true,
                ],
            ];
        }
    }