├── .editorconfig
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── docker.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── build.xml
├── build
├── binary-phar-autoload.php.in
├── library-phar-autoload.php.in
├── phar-manifest.php
└── phar-version.php
├── composer.json
├── composer.lock
├── docs
└── man1
│ └── phpdraft.1
├── phpdraft
├── phpstan.neon.dist
├── sonar-project.properties
├── src
└── PHPDraft
│ ├── Core
│ └── Autoloader.php
│ ├── In
│ ├── ApibFileParser.php
│ └── Tests
│ │ └── ApibFileParserTest.php
│ ├── Model
│ ├── Category.php
│ ├── Comparable.php
│ ├── Elements
│ │ ├── ArrayStructureElement.php
│ │ ├── BasicStructureElement.php
│ │ ├── ElementStructureElement.php
│ │ ├── EnumStructureElement.php
│ │ ├── ObjectStructureElement.php
│ │ ├── RequestBodyElement.php
│ │ ├── StructureElement.php
│ │ └── Tests
│ │ │ ├── ArrayStructureElementTest.php
│ │ │ ├── BasicStructureElementTest.php
│ │ │ ├── ElementStructureElementTest.php
│ │ │ ├── EnumStructureElementTest.php
│ │ │ ├── ObjectStructureElementTest.php
│ │ │ └── RequestBodyElementTest.php
│ ├── HTTPRequest.php
│ ├── HTTPResponse.php
│ ├── HierarchyElement.php
│ ├── Resource.php
│ ├── Tests
│ │ ├── CategoryTest.php
│ │ ├── HTTPRequestTest.php
│ │ ├── HTTPResponseTest.php
│ │ ├── HierarchyElementChildTestBase.php
│ │ ├── HierarchyElementTest.php
│ │ ├── ObjectElementTest.php
│ │ ├── ResourceTest.php
│ │ └── TransitionTest.php
│ └── Transition.php
│ ├── Out
│ ├── BaseTemplateRenderer.php
│ ├── HTML
│ │ ├── default
│ │ │ ├── category.twig
│ │ │ ├── main.css
│ │ │ ├── main.js
│ │ │ ├── main.twig
│ │ │ ├── nav.twig
│ │ │ ├── resource.twig
│ │ │ ├── structure.twig
│ │ │ ├── transition.twig
│ │ │ └── value.twig
│ │ └── material
│ │ │ ├── main.css
│ │ │ ├── main.js
│ │ │ ├── main.twig
│ │ │ ├── nav.twig
│ │ │ ├── structure.twig
│ │ │ └── transition.twig
│ ├── HtmlTemplateRenderer.php
│ ├── OpenAPI
│ │ ├── OpenApiRenderer.php
│ │ └── Tests
│ │ │ └── OpenApiRendererTest.php
│ ├── Sorting.php
│ ├── Tests
│ │ ├── HtmlTemplateRendererTest.php
│ │ ├── SortingTest.php
│ │ ├── TwigFactoryTest.php
│ │ └── VersionTest.php
│ ├── TwigFactory.php
│ └── Version.php
│ └── Parse
│ ├── BaseHtmlGenerator.php
│ ├── BaseParser.php
│ ├── Drafter.php
│ ├── DrafterAPI.php
│ ├── ExecutionException.php
│ ├── HtmlGenerator.php
│ ├── ParserFactory.php
│ ├── ResourceException.php
│ └── Tests
│ ├── BaseParserTest.php
│ ├── DrafterAPITest.php
│ ├── DrafterTest.php
│ ├── HtmlGeneratorTest.php
│ └── ParserFactoryTest.php
└── tests
├── phpcs.xml
├── phpunit.xml
├── statics
├── basic_html_template
├── drafter
│ ├── apib
│ │ ├── errors.apib
│ │ ├── include.apib
│ │ ├── include.md
│ │ ├── including.apib
│ │ ├── index.apib
│ │ └── inheritance.apib
│ ├── help.txt
│ ├── html
│ │ ├── basic.html
│ │ ├── basic_old.html
│ │ ├── index.html
│ │ ├── inheritance.html
│ │ ├── material.html
│ │ └── material_old.html
│ └── json
│ │ ├── error.json
│ │ ├── index.json
│ │ └── inheritance.json
├── empty_html_template
├── full_html_template
├── full_test.apib
├── include_folders
│ ├── hello
│ │ └── hello.txt
│ └── templates
│ │ ├── test.txt
│ │ └── text
│ │ └── text.txt
├── include_single
│ └── hello.txt
└── openapi
│ └── empty.json
└── test.bootstrap.inc.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.html]
2 | indent_size = 4
3 | indent_style = space
4 |
5 | [*.php]
6 | indent_size = 4
7 | indent_style = space
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: smillerdev
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | # Check for updates to GitHub Actions every week
12 | interval: "weekly"
13 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Create and publish a Docker image
2 | on:
3 | push:
4 | branches: ['main']
5 | tags:
6 | - ".*"
7 | pull_request:
8 | paths:
9 | - Dockerfile
10 | - .github/workflows/docker.yml
11 | env:
12 | REGISTRY: ghcr.io
13 | IMAGE_NAME: ${{ github.repository }}
14 | jobs:
15 | build-and-push-image:
16 | runs-on: ubuntu-latest
17 | permissions:
18 | contents: read
19 | packages: write
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v4
23 | with:
24 | fetch-tags: true
25 | fetch-depth: 0
26 |
27 | - name: Log in to the Container registry
28 | uses: docker/login-action@master
29 | with:
30 | registry: ${{ env.REGISTRY }}
31 | username: ${{ github.actor }}
32 | password: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Extract metadata (tags, labels) for Drafter
35 | id: meta-drafter
36 | uses: docker/metadata-action@master
37 | with:
38 | images: ${{ env.REGISTRY }}/${{ github.repository }}/drafter
39 |
40 | - name: Build and push drafter Docker image
41 | uses: docker/build-push-action@master
42 | with:
43 | context: .
44 | push: true
45 | tags: ${{ steps.meta-drafter.outputs.tags }}
46 | labels: ${{ steps.meta-drafter.outputs.labels }}
47 | target: drafter
48 | no-cache-filters: drafter-build,drafter
49 | cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}/drafter:latest
50 |
51 | - name: Extract metadata (tags, labels) for PHPDraft
52 | id: meta
53 | uses: docker/metadata-action@master
54 | with:
55 | images: ${{ env.REGISTRY }}/${{ github.repository }}
56 |
57 | - name: Last tag
58 | id: tag-info
59 | run: |
60 | echo "latest=$(git describe --tags --always --abbrev=0)" >> "$GITHUB_OUTPUT"
61 |
62 | - name: Build and push PHPDraft Docker image
63 | uses: docker/build-push-action@master
64 | with:
65 | push: true
66 | tags: ${{ steps.meta.outputs.tags }}
67 | labels: ${{ steps.meta.outputs.labels }}
68 | target: phpdraft
69 | no-cache-filters: composer,phpdraft-build,phpdraft
70 | build-args: |
71 | BUILDKIT_CONTEXT_KEEP_GIT_DIR=true
72 | PHPDRAFT_RELEASE_ID=${{ steps.tag-info.outputs.latest }}
73 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: PHPDrafter release
2 |
3 | on:
4 | release:
5 | types: [created, edited]
6 |
7 | jobs:
8 | run:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 |
14 | - name: Setup PHP
15 | uses: shivammathur/setup-php@v2
16 | with:
17 | php-version: 8.1
18 | ini-values: assert.exception=1, phar.readonly=0, zend.assertions=1
19 | extensions: curl, json, phar, mbstring, gzip, bzip2, openssl
20 | tools: pecl, phing
21 | coverage: none
22 |
23 | - name: Get Composer Cache Directory
24 | id: composer-cache
25 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
26 |
27 | - name: Cache dependencies
28 | uses: actions/cache@v4
29 | with:
30 | path: ${{ steps.composer-cache.outputs.dir }}
31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
32 | restore-keys: ${{ runner.os }}-composer-
33 |
34 | - name: Validate composer.json and composer.lock
35 | run: composer validate
36 |
37 | - name: Install dependencies
38 | run: composer install --prefer-dist --no-progress --ignore-platform-reqs
39 |
40 | - name: Compile phar
41 | run: phing phar
42 |
43 | - name: Shasum builds
44 | run: sha256sum build/out/*
45 |
46 | - name: Upload binary to release
47 | uses: svenstaro/upload-release-action@2.9.0
48 | with:
49 | repo_token: ${{ secrets.GITHUB_TOKEN }}
50 | file: build/out/phpdraft-${{ github.event.release.tag_name }}.phar
51 | asset_name: phpdraft-${{ github.event.release.tag_name }}.phar
52 | tag: ${{ github.event.release.tag_name }}
53 | overwrite: false
54 |
55 | - name: Upload library to release
56 | uses: svenstaro/upload-release-action@2.9.0
57 | with:
58 | repo_token: ${{ secrets.GITHUB_TOKEN }}
59 | file: build/out/phpdraft-library-${{ github.event.release.tag_name }}.phar
60 | asset_name: phpdraft-library-${{ github.event.release.tag_name }}.phar
61 | tag: ${{ github.event.release.tag_name }}
62 | overwrite: false
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /.phpintel/
3 | /tests/.phpunit.cache/**
4 | /tests/.phpunit.result.cache
5 |
6 | # IntelliJ
7 | /build/coverage
8 | /build/logs
9 | /build/phar
10 | /build/tmp
11 | /build/out
12 | /build/*.phar
13 | /tests/statics/index.*
14 | src/.gitignore
15 | vendor/**
16 |
17 | # JIRA plugin
18 | atlassian-ide-plugin.xml
19 |
20 | *.pem
21 |
22 | /index.html
23 | /openapi.json
24 |
25 | /coverage.xml
26 | /event.json
27 | !/src/PHPDraft/Out/
28 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:bullseye-slim AS drafter-build
2 | RUN apt-get update && \
3 | apt-get install --yes curl ca-certificates
4 |
5 | RUN curl -L --fail -o drafter.tar.gz https://github.com/apiaryio/drafter/releases/download/v5.1.0/drafter-v5.1.0.tar.gz
6 | RUN install -d /usr/src/drafter
7 | RUN tar -xvf drafter.tar.gz --strip-components=1 --directory /usr/src/drafter
8 |
9 | WORKDIR /usr/src/drafter
10 |
11 | RUN apt-get install --yes cmake g++
12 |
13 | RUN cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
14 | RUN cmake --build build
15 | RUN cmake --install build
16 |
17 | FROM debian:bullseye-slim AS drafter
18 | COPY --from=drafter-build /usr/local/bin/drafter /usr/local/bin/drafter
19 |
20 | CMD drafter
21 |
22 | FROM composer:latest AS composer
23 |
24 | WORKDIR /usr/src/phpdraft
25 | COPY . /usr/src/phpdraft/
26 | RUN composer install --ignore-platform-req=ext-uopz
27 |
28 | FROM php:8.3-cli-bullseye AS phpdraft-build
29 |
30 | ARG PHPDRAFT_RELEASE_ID=0.0.0
31 |
32 | RUN echo $PHPDRAFT_RELEASE_ID
33 |
34 | COPY --from=composer /usr/src/phpdraft /usr/src/phpdraft
35 | WORKDIR /usr/src/phpdraft
36 |
37 | RUN echo "phar.readonly=0" >> /usr/local/etc/php/conf.d/phar.ini
38 |
39 | RUN php ./vendor/bin/phing phar-nightly
40 | RUN cp /usr/src/phpdraft/build/out/phpdraft-nightly.phar /usr/local/bin/phpdraft
41 |
42 | FROM php:8.3-cli-bullseye AS phpdraft
43 |
44 | LABEL maintainer="Sean Molenaar sean@seanmolenaar.eu"
45 |
46 | COPY --from=drafter-build /usr/local/bin/drafter /usr/local/bin/drafter
47 | COPY --from=phpdraft-build /usr/local/bin/phpdraft /usr/local/bin/phpdraft
48 |
49 | CMD phpdraft
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHPDraft [](https://github.com/SMillerDev/phpdraft/releases/latest) [](https://sonarcloud.io/dashboard?id=SMillerDev_phpdraft) [](https://codecov.io/gh/SMillerDev/phpdraft) [](https://app.fossa.com/projects/git%2Bgithub.com%2FSMillerDev%2Fphpdraft?ref=badge_shield)
2 |
3 | This is a parser for API Blueprint files in PHP.[1](#dependencies)
4 |
5 | ## Dependencies
6 | PHPDraft requires [drafter](https://github.com/apiaryio/drafter) to be installed. Refer to the drafter page for the installation details. If you don't want to install drafter, you can pass `-o` to the command to make it use [https://api.apiblueprint.org/parser](https://api.apiblueprint.org/parser)
7 |
8 | ## Usage
9 | Requires PHP 8.1+ to run. Unittests require runkit or uopz
10 | For direct usage you can run:
11 | ```bash
12 | $ ./phpdraft.phar -f blueprint-file.apib > blueprint-webpage.html
13 | ```
14 | You can also install it first:
15 | ```bash
16 | $ cp phpdraft.phar /usr/bin/phpdraft
17 | $ chmod +x /usr/bin/phpdraft
18 | $ phpdraft -f blueprint-file.apib > blueprint-webpage.html
19 | ```
20 |
21 | ## Extra features
22 | We got some fun stuff, check the [wiki](https://github.com/SMillerDev/phpdraft/wiki) for more.
23 |
24 | ## Writing API documentation
25 |
26 | For writing API documentation using [API Blueprint](http://apiblueprint.org/) syntax. You can read about its [specification](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md).
27 |
28 | Here's the example:
29 |
30 | ```markdown
31 | FORMAT: 1A
32 | HOST: https://api.example.com/v1
33 |
34 | # Hello API
35 |
36 | A simple API demo
37 |
38 | # Group People
39 |
40 | This section describes about the People
41 |
42 | ## Person [/people/{id}]
43 |
44 | Represent particular Person
45 |
46 | + Parameters
47 |
48 | + id (required, string, `123`) ... The id of the Person.
49 |
50 | + Model (application/json)
51 |
52 | ```
53 | {"name":"Gesang","birthdate":"01-09-1917"}
54 | ```
55 |
56 | ### Retrieve Person [GET]
57 |
58 | Return the information for the Person
59 |
60 | + Request (application/json)
61 |
62 | + Headers
63 |
64 | ```
65 | Authorization: Basic AbcdeFg=
66 | ```
67 |
68 | + Response 200 (application/json)
69 |
70 | [Person][]
71 |
72 | ```
73 |
74 | ## Building an executable
75 | Install the binary dependencies with composer (`composer install`).
76 | Run `phing phar` or `phing phar-nightly`
77 |
78 | ## Libraries
79 | This app usage the following libraries:
80 | * https://github.com/michelf/php-markdown.git
81 |
82 |
83 | ## License
84 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FSMillerDev%2Fphpdraft?ref=badge_large)
85 |
--------------------------------------------------------------------------------
/build/binary-phar-autoload.php.in:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | ')) {
4 | fwrite(
5 | STDERR,
6 | 'This version of PHPDraft requires PHP ___PHPMINVER___; using the latest version of PHP is highly recommended.' . PHP_EOL
7 | );
8 |
9 | die(1);
10 | }
11 |
12 | if (__FILE__ == realpath($GLOBALS['_SERVER']['SCRIPT_NAME'])) {
13 | $execute = true;
14 | } else {
15 | $execute = false;
16 | }
17 |
18 | define('__PHPDRAFT_PHAR__', str_replace(DIRECTORY_SEPARATOR, '/', __FILE__));
19 | define('__PHPDRAFT_PHAR_ROOT__', 'phar://___PHAR___');
20 |
21 | Phar::mapPhar('___PHAR___');
22 |
23 | ___FILELIST___
24 |
25 | if ($execute) {
26 | if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') {
27 | print file_get_contents(__PHPDRAFT_PHAR_ROOT__ . '/manifest.txt');
28 | exit;
29 | }
30 |
31 | require_once __PHPDRAFT_PHAR_ROOT__.DIRECTORY_SEPARATOR.'phpdraft'.DIRECTORY_SEPARATOR.'phpdraft';
32 | }
33 |
34 | __HALT_COMPILER();
35 |
--------------------------------------------------------------------------------
/build/library-phar-autoload.php.in:
--------------------------------------------------------------------------------
1 | &1');
6 |
7 | if (strpos($tag, '-') === false && strpos($tag, 'No names found') === false) {
8 | print $tag;
9 | } else {
10 | $branch = @exec('git rev-parse --abbrev-ref HEAD');
11 | $hash = @exec('git log -1 --format="%H"');
12 | print $branch . '@' . $hash;
13 | }
14 |
15 | print "\n";
16 |
--------------------------------------------------------------------------------
/build/phar-version.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | ]
7 | .PP
8 | Parse API Blueprint files.
9 | .PP
10 | OPTIONS
11 | .TP
12 | \fB\-\-css\fR, \fB\-c\fR
13 | Specifies a CSS file to include (value is put in a link
14 | element without checking).
15 | .TP
16 | \fB\-\-debug\-json\fR
17 | Input a rendered JSON text for debugging.
18 | .TP
19 | \fB\-\-debug\-json\-file\fR
20 | Input a rendered JSON file for debugging.
21 | .TP
22 | \fB\-\-file\fR, \fB\-f\fR
23 | Specifies the file to parse.
24 | .TP
25 | \fB\-\-header_image\fR, \fB\-i\fR
26 | Specifies an image to display in the header.
27 | .TP
28 | \fB\-\-help\fR, \-?
29 | Display this help.
30 | .TP
31 | \fB\-\-javascript\fR, \fB\-j\fR
32 | Specifies a JS file to include (value is put in a script
33 | element without checking).
34 | .TP
35 | \fB\-\-online\fR, \fB\-o\fR
36 | Always use the online mode.
37 | .TP
38 | \fB\-\-sort\fR, \fB\-s\fR
39 | Sort displayed values [All|None|Structures|Webservices]
40 | (defaults to the way the objects are in the file).
41 | .TP
42 | \fB\-\-template\fR, \fB\-t\fR
43 | Specifies the template to use. (defaults to 'default').
44 | .TP
45 | \fB\-\-version\fR, \fB\-v\fR
46 | Print the version for PHPDraft.
47 | .TP
48 | \fB\-\-yes\fR, \fB\-y\fR
49 | Always accept using the online mode.
50 | .SH "SEE ALSO"
51 | The full documentation for
52 | .B PHPDraft:
53 | is maintained as a Texinfo manual. If the
54 | .B info
55 | and
56 | .B PHPDraft:
57 | programs are properly installed at your site, the command
58 | .IP
59 | .B info PHPDraft:
60 | .PP
61 | should give you access to the complete manual.
62 |
--------------------------------------------------------------------------------
/phpdraft:
--------------------------------------------------------------------------------
1 | description('Parse API Blueprint files.')
28 | ->opt('help:h', 'This help text', false)
29 | ->opt('version:v', 'Print the version for PHPDraft.', false)
30 | ->opt('file:f', 'Specifies the file to parse.', false)
31 | ->opt('openapi:a', 'Output location for an OpenAPI file.', false)
32 | ->opt('yes:y', 'Always accept using the online mode.', false, 'bool')
33 | ->opt('online:o', 'Always use the online mode.', false, 'bool')
34 | ->opt('template:t', 'Specifies the template to use. (defaults to \'default\').', false)
35 | ->opt('sort:s', 'Sort displayed values [All|None|Structures|Webservices] (defaults to the way the objects are in the file).', false)
36 | ->opt('header_image:i', 'Specifies an image to display in the header.', false)
37 | ->opt('css:c', 'Specifies a CSS file to include (value is put in a link element without checking).', false)
38 | ->opt('javascript:j', 'Specifies a JS file to include (value is put in a script element without checking).', false)
39 | ->opt('debug-json-file', 'Input a rendered JSON file for debugging.', false)
40 | ->opt('debug-json', 'Input a rendered JSON text for debugging.', false);
41 |
42 | // Parse and return cli args.
43 | $args = $cli->parse($argv, FALSE);
44 | if (isset($args['help']) || empty($args->getOpts())) {
45 | $cli->writeHelp();
46 | throw new ExecutionException('', 0);
47 | }
48 | if (isset($args['version'])) {
49 | Version::version();
50 | throw new ExecutionException('', 0);
51 | }
52 |
53 | stream_set_blocking(STDIN, false);
54 | $stdin = stream_get_contents(STDIN);
55 | $file = $args->getOpt('file');
56 | if (!empty($stdin) && $file !== NULL) {
57 | throw new ExecutionException('ERROR: Passed data in both file and stdin', 2);
58 | } elseif (!empty($stdin) && $file === NULL) {
59 | $file = tempnam(sys_get_temp_dir(), 'phpdraft');
60 | file_put_contents($file, $stdin);
61 | }
62 | if ($file === NULL || $file === '')
63 | {
64 | throw new ExecutionException('ERROR: File does not exist', 200);
65 | }
66 |
67 | if (!($file !== NULL || isset($args['debug-json-file']) || isset($args['debug-json']))) {
68 | throw new ExecutionException('Missing required option: file', 1);
69 | }
70 |
71 | define('THIRD_PARTY_ALLOWED', getenv('PHPDRAFT_THIRD_PARTY') !== '0');
72 | if ((isset($args['y']) || isset($args['o'])) && THIRD_PARTY_ALLOWED) {
73 | define('DRAFTER_ONLINE_MODE', 1);
74 | }
75 |
76 | if (!isset($args['debug-json-file']) && !isset($args['debug-json'])) {
77 | $apib_parser = new ApibFileParser($file);
78 | $apib = $apib_parser->parse();
79 | $offline = FALSE;
80 | $online = FALSE;
81 |
82 | try {
83 | $parser = ParserFactory::getDrafter();
84 | $parser = $parser->init($apib);
85 | $data = $parser->parseToJson();
86 | } catch (ResourceException $exception) {
87 | throw new ExecutionException('No drafter available', 255);
88 | }
89 | } else {
90 | $json_string = $args['debug-json'] ?? file_get_contents($args['debug-json-file']);
91 | $data = json_decode($json_string);
92 | }
93 |
94 | if (isset($args['openapi'])) {
95 | $openapi = ParserFactory::getOpenAPI()->init($data);
96 | $openapi->write($args['openapi']);
97 | }
98 |
99 | $html = ParserFactory::getJson()->init($data);
100 | $name = 'PHPD_SORT_' . strtoupper($args->getOpt('sort', ''));
101 | $html->sorting = Sorting::${$name} ?? Sorting::PHPD_SORT_NONE->value;
102 |
103 | $color1 = getenv('COLOR_PRIMARY') === FALSE ? NULL : getenv('COLOR_PRIMARY');
104 | $color2 = getenv('COLOR_SECONDARY') === FALSE ? NULL : getenv('COLOR_SECONDARY');
105 | $colors = (is_null($color1) || is_null($color2)) ? '' : '__' . $color1 . '__' . $color2;
106 | $html->build_html(
107 | $args->getOpt('template', 'default') . $colors,
108 | $args['header_image'],
109 | $args['css'],
110 | $args['javascript']
111 | );
112 |
113 | echo $html;
114 | }
115 | catch (ExecutionException|Exception $exception)
116 | {
117 | file_put_contents('php://stderr', $exception->getMessage() . PHP_EOL);
118 | exit($exception->getCode());
119 | }
120 |
121 | function phpdraft_var_dump(...$vars)
122 | {
123 | if (defined('__PHPDRAFT_PHAR__'))
124 | {
125 | return;
126 | }
127 | echo '
';
128 | foreach ($vars as $var)
129 | {
130 | var_dump($var);
131 | }
132 | echo ' ';
133 | }
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | phpVersion: 80100
3 | bootstrapFiles:
4 | - tests/test.bootstrap.inc.php
5 | excludePaths:
6 | - src/PHPDraft/Out/Version.php
7 | - src/PHPDraft/Parse/Tests/BaseParserTest.php
8 | - src/PHPDraft/Parse/Tests/DrafterTest.php
9 | - src/PHPDraft/Parse/Tests/DrafterAPITest.php
10 | - src/PHPDraft/**/Tests/*
11 | ignoreErrors:
12 | - '#Access to an undefined property object::\$[a-zA-Z0-9\\_]+#'
13 | - '#Access to an undefined property PHPDraft\\Model\\HierarchyElement::\$[a-zA-Z0-9_]+#'
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=SMillerDev_phpdraft
2 | sonar.organization=smillerdev
3 | sonar.projectName=PHPDraft
4 | sonar.sources=src/PHPDraft
5 | sonar.php.coverage.reportPaths=coverage.xml
6 | sonar.exclusions=src/PHPDraft/**/Tests/**, tests/**
7 | sonar.coverage.exclusions=src/PHPDraft/Out/HTML/**
8 |
--------------------------------------------------------------------------------
/src/PHPDraft/Core/Autoloader.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | /**
14 | * Autoload classes according to PSR-1.
15 | */
16 | spl_autoload_register(
17 | function (string $classname): void {
18 | $classname = ltrim($classname, '\\');
19 | preg_match('/^(.+)?([^\\\\]+)$/U', $classname, $match);
20 | $classname = str_replace('\\', '/', $match[1]) . str_replace(['\\', '_'], '/', $match[2]) . '.php';
21 | if (in_array($classname, ['PHPDraft', 'Mitchelf', 'QL'], true) !== false) {
22 | include_once $classname;
23 | }
24 | }
25 | );
26 |
--------------------------------------------------------------------------------
/src/PHPDraft/In/ApibFileParser.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 |
11 | namespace PHPDraft\In;
12 |
13 | use PHPDraft\Parse\ExecutionException;
14 | use Stringable;
15 |
16 | /**
17 | * Class ApibFileParser.
18 | */
19 | class ApibFileParser implements Stringable
20 | {
21 | /**
22 | * Complete API Blueprint.
23 | *
24 | * @var string
25 | */
26 | protected string $full_apib;
27 |
28 | /**
29 | * Location of the API Blueprint to parse.
30 | *
31 | * @var string
32 | */
33 | protected string $location;
34 |
35 | /**
36 | * Filename to parse.
37 | *
38 | * @var string
39 | */
40 | private string $filename;
41 |
42 | /**
43 | * FileParser constructor.
44 | *
45 | * @param string $filename File to parse
46 | */
47 | public function __construct(string $filename = 'index.apib')
48 | {
49 | $this->filename = $filename;
50 | $this->location = pathinfo($this->filename, PATHINFO_DIRNAME) . '/';
51 |
52 | set_include_path(get_include_path() . ':' . $this->location);
53 | }
54 |
55 | /**
56 | * Get parse the apib file.
57 | *
58 | * @throws ExecutionException
59 | *
60 | * @return self self reference.
61 | */
62 | public function parse(): self
63 | {
64 | $this->full_apib = $this->get_apib($this->filename, $this->location);
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * Set apib content.
71 | *
72 | * @param string $content The content
73 | */
74 | public function set_apib_content(string $content): void
75 | {
76 | $this->full_apib = $content;
77 | }
78 |
79 | /**
80 | * Parse a given API Blueprint file
81 | * This changes all `include(file)` tags to the contents of the file.
82 | *
83 | * @param string $filename File to parse.
84 | * @param string|null $rel_path File location to look.
85 | *
86 | * @throws ExecutionException when the file could not be found.
87 | *
88 | * @return string The full API blueprint file.
89 | */
90 | private function get_apib(string $filename, ?string $rel_path = null): string
91 | {
92 | $path = $this->file_path($filename, $rel_path);
93 | $file = file_get_contents($path);
94 | $matches = [];
95 | preg_match_all('', $file, $matches);
96 | for ($i = 0; $i < count($matches[1]); $i++) {
97 | $file = str_replace(
98 | '',
99 | $this->get_apib($matches[1][$i] . $matches[2][$i], dirname($path)),
100 | $file
101 | );
102 | }
103 |
104 | preg_match_all('', $file, $matches);
105 | foreach ($matches[1] as $value) {
106 | $file = str_replace('', $this->get_schema($value), $file);
107 | }
108 |
109 | return $file;
110 | }
111 |
112 | /**
113 | * Check if an APIB file exists.
114 | *
115 | * @param string $filename File to check
116 | * @param string|null $rel_path File location to look.
117 | *
118 | * @throws ExecutionException when the file could not be found.
119 | *
120 | * @return string
121 | */
122 | private function file_path(string $filename, ?string $rel_path = null): string
123 | {
124 | // Absolute path
125 | if (file_exists($filename)) {
126 | return $filename;
127 | }
128 |
129 | // Path relative to the top file
130 | if ($rel_path !== null && file_exists($rel_path . $filename)) {
131 | return $rel_path . $filename;
132 | }
133 |
134 | // Path relative to the top file
135 | if (file_exists($this->location . $filename)) {
136 | return $this->location . $filename;
137 | }
138 |
139 | $included_path = stream_resolve_include_path($filename);
140 | if ($included_path !== false) {
141 | return $included_path;
142 | }
143 |
144 | throw new ExecutionException("API File not found: $filename", 1);
145 | }
146 |
147 | /**
148 | * Get an external Schema by URL.
149 | *
150 | * @param string $url URL to fetch the schema from
151 | *
152 | * @return string The schema as a string
153 | */
154 | private function get_schema(string $url): string
155 | {
156 | $ch = curl_init();
157 | curl_setopt($ch, CURLOPT_URL, $url);
158 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
159 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
160 | $result = curl_exec($ch);
161 | curl_close($ch);
162 |
163 | return $result;
164 | }
165 |
166 | /**
167 | * Return the value of the file.
168 | *
169 | * @return string
170 | */
171 | public function content(): string
172 | {
173 | return $this->full_apib;
174 | }
175 |
176 | /**
177 | * Return the value of the class.
178 | *
179 | * @return string
180 | */
181 | public function __toString(): string
182 | {
183 | return $this->content();
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/PHPDraft/In/Tests/ApibFileParserTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\In\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\In\ApibFileParser;
14 | use ReflectionClass;
15 |
16 | /**
17 | * Class ApibFileParserTest
18 | * @covers \PHPDraft\In\ApibFileParser
19 | */
20 | class ApibFileParserTest extends LunrBaseTest
21 | {
22 |
23 | private ApibFileParser $class;
24 |
25 | /**
26 | * Set up tests.
27 | *
28 | * @return void Test is now set up.
29 | */
30 | public function setUp(): void
31 | {
32 | $this->class = new ApibFileParser(__DIR__ . '/ApibFileParserTest.php');
33 | $this->baseSetUp($this->class);
34 | }
35 |
36 | /**
37 | * Test if setup is successful
38 | * @return void
39 | */
40 | public function testLocationSetup(): void
41 | {
42 | $this->assertPropertyEquals('location', __DIR__ . '/');
43 | }
44 |
45 | /**
46 | * Test if setup is successful
47 | * @return void
48 | */
49 | public function testFilenameSetup(): void
50 | {
51 | $this->assertPropertySame('filename', __DIR__ . '/ApibFileParserTest.php');
52 | }
53 |
54 | /**
55 | * Test if exception when the file doesn't exist
56 | *
57 | * @return void
58 | */
59 | public function testFilenameSetupWrong(): void
60 | {
61 | $this->expectException('\PHPDraft\Parse\ExecutionException');
62 | $this->expectExceptionMessageMatches('/API File not found: .*\/drafter\/non_existing_including_apib/');
63 | $this->expectExceptionCode(1);
64 |
65 | $this->set_reflection_property_value('filename', TEST_STATICS . '/drafter/non_existing_including_apib');
66 | $this->class->parse();
67 | }
68 |
69 | /**
70 | * Test if setup is successful
71 | * @return void
72 | */
73 | public function testParseBasic(): void
74 | {
75 | $this->set_reflection_property_value('filename', TEST_STATICS . '/drafter/apib/including.apib');
76 | $this->set_reflection_property_value('location', TEST_STATICS . '/drafter/apib/');
77 |
78 |
79 | $this->mock_function('curl_exec', fn() => 'hello');
80 |
81 | $this->class->parse();
82 |
83 | $this->unmock_function('curl_exec');
84 |
85 | $text = "FORMAT: 1A\nHOST: https://owner-api.teslamotors.com\n";
86 | $text .= "EXTRA_HOSTS: https://test.owner-api.teslamotors.com\nSOMETHING: INFO\n\n";
87 | $text .= "# Tesla Model S JSON API\nThis is unofficial documentation of the";
88 | $text .= " Tesla Model S JSON API used by the iOS and Android apps. It features";
89 | $text .= " functionality to monitor and control the Model S remotely.\n\nTEST";
90 | $text .= "\n\n# Hello\nThis is a test.\nhello";
91 |
92 | $this->assertPropertyEquals('full_apib', $text);
93 | $this->assertSame($text, $this->class->__toString());
94 | }
95 |
96 | /**
97 | * Test setting content
98 | *
99 | * @covers \PHPDraft\In\ApibFileParser::set_apib_content
100 | */
101 | public function testSetContent(): void
102 | {
103 | $this->class->set_apib_content('content');
104 | $this->assertEquals('content', $this->get_reflection_property_value('full_apib'));
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Category.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model;
14 |
15 | use PHPDraft\Model\Elements\BasicStructureElement;
16 | use PHPDraft\Model\Elements\ObjectStructureElement;
17 |
18 | /**
19 | * Class Category.
20 | */
21 | class Category extends HierarchyElement
22 | {
23 | /**
24 | * API Structure element.
25 | *
26 | * @var BasicStructureElement[]
27 | */
28 | public array $structures = [];
29 |
30 | /**
31 | * Fill class values based on JSON object.
32 | *
33 | * @param object $object JSON object
34 | *
35 | * @return self self-reference
36 | */
37 | public function parse(object $object): self
38 | {
39 | parent::parse($object);
40 |
41 | foreach ($object->content as $item) {
42 | switch ($item->element) {
43 | case 'resource':
44 | $resource = new Resource($this);
45 | $this->children[] = $resource->parse($item);
46 | break;
47 | case 'dataStructure':
48 | $deps = [];
49 | $struct = (new ObjectStructureElement())->get_class($item->content->element);
50 | $struct->deps = $deps;
51 | $struct->parse($item->content, $deps);
52 |
53 | if (isset($item->content->content) && is_array($item->content->content) && isset($item->content->content[0]->meta->id)) {
54 | $this->structures[$item->content->content[0]->meta->id] = $struct;
55 | } elseif (isset($item->content->meta->id->content)) {
56 | $this->structures[$item->content->meta->id->content] = $struct;
57 | } else {
58 | $this->structures[] = $struct;
59 | }
60 |
61 | break;
62 | default:
63 | continue 2;
64 | }
65 | }
66 |
67 | return $this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Comparable.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model\Elements;
14 |
15 | use Michelf\MarkdownExtra;
16 |
17 | /**
18 | * Class ArrayStructureElement.
19 | */
20 | class ArrayStructureElement extends BasicStructureElement
21 | {
22 | /**
23 | * Parse an array object.
24 | *
25 | * @param object|null $object APIB Item to parse
26 | * @param string[] $dependencies List of dependencies build
27 | *
28 | * @return self Self reference
29 | */
30 | public function parse(?object $object, array &$dependencies): self
31 | {
32 | $this->element = $object->element ?? 'array';
33 |
34 | $this->parse_common($object, $dependencies);
35 |
36 | if (!isset($object->content)) {
37 | $this->value = [];
38 |
39 | return $this;
40 | }
41 |
42 | foreach ($object->content as $sub_item) {
43 | $element = new ElementStructureElement();
44 | $element->parse($sub_item, $dependencies);
45 | $this->value[] = $element;
46 | }
47 |
48 | $this->deps = $dependencies;
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * Provide HTML representation.
55 | *
56 | * @return string
57 | */
58 | public function __toString(): string
59 | {
60 | if (is_string($this->value)) {
61 | $type = $this->get_element_as_html($this->element);
62 | $desc = '';
63 | if ($this->description !== null) {
64 | $desc = MarkdownExtra::defaultTransform($this->description);
65 | }
66 |
67 | return "$this->key $type $desc ";
68 | }
69 |
70 | $return = '';
71 | foreach ($this->value as $item) {
72 | $return .= $item->__toString();
73 | }
74 |
75 | return '';
76 | }
77 |
78 | /**
79 | * Get a new instance of a class.
80 | *
81 | * @return self
82 | */
83 | protected function new_instance(): self
84 | {
85 | return new self();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/BasicStructureElement.php:
--------------------------------------------------------------------------------
1 | key = null;
105 | if (isset($object->content->key)) {
106 | $key = new ElementStructureElement();
107 | $key->parse($object->content->key, $dependencies);
108 | $this->key = $key;
109 | }
110 |
111 | $this->type = $object->content->value->element
112 | ?? $object->meta->title->content
113 | ?? $object->meta->id->content
114 | ?? null;
115 | $this->description = null;
116 | if (isset($object->meta->description->content)) {
117 | $this->description = htmlentities($object->meta->description->content);
118 | } elseif (isset($object->meta->description)) {
119 | $this->description = htmlentities($object->meta->description);
120 | }
121 | if ($this->description !== null) {
122 | $encoded = htmlentities($this->description, ENT_COMPAT, 'ISO-8859-1', false);
123 | $this->description = $encoded;
124 | }
125 |
126 | $this->ref = null;
127 | if ($this->element === 'ref') {
128 | $this->ref = $object->content;
129 | }
130 |
131 | $this->is_variable = $object->attributes->variable->content ?? false;
132 |
133 | if (isset($object->attributes->typeAttributes->content)) {
134 | $data = array_map(function ($item) {
135 | return $item->content;
136 | }, $object->attributes->typeAttributes->content);
137 | $this->status = $data;
138 | } elseif (isset($object->attributes->typeAttributes)) {
139 | $this->status = $object->attributes->typeAttributes;
140 | }
141 |
142 | if (!in_array($this->type, self::DEFAULTS, true) && $this->type !== null) {
143 | $dependencies[] = $this->type;
144 | }
145 | }
146 |
147 | /**
148 | * Represent the element in HTML.
149 | *
150 | * @param string $element Element name
151 | *
152 | * @return string HTML string
153 | */
154 | protected function get_element_as_html(string $element): string
155 | {
156 | if (in_array($element, self::DEFAULTS, true)) {
157 | return '' . $element . '
';
158 | }
159 |
160 | $link_name = str_replace(' ', '-', strtolower($element));
161 | return '' . $element . ' ';
162 | }
163 |
164 | /**
165 | * Get a string representation of the value.
166 | *
167 | * @param bool $flat get a flat representation of the item.
168 | *
169 | * @return string
170 | */
171 | public function string_value(bool $flat = false)
172 | {
173 | if (is_array($this->value)) {
174 | $value_key = rand(0, count($this->value));
175 | if (is_subclass_of($this->value[$value_key], StructureElement::class) && $flat === false) {
176 | return $this->value[$value_key]->string_value($flat);
177 | }
178 |
179 | return $this->value[$value_key];
180 | }
181 |
182 | if (is_subclass_of($this->value, BasicStructureElement::class) && $flat === true) {
183 | return is_array($this->value->value) ? array_keys($this->value->value)[0] : $this->value->value;
184 | }
185 |
186 | return $this->value;
187 | }
188 |
189 | /**
190 | * Get what element to parse with.
191 | *
192 | * @param string $element The string to parse.
193 | *
194 | * @return BasicStructureElement The element to parse to
195 | */
196 | public function get_class(string $element): BasicStructureElement
197 | {
198 | return match ($element) {
199 | default => $this->new_instance(),
200 | 'array' => new ArrayStructureElement(),
201 | 'enum' => new EnumStructureElement(),
202 | };
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/ElementStructureElement.php:
--------------------------------------------------------------------------------
1 | element, self::DEFAULTS, true)) {
41 | $dependencies[] = $object->element;
42 | }
43 |
44 | $this->type = $object->element;
45 | $this->value = $object->content ?? null;
46 | $this->description = $object->meta->description->content ?? null;
47 |
48 | return $this;
49 | }
50 |
51 | public function __toString(): string
52 | {
53 | $type = $this->get_element_as_html($this->type);
54 |
55 | $desc = is_null($this->description) ? '' : " - $this->description ";
56 | $value = is_null($this->value) ? '' : " - $this->value ";
57 | return '' . $type . $desc . $value . ' ';
58 | }
59 |
60 | /**
61 | * Get a string representation of the value.
62 | *
63 | * @param bool $flat get a flat representation of the item.
64 | *
65 | * @return string
66 | */
67 | public function string_value(bool $flat = false)
68 | {
69 | if ($flat === true) {
70 | return $this->value;
71 | }
72 |
73 | return $this->__toString();
74 | }
75 |
76 | /**
77 | * Represent the element in HTML.
78 | *
79 | * @param string|null $element Element name
80 | *
81 | * @return string HTML string
82 | */
83 | protected function get_element_as_html(?string $element): string
84 | {
85 | if ($element === null) {
86 | return 'null
';
87 | }
88 |
89 | if (in_array($element, self::DEFAULTS, true)) {
90 | return '' . $element . '
';
91 | }
92 |
93 | $link_name = str_replace(' ', '-', strtolower($element));
94 | return '' . $element . ' ';
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/EnumStructureElement.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model\Elements;
14 |
15 | use Michelf\MarkdownExtra;
16 |
17 | class EnumStructureElement extends BasicStructureElement
18 | {
19 | /**
20 | * Parse an array object.
21 | *
22 | * @param object|null $object APIB Item to parse
23 | * @param string[] $dependencies List of dependencies build
24 | *
25 | * @return self
26 | */
27 | public function parse(?object $object, array &$dependencies): self
28 | {
29 | $this->element = $object->element;
30 |
31 | $this->parse_common($object, $dependencies);
32 | if (!isset($this->key) && isset($object->content->content)) {
33 | $this->key = new ElementStructureElement();
34 | $this->key->parse($object->content, $dependencies);
35 | }
36 | $this->type = $this->type ?? $object->content->element ?? null;
37 |
38 | if (!isset($object->content) && !isset($object->attributes)) {
39 | $this->value = $this->key;
40 |
41 | return $this;
42 | }
43 |
44 | if (isset($object->attributes->default)) {
45 | if (!in_array($object->attributes->default->content->element ?? '', self::DEFAULTS, true)) {
46 | $dependencies[] = $object->attributes->default->content->element;
47 | }
48 | $this->value = $object->attributes->default->content->content;
49 | $this->deps = $dependencies;
50 |
51 | return $this;
52 | }
53 |
54 | if (isset($object->content)) {
55 | if (!in_array($object->content->element, self::DEFAULTS, true)) {
56 | $dependencies[] = $object->content->element;
57 | }
58 | $this->value = $object->content->content;
59 | $this->deps = $dependencies;
60 |
61 | return $this;
62 | }
63 |
64 | foreach ($object->attributes->enumerations->content as $sub_item) {
65 | $element = new ElementStructureElement();
66 | $element->parse($sub_item, $dependencies);
67 | $this->value[] = $element;
68 | }
69 |
70 | $this->deps = $dependencies;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * Provide HTML representation.
77 | *
78 | * @return string
79 | */
80 | public function __toString(): string
81 | {
82 | if (is_iterable($this->value)) {
83 | $return = '';
84 | foreach ($this->value as $item) {
85 | $return .= $item->__toString();
86 | }
87 |
88 | return '';
89 | }
90 |
91 | $type = $this->get_element_as_html($this->element);
92 | $desc = $this->description === null ? '' : MarkdownExtra::defaultTransform($this->description);
93 |
94 | return "{$this->key->value} $type $desc ";
95 | }
96 |
97 | /**
98 | * Get a new instance of a class.
99 | *
100 | * @return self
101 | */
102 | protected function new_instance(): self
103 | {
104 | return new self();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/ObjectStructureElement.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model\Elements;
14 |
15 | use Michelf\MarkdownExtra;
16 |
17 | /**
18 | * Class ObjectStructureElement.
19 | */
20 | class ObjectStructureElement extends BasicStructureElement
21 | {
22 | /**
23 | * Object representation before parsing
24 | * @var object|null
25 | * @phpstan-ignore-next-line
26 | */
27 | private ?object $object;
28 |
29 | /**
30 | * Unset object function.
31 | * @internal Only for tests
32 | */
33 | public function __clearForTest(): void
34 | {
35 | $this->object = null;
36 | }
37 |
38 | /**
39 | * Parse a JSON object to a data structure.
40 | *
41 | * @param object|null $object An object to parse
42 | * @param string[] $dependencies Dependencies of this object
43 | *
44 | * @return self self reference
45 | */
46 | public function parse(?object $object, array &$dependencies): self
47 | {
48 | $this->object = $object;
49 | if (is_null($object) || !isset($object->element) || !(isset($object->content) || isset($object->meta) )) {
50 | return $this;
51 | }
52 |
53 | $this->element = $object->element;
54 | $this->parse_common($object, $dependencies);
55 |
56 | if (isset($object->content) && is_array($object->content)) {
57 | $this->parse_array_content($object, $dependencies);
58 | return $this;
59 | }
60 |
61 | if (in_array($this->type, ['object', 'array', 'enum'], true) || !in_array($this->type, self::DEFAULTS, true)) {
62 | $this->parse_value_structure($object, $dependencies);
63 |
64 | return $this;
65 | }
66 |
67 | if (isset($object->content->value->content)) {
68 | $this->value = $object->content->value->content;
69 | } elseif (isset($object->content->value->attributes->samples)) {
70 | $this->value = array_reduce($object->content->value->attributes->samples->content, function ($carry, $item) {
71 | if ($carry === null) {
72 | return "$item->content ($item->element)";
73 | }
74 | return "$carry | $item->content ($item->element)";
75 | });
76 | } else {
77 | $this->value = null;
78 | }
79 |
80 | return $this;
81 | }
82 |
83 | /**
84 | * Parse $this->value as a structure based on given content.
85 | *
86 | * @param object $object APIB content
87 | * @param string[] $dependencies Object dependencies
88 | *
89 | * @return void
90 | */
91 | protected function parse_value_structure(object $object, array &$dependencies)
92 | {
93 | if (isset($object->content->content) || in_array($this->element, ['boolean', 'string', 'number', 'ref'], true)) {
94 | return;
95 | }
96 |
97 | $value = $object->content->value ?? $object;
98 | $type = in_array($this->element, ['member'], true) ? $this->type : $this->element;
99 | $struct = $this->get_class($type);
100 |
101 | $this->value = $struct->parse($value, $dependencies);
102 |
103 | unset($struct);
104 | unset($value);
105 | }
106 |
107 | /**
108 | * Get a new instance of a class.
109 | *
110 | * @return ObjectStructureElement
111 | */
112 | protected function new_instance(): self
113 | {
114 | return new self();
115 | }
116 |
117 | /**
118 | * Parse content formed as an array.
119 | *
120 | * @param object $object APIB content
121 | * @param string[] $dependencies Object dependencies
122 | *
123 | * @return void
124 | */
125 | protected function parse_array_content(object $object, array &$dependencies): void
126 | {
127 | foreach ($object->content as $value) {
128 | $type = $this->element === 'member' ? $this->type : $this->element;
129 | $struct = $this->get_class($type);
130 |
131 | $this->value[] = $struct->parse($value, $dependencies);
132 | unset($struct);
133 | }
134 |
135 | unset($value);
136 | }
137 |
138 | /**
139 | * Print a string representation.
140 | *
141 | * @return string
142 | */
143 | public function __toString(): string
144 | {
145 | if (is_array($this->value)) {
146 | $return = '';
147 | foreach ($this->value as $object) {
148 | if (is_string($object) || is_subclass_of(get_class($object), StructureElement::class)) {
149 | $return .= $object;
150 | }
151 | }
152 |
153 | return "";
154 | }
155 |
156 | if ($this->value === null && $this->key === null && $this->description !== null) {
157 | return '';
158 | }
159 |
160 | if ($this->value === null && $this->key === null && $this->description === null) {
161 | return '{ } ';
162 | }
163 |
164 | if (is_null($this->value)) {
165 | return $this->construct_string_return('');
166 | }
167 |
168 | if (is_object($this->value) && (self::class === get_class($this->value) || RequestBodyElement::class === get_class($this->value))) {
169 | return $this->construct_string_return('' . $this->value . '
');
170 | }
171 |
172 | if (is_object($this->value) && (ArrayStructureElement::class === get_class($this->value))) {
173 | return $this->construct_string_return('' . $this->value . '
');
174 | }
175 |
176 | if (is_object($this->value) && (EnumStructureElement::class === get_class($this->value))) {
177 | return $this->construct_string_return('' . $this->value . '
');
178 | }
179 |
180 | $value = '';
181 | if (is_bool($this->value)) {
182 | $value .= ($this->value) ? 'true' : 'false';
183 | } else {
184 | $value .= $this->value;
185 | }
186 |
187 | $value .= ' ';
188 |
189 | return $this->construct_string_return($value);
190 | }
191 |
192 | /**
193 | * Create an HTML return.
194 | *
195 | * @param string $value value to display
196 | *
197 | * @return string
198 | */
199 | protected function construct_string_return(string $value): string
200 | {
201 | if ($this->type === null) {
202 | return $value;
203 | }
204 |
205 | $type = $this->get_element_as_html($this->type);
206 | $variable = '';
207 | if ($this->is_variable === true) {
208 | $link_name = str_replace(' ', '-', strtolower($this->key->type));
209 | $tooltip = 'This is a variable key of type "' . $this->key->type . '"';
210 | $variable = ' ';
211 | }
212 | $desc = '';
213 | if ($this->description !== null) {
214 | $desc = MarkdownExtra::defaultTransform($this->description);
215 | }
216 |
217 | $status_string = join(', ', $this->status);
218 | return "{$this->key->value} {$variable}{$type} {$status_string} {$desc} {$value} ";
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/RequestBodyElement.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model\Elements;
14 |
15 | /**
16 | * Class RequestBodyElement.
17 | */
18 | class RequestBodyElement extends ObjectStructureElement
19 | {
20 | /**
21 | * Print the request body as a string.
22 | *
23 | * @param string|null $type The type of request
24 | *
25 | * @return string Request body
26 | */
27 | public function print_request(?string $type = 'application/x-www-form-urlencoded'): string
28 | {
29 | if (is_array($this->value)) {
30 | $return = '';
31 | $list = [];
32 | foreach ($this->value as $object) {
33 | if (get_class($object) === self::class) {
34 | $list[] = $object->print_request($type);
35 | }
36 | }
37 |
38 | $return .= match ($type) {
39 | 'application/x-www-form-urlencoded' => join('&', $list),
40 | default => join(PHP_EOL, $list),
41 | };
42 |
43 | $return .= '
';
44 |
45 | return $return;
46 | }
47 |
48 | $value = ($this->value === null || $this->value === '') ? '?' : $this->value;
49 |
50 | switch ($type) {
51 | case 'application/x-www-form-urlencoded':
52 | return "{$this->key->value}=$value ";
53 | default:
54 | $object = [];
55 | $object[$this->key->value] = $value;
56 |
57 | return json_encode($object);
58 | }
59 | }
60 |
61 | /**
62 | * Return a new instance.
63 | *
64 | * @return RequestBodyElement
65 | */
66 | protected function new_instance(): self
67 | {
68 | return new self();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/StructureElement.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model\Elements;
14 |
15 | use Stringable;
16 |
17 | interface StructureElement extends Stringable
18 | {
19 | /**
20 | * Default data types.
21 | *
22 | * @var string[]
23 | */
24 | public const DEFAULTS = ['boolean', 'string', 'number', 'object', 'array', 'enum'];
25 |
26 | /**
27 | * Parse a JSON object to a structure.
28 | *
29 | * @param object|null $object An object to parse
30 | * @param string[] $dependencies Dependencies of this object
31 | *
32 | * @return self self reference
33 | */
34 | public function parse(?object $object, array &$dependencies): self;
35 |
36 |
37 | /**
38 | * Get a string representation of the value.
39 | *
40 | * @param bool $flat get a flat representation of the item.
41 | *
42 | * @return string
43 | */
44 | public function string_value(bool $flat = false);
45 | }
46 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/Tests/BasicStructureElementTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Elements\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\Elements\ArrayStructureElement;
14 | use PHPDraft\Model\Elements\BasicStructureElement;
15 | use PHPDraft\Model\Elements\ElementStructureElement;
16 | use PHPDraft\Model\Elements\ObjectStructureElement;
17 | use ReflectionClass;
18 |
19 | /**
20 | * Class BasicStructureElementTest
21 | * @covers \PHPDraft\Model\Elements\BasicStructureElement
22 | */
23 | class BasicStructureElementTest extends LunrBaseTest
24 | {
25 | private BasicStructureElement $class;
26 |
27 | /**
28 | * Set up tests
29 | *
30 | * @return void
31 | */
32 | public function setUp(): void
33 | {
34 | $this->class = $this->getMockBuilder('\PHPDraft\Model\Elements\BasicStructureElement')
35 | ->disableOriginalConstructor()
36 | ->getMockForAbstractClass();
37 | $this->baseSetUp($this->class);
38 | }
39 |
40 | /**
41 | * Test if the value the class is initialized with is correct
42 | */
43 | public function testSetupCorrectly(): void
44 | {
45 | $this->assertPropertyEquals('element', NULL);
46 | }
47 |
48 | /**
49 | * Test if the value the class is initialized with is correct
50 | *
51 | * @dataProvider stringValueProvider
52 | *
53 | * @param mixed $value Value to set to the class
54 | * @param mixed $string_value Expected string representation
55 | */
56 | public function testStringValue(mixed $value, mixed $string_value): void
57 | {
58 | $this->set_reflection_property_value('value', $value);
59 |
60 | $this->mock_function('rand', fn() => 0);
61 | $return = $this->class->string_value();
62 | $this->unmock_function('rand');
63 |
64 | $this->assertSame($string_value, $return);
65 | }
66 |
67 | /**
68 | * Provide string values
69 | *
70 | * @return array
71 | */
72 | public static function stringValueProvider(): array
73 | {
74 | $return = [];
75 |
76 | $return[] = ['hello', 'hello'];
77 | $return[] = [1, 1];
78 | $return[] = [true, true];
79 | $return[] = [[1], 1];
80 |
81 | $obj = new ArrayStructureElement();
82 | $obj->value = 'hello';
83 | $return[] = [[$obj], 'hello'];
84 |
85 | return $return;
86 | }
87 |
88 | /**
89 | * Test if the value the class is initialized with is correct
90 | */
91 | public function testParseCommonDeps(): void
92 | {
93 | $dep = [];
94 |
95 | $json = '{"meta":{},"attributes":{},"content":{"key":{"element": "string", "content":"key"}, "value":{"element":"cat"}}}';
96 |
97 | $answer = new ObjectStructureElement();
98 | $answer->key = new ElementStructureElement();
99 | $answer->key->type = 'string';
100 | $answer->key->value = 'key';
101 | $answer->type = 'cat';
102 | $answer->description = null;
103 |
104 | $method = $this->get_reflection_method('parse_common');
105 | $method->invokeArgs($this->class, [json_decode($json), &$dep]);
106 |
107 | $this->assertEquals($answer->key, $this->class->key);
108 | $this->assertEquals($answer->type, $this->class->type);
109 | $this->assertEquals($answer->description, $this->class->description);
110 | $this->assertEquals($answer->status, $this->class->status);
111 | $this->assertEquals(['cat'], $dep);
112 | }
113 |
114 | /**
115 | * Test if the value the class is initialized with is correct
116 | *
117 | * @dataProvider parseValueProvider
118 | *
119 | * @param mixed $value Value to set to the class
120 | * @param BasicStructureElement $expected_value Expected string representation
121 | */
122 | public function testParseCommon($value, BasicStructureElement $expected_value): void
123 | {
124 | $dep = [];
125 | $method = $this->get_reflection_method('parse_common');
126 | $method->invokeArgs($this->class, [$value, &$dep]);
127 |
128 | $this->assertEquals($expected_value->key, $this->class->key);
129 | $this->assertEquals($expected_value->type, $this->class->type);
130 | $this->assertEquals($expected_value->description, $this->class->description);
131 | $this->assertEquals($expected_value->status, $this->class->status);
132 | $this->assertEquals([], $dep);
133 | }
134 |
135 | public static function parseValueProvider(): array
136 | {
137 | $return = [];
138 |
139 | $json = '{"meta":{},"attributes":{},"content":{"key":{"element": "string", "content":"key"}, "value":{"element":"string"}}}';
140 | $obj = json_decode($json);
141 |
142 | $answer = new ObjectStructureElement();
143 | $answer->key = new ElementStructureElement();
144 | $answer->key->type = 'string';
145 | $answer->key->value = 'key';
146 | $answer->type = 'string';
147 | $answer->description = PHP_EOL;
148 |
149 | $return[] = [$obj, $answer];
150 |
151 | $obj2 = clone $obj;
152 | $obj2->attributes->typeAttributes = [1, 2];
153 | $answer->status = [1, 2];
154 |
155 | $return[] = [$obj2, $answer];
156 |
157 | $obj3 = clone $obj;
158 | $obj3->meta->description = '__hello__';
159 | $answer->description = '__hello__';
160 |
161 | $return[] = [$obj3, $answer];
162 |
163 | return $return;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Elements/Tests/ElementStructureElementTest.php:
--------------------------------------------------------------------------------
1 | class = new ElementStructureElement();
23 | $this->baseSetUp($this->class);
24 | }
25 |
26 | /**
27 | * Tear down
28 | */
29 | public function tearDown(): void
30 | {
31 | unset($this->class);
32 | unset($this->reflection);
33 | }
34 |
35 | /**
36 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::parse
37 | */
38 | public function testParse(): void
39 | {
40 | $json = '{"element": "Cow", "content": "stuff", "meta": {"description": {"content": "desc"}}}';
41 | $dep = [];
42 | $this->class->parse(json_decode($json), $dep);
43 |
44 | $this->assertPropertySame('type', 'Cow');
45 | $this->assertPropertySame('value', 'stuff');
46 | $this->assertPropertySame('description', 'desc');
47 | $this->assertSame(['Cow'], $dep);
48 | }
49 |
50 | /**
51 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::string_value
52 | */
53 | public function testStringValue(): void
54 | {
55 | $this->set_reflection_property_value('type', 'string');
56 | $this->set_reflection_property_value('description', null);
57 | $this->assertSame('string
', $this->class->string_value());
58 | }
59 |
60 | /**
61 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::__toString
62 | */
63 | public function testToString(): void
64 | {
65 | $this->set_reflection_property_value('type', 'string');
66 | $this->set_reflection_property_value('description', null);
67 |
68 | $this->assertSame('string
', $this->class->__toString());
69 | }
70 |
71 | /**
72 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::__toString
73 | */
74 | public function testToStringCustomType(): void
75 | {
76 | $this->set_reflection_property_value('type', 'Cow');
77 | $this->set_reflection_property_value('description', null);
78 |
79 | $this->assertSame('Cow ', $this->class->__toString());
80 | }
81 |
82 | /**
83 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::__toString
84 | */
85 | public function testToStringDescription(): void
86 | {
87 | $this->set_reflection_property_value('type', 'Cow');
88 | $this->set_reflection_property_value('description', 'Something');
89 |
90 | $this->assertSame('Cow - Something ', $this->class->__toString());
91 | }
92 |
93 | /**
94 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::__toString
95 | */
96 | public function testToStringValue(): void
97 | {
98 | $this->set_reflection_property_value('type', 'Cow');
99 | $this->set_reflection_property_value('value', 'stuff');
100 | $this->set_reflection_property_value('description', null);
101 |
102 | $this->assertSame('Cow - stuff ', $this->class->__toString());
103 | }
104 |
105 | /**
106 | * @covers \PHPDraft\Model\Elements\ElementStructureElement::__toString
107 | */
108 | public function testToStringDescriptionAndValue(): void
109 | {
110 | $this->set_reflection_property_value('type', 'Cow');
111 | $this->set_reflection_property_value('value', 'stuff');
112 | $this->set_reflection_property_value('description', 'Something');
113 |
114 | $this->assertSame('Cow - Something - stuff ', $this->class->__toString());
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/HTTPRequest.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model;
14 |
15 | use PHPDraft\Model\Elements\ObjectStructureElement;
16 | use PHPDraft\Model\Elements\RequestBodyElement;
17 | use PHPDraft\Model\Elements\StructureElement;
18 |
19 | class HTTPRequest implements Comparable
20 | {
21 | /**
22 | * HTTP Headers.
23 | *
24 | * @var array
25 | */
26 | public array $headers = [];
27 |
28 | /**
29 | * The HTTP Method.
30 | *
31 | * @var string
32 | */
33 | public string $method;
34 |
35 | /**
36 | * Title of the request.
37 | *
38 | * @var string|null
39 | */
40 | public ?string $title;
41 |
42 | /**
43 | * Description of the request.
44 | *
45 | * @var string|null
46 | */
47 | public ?string $description = NULL;
48 |
49 | /**
50 | * Parent class.
51 | *
52 | * @var Transition
53 | */
54 | public Transition $parent;
55 |
56 | /**
57 | * Body of the request.
58 | *
59 | * @var mixed
60 | */
61 | public mixed $body = null;
62 |
63 | /**
64 | * Schema of the body of the request.
65 | *
66 | * @var string|null
67 | */
68 | public ?string $body_schema = null;
69 | /**
70 | * Structure of the request.
71 | *
72 | * @var RequestBodyElement[]|RequestBodyElement|ObjectStructureElement
73 | */
74 | public RequestBodyElement|ObjectStructureElement|array|null $struct = [];
75 |
76 | /**
77 | * Identifier for the request.
78 | *
79 | * @var string
80 | */
81 | protected string $id;
82 |
83 | /**
84 | * HTTPRequest constructor.
85 | *
86 | * @param Transition $parent Parent entity
87 | */
88 | public function __construct(Transition &$parent)
89 | {
90 | $this->parent = &$parent;
91 | $this->id = defined('ID_STATIC') ? ID_STATIC : md5(microtime());
92 | }
93 |
94 | /**
95 | * Fill class values based on JSON object.
96 | *
97 | * @param object $object JSON object
98 | *
99 | * @return self self-reference
100 | */
101 | public function parse(object $object): self
102 | {
103 | $this->method = $object->attributes->method->content ?? $object->attributes->method;
104 | $this->title = $object->meta->title->content ?? $object->meta->title ?? null;
105 |
106 | if (isset($object->content) && $object->content !== null) {
107 | foreach ($object->content as $value) {
108 | if ($value->element === 'dataStructure') {
109 | $this->parse_structure($value);
110 | continue;
111 | }
112 |
113 | if ($value->element === 'copy') {
114 | $this->description = $value->content;
115 | continue;
116 | }
117 |
118 | if ($value->element !== 'asset') {
119 | continue;
120 | }
121 | if (is_array($value->meta->classes) && in_array('messageBody', $value->meta->classes, true)) {
122 | $this->body[] = (isset($value->content)) ? $value->content : null;
123 | $this->headers['Content-Type'] = (isset($value->attributes->contentType)) ? $value->attributes->contentType : '';
124 | continue;
125 | }
126 |
127 | if (
128 | isset($value->meta->classes->content)
129 | && is_array($value->meta->classes->content)
130 | && $value->meta->classes->content[0]->content === 'messageBody'
131 | ) {
132 | $this->body[] = (isset($value->content)) ? $value->content : null;
133 | $this->headers['Content-Type'] = (isset($value->attributes->contentType->content)) ? $value->attributes->contentType->content : '';
134 | } elseif (
135 | isset($value->meta->classes->content)
136 | && is_array($value->meta->classes->content)
137 | && $value->meta->classes->content[0]->content === 'messageBodySchema'
138 | ) {
139 | $this->body_schema = (isset($value->content)) ? $value->content : null;
140 | }
141 | }
142 | }
143 |
144 | if (isset($object->attributes->headers)) {
145 | foreach ($object->attributes->headers->content as $value) {
146 | $this->headers[$value->content->key->content] = $value->content->value->content;
147 | }
148 | }
149 |
150 | if ($this->body === null) {
151 | $this->body = &$this->struct;
152 | }
153 |
154 | return $this;
155 | }
156 |
157 | /**
158 | * Parse the objects into a request body.
159 | *
160 | * @param object $objects JSON objects
161 | */
162 | private function parse_structure(object $objects): void
163 | {
164 | $deps = [];
165 | $structure = new RequestBodyElement();
166 | $structure->parse($objects->content, $deps);
167 | $structure->deps = $deps;
168 |
169 | $this->struct = $structure;
170 | }
171 |
172 | public function get_id(): string
173 | {
174 | return $this->id;
175 | }
176 |
177 | /**
178 | * Generate a cURL command for the HTTP request.
179 | *
180 | * @param string $base_url URL to the base server
181 | * @param array $additional Extra options to pass to cURL
182 | *
183 | * @return string An executable cURL command
184 | */
185 | public function get_curl_command(string $base_url, array $additional = []): string
186 | {
187 | $options = [];
188 |
189 | $type = $this->headers['Content-Type'] ?? null;
190 |
191 | $options[] = '-X' . $this->method;
192 | if (is_string($this->body)) {
193 | $options[] = '--data-binary ' . escapeshellarg($this->body);
194 | } elseif (is_array($this->body) && $this->body !== []) {
195 | $options[] = '--data-binary ' . escapeshellarg(join('', $this->body));
196 | } elseif (is_subclass_of($this->struct, StructureElement::class)) {
197 | foreach ($this->struct->value as $body) {
198 | if (is_null($body) || $body === []) {
199 | continue;
200 | }
201 | $options[] = '--data-binary ' . escapeshellarg(strip_tags($body->print_request($type)));
202 | }
203 | }
204 | foreach ($this->headers as $header => $value) {
205 | $options[] = '-H ' . escapeshellarg($header . ': ' . $value);
206 | }
207 |
208 | $options = array_merge($options, $additional);
209 | $url = escapeshellarg($this->parent->build_url($base_url, true));
210 |
211 | return htmlspecialchars('curl ' . join(' ', $options) . ' ' . $url, ENT_NOQUOTES | ENT_SUBSTITUTE);
212 | }
213 |
214 | /**
215 | * Check if item is the same as other item.
216 | *
217 | * @param object $b Object to compare to
218 | *
219 | * @return bool
220 | */
221 | public function is_equal_to(object $b): bool
222 | {
223 | if (!($b instanceof self)) {
224 | return false;
225 | }
226 | return ($this->method === $b->method)
227 | && ($this->body == $b->body)
228 | && ($this->headers == $b->headers)
229 | && ($this->title === $b->title);
230 | }
231 |
232 | /**
233 | * Convert class to string identifier
234 | */
235 | public function __toString(): string
236 | {
237 | $headers = json_encode($this->headers);
238 | $body = json_encode($this->body);
239 | return sprintf("%s_%s_%s", $this->method, $body, $headers);
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/HTTPResponse.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model;
14 |
15 | use PHPDraft\Model\Elements\ObjectStructureElement;
16 |
17 | class HTTPResponse implements Comparable
18 | {
19 | /**
20 | * HTTP Status code.
21 | *
22 | * @var int
23 | */
24 | public int $statuscode;
25 |
26 | /**
27 | * Description of the object.
28 | *
29 | * @var string|null
30 | */
31 | public ?string $description = null;
32 |
33 | /**
34 | * Identifier for the request.
35 | *
36 | * @var string
37 | */
38 | protected string $id;
39 |
40 | /**
41 | * Response headers.
42 | *
43 | * @var array
44 | */
45 | public array $headers = [];
46 |
47 | /**
48 | * Response bodies.
49 | *
50 | * @var array
51 | */
52 | public array $content = [];
53 |
54 | /**
55 | * Response structure.
56 | *
57 | * @var ObjectStructureElement[]
58 | */
59 | public array $structure = [];
60 |
61 | /**
62 | * Parent entity.
63 | *
64 | * @var Transition
65 | */
66 | protected Transition $parent;
67 |
68 | public function __construct(Transition $parent)
69 | {
70 | $this->parent = &$parent;
71 | $this->id = defined('ID_STATIC') ? ID_STATIC : md5(microtime());
72 | }
73 |
74 | /**
75 | * Fill class values based on JSON object.
76 | *
77 | * @param object $object JSON object
78 | *
79 | * @return self self-reference
80 | */
81 | public function parse(object $object): self
82 | {
83 | if (isset($object->attributes->statusCode->content)) {
84 | $this->statuscode = intval($object->attributes->statusCode->content);
85 | } elseif (isset($object->attributes->statusCode)) {
86 | $this->statuscode = intval($object->attributes->statusCode);
87 | }
88 | if (isset($object->attributes->headers)) {
89 | $this->parse_headers($object->attributes->headers);
90 | }
91 |
92 | foreach ($object->content as $value) {
93 | $this->parse_content($value);
94 | }
95 |
96 | return $this;
97 | }
98 |
99 | public function get_id(): string
100 | {
101 | return $this->id;
102 | }
103 |
104 | /**
105 | * Parse request headers.
106 | *
107 | * @param object $object An object to parse for headers
108 | *
109 | * @return void
110 | */
111 | protected function parse_headers(object $object): void
112 | {
113 | foreach ($object->content as $value) {
114 | if (isset($value->content)) {
115 | $this->headers[$value->content->key->content] = $value->content->value->content;
116 | }
117 | }
118 | }
119 |
120 | /**
121 | * Parse request content.
122 | *
123 | * @param object $value An object to parse for content
124 | *
125 | * @return void
126 | */
127 | protected function parse_content(object $value): void
128 | {
129 | if ($value->element === 'copy') {
130 | $this->description = $value->content;
131 | return;
132 | }
133 |
134 | if ($value->element === 'asset') {
135 | if (isset($value->attributes->contentType->content)) {
136 | $this->content[$value->attributes->contentType->content] = $value->content;
137 | } elseif (isset($value->attributes->contentType)) {
138 | $this->content[$value->attributes->contentType] = $value->content;
139 | }
140 | return;
141 | }
142 |
143 | if ($value->element === 'dataStructure') {
144 | foreach ($value->content->content as $object) {
145 | $this->parse_structure($object);
146 | }
147 | }
148 | }
149 |
150 | /**
151 | * Parse structure of the content.
152 | *
153 | * @param object $object Objects containing the structure
154 | *
155 | * @return void
156 | */
157 | protected function parse_structure(object $object): void
158 | {
159 | $deps = [];
160 | $struct = new ObjectStructureElement();
161 | $struct->parse($object, $deps);
162 | $struct->deps = $deps;
163 | foreach ($this->structure as $prev) {
164 | if ($struct->__toString() === $prev->__toString()) {
165 | return;
166 | }
167 | }
168 |
169 | $this->structure[] = $struct;
170 | }
171 |
172 | /**
173 | * Check if item is the same as other item.
174 | *
175 | * @param object $b Object to compare to
176 | *
177 | * @return bool
178 | */
179 | public function is_equal_to(object $b): bool
180 | {
181 | if (!($b instanceof self)) {
182 | return false;
183 | }
184 | return ($this->statuscode === $b->statuscode)
185 | && ($this->description === $b->description);
186 | }
187 |
188 | /**
189 | * Convert class to string identifier
190 | */
191 | public function __toString(): string
192 | {
193 | return "{$this->statuscode}_{$this->description}";
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/HierarchyElement.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model;
14 |
15 | /**
16 | * Class HierarchyElement.
17 | */
18 | abstract class HierarchyElement
19 | {
20 | /**
21 | * Title of the element.
22 | *
23 | * @var string
24 | */
25 | public string $title;
26 |
27 | /**
28 | * Description of the element.
29 | *
30 | * @var string|null
31 | */
32 | public ?string $description = NULL;
33 |
34 | /**
35 | * Child elements.
36 | *
37 | * @var HierarchyElement[]
38 | */
39 | public array $children = [];
40 |
41 | /**
42 | * Parent Element.
43 | *
44 | * @var HierarchyElement|null
45 | */
46 | protected ?HierarchyElement $parent = null;
47 |
48 | /**
49 | * Parse a JSON object to an element.
50 | *
51 | * @param object $object an object to parse
52 | *
53 | * @return void
54 | */
55 | public function parse(object $object)
56 | {
57 | if (isset($object->meta) && isset($object->meta->title)) {
58 | $this->title = $object->meta->title->content ?? $object->meta->title;
59 | }
60 |
61 | if (!isset($object->content) || !is_array($object->content)) {
62 | return;
63 | }
64 |
65 | foreach ($object->content as $key => $item) {
66 | if ($item->element === 'copy') {
67 | $this->description = $item->content;
68 | unset($object->content[$key]);
69 | }
70 | }
71 |
72 | if ($object->content !== null && $object->content !== []) {
73 | $object->content = array_slice($object->content, 0);
74 | }
75 | }
76 |
77 | /**
78 | * Get a linkable HREF.
79 | *
80 | * @return string
81 | */
82 | public function get_href(): string
83 | {
84 | $separator = '-';
85 | $prep = ($this->parent !== null) ? $this->parent->get_href() . $separator : '';
86 |
87 | return $prep . str_replace(' ', '-', strtolower($this->title));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Resource.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Model;
14 |
15 | use PHPDraft\Model\Elements\ObjectStructureElement;
16 |
17 | class Resource extends HierarchyElement
18 | {
19 | /**
20 | * Location relative to the base URL.
21 | *
22 | * @var string|null
23 | */
24 | public ?string $href = null;
25 |
26 | /**
27 | * URL variables.
28 | *
29 | * @var ObjectStructureElement[]
30 | */
31 | public array $url_variables = [];
32 |
33 | /**
34 | * Resource constructor.
35 | *
36 | * @param Category $parent A reference to the parent object
37 | */
38 | public function __construct(Category &$parent)
39 | {
40 | $this->parent = $parent;
41 | }
42 |
43 | /**
44 | * Fill class values based on JSON object.
45 | *
46 | * @param object $object JSON object
47 | *
48 | * @return self self-reference
49 | */
50 | public function parse(object $object): self
51 | {
52 | parent::parse($object);
53 |
54 | if (isset($object->attributes)) {
55 | $this->href = $object->attributes->href->content ?? $object->attributes->href;
56 | }
57 |
58 | if (isset($object->attributes->hrefVariables)) {
59 | $deps = [];
60 | foreach ($object->attributes->hrefVariables->content as $variable) {
61 | $struct = new ObjectStructureElement();
62 | $this->url_variables[] = $struct->parse($variable, $deps);
63 | }
64 | }
65 |
66 | foreach ($object->content as $item) {
67 | $transition = new Transition($this);
68 | $this->children[] = $transition->parse($item);
69 | }
70 |
71 | return $this;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/CategoryTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use PHPDraft\Model\Category;
13 | use ReflectionClass;
14 |
15 | /**
16 | * Class CategoryTest
17 | * @covers \PHPDraft\Model\Category
18 | */
19 | class CategoryTest extends HierarchyElementChildTestBase
20 | {
21 | private Category $class;
22 |
23 | /**
24 | * Set up
25 | */
26 | public function setUp(): void
27 | {
28 | parent::setUp();
29 | $this->class = new Category();
30 | $this->baseSetUp($this->class);
31 | }
32 |
33 | /**
34 | * Tear down
35 | */
36 | public function tearDown(): void
37 | {
38 | unset($this->parent);
39 | parent::tearDown();
40 | }
41 |
42 | /**
43 | * Test if the value the class is initialized with is correct
44 | * @covers \PHPDraft\Model\HierarchyElement
45 | */
46 | public function testChildrenSetup(): void
47 | {
48 | $this->assertSame([], $this->class->children);
49 | }
50 |
51 | /**
52 | * Test if the value the class is initialized with is correct
53 | */
54 | public function testSetupCorrectly(): void
55 | {
56 | $this->assertPropertySame('parent', null);
57 | }
58 |
59 | /**
60 | * Test if the value the class is initialized with is correct
61 | */
62 | public function testStructuresSetup(): void
63 | {
64 | $this->assertSame([], $this->class->structures);
65 | }
66 |
67 | /**
68 | * Test basic parse functions
69 | */
70 | public function testParseIsCalled(): void
71 | {
72 | $this->set_reflection_property_value('parent', $this->parent);
73 |
74 | $obj = (object) [];
75 | $obj->content = [];
76 |
77 | $this->class->parse($obj);
78 |
79 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
80 | }
81 |
82 | /**
83 | * Test basic parse functions where 'element=resource'
84 | */
85 | public function testParseIsCalledResource(): void
86 | {
87 | $this->set_reflection_property_value('parent', $this->parent);
88 |
89 | $json = '{"content":[{"element":"resource", "content":[{"element":"copy", "content":""}]}]}';
90 |
91 | $this->class->parse(json_decode($json));
92 |
93 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
94 |
95 | $this->assertNotEmpty($this->get_reflection_property_value('children'));
96 | }
97 |
98 | /**
99 | * Test basic parse functions where 'element=dataStructure'
100 | */
101 | public function testParseIsCalledObject(): void
102 | {
103 | $this->set_reflection_property_value('parent', $this->parent);
104 |
105 | $json = '{"content":[{"element":"dataStructure", "content":{"element": "object", "key":{"content":"none"}, "value":{"element":"none"}}}]}';
106 |
107 | $this->class->parse(json_decode($json));
108 |
109 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
110 | $this->assertNotEmpty($this->get_reflection_property_value('structures'));
111 | }
112 |
113 | /**
114 | * Test basic parse functions where 'element=dataStructure'
115 | */
116 | public function testParseIsCalledObjectMetaID(): void
117 | {
118 | $this->set_reflection_property_value('parent', $this->parent);
119 |
120 | $json = '{
121 | "element": "category",
122 | "meta": {
123 | "classes": {
124 | "element": "array",
125 | "content": [
126 | {
127 | "element": "string",
128 | "content": "dataStructures"
129 | }
130 | ]
131 | }
132 | },
133 | "content": [
134 | {
135 | "element": "dataStructure",
136 | "content": {
137 | "element": "object",
138 | "meta": {
139 | "id": {
140 | "element": "string",
141 | "content": "Org"
142 | },
143 | "description": {
144 | "element": "string",
145 | "content": "An organization"
146 | }
147 | },
148 | "content": [
149 | {
150 | "element": "member",
151 | "content": {
152 | "key": {
153 | "element": "string",
154 | "content": "name"
155 | },
156 | "value": {
157 | "element": "string",
158 | "content": "Apiary"
159 | }
160 | }
161 | }
162 | ]
163 | }
164 | }
165 | ]
166 | }';
167 |
168 | $this->class->parse(json_decode($json));
169 |
170 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
171 | $this->assertNotEmpty($this->get_reflection_property_value('structures'));
172 | }
173 |
174 | /**
175 | * Test basic parse functions where 'element=henk'
176 | */
177 | public function testParseIsCalledDef(): void
178 | {
179 | $this->set_reflection_property_value('parent', $this->parent);
180 |
181 | $json = '{"content":[{"element":"henk", "content":[{"element":"copy", "content":""}]}]}';
182 |
183 | $this->class->parse(json_decode($json));
184 |
185 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
186 | $this->assertEmpty($this->get_reflection_property_value('children'));
187 | $this->assertEmpty($this->get_reflection_property_value('structures'));
188 | }
189 |
190 | /**
191 | * Test basic get_href
192 | */
193 | public function testGetHrefIsCalledWithParent(): void
194 | {
195 | $this->set_reflection_property_value('parent', $this->parent);
196 | $this->set_reflection_property_value('title', 'title');
197 |
198 | $this->parent->expects($this->once())
199 | ->method('get_href')
200 | ->willReturn('hello');
201 |
202 | $result = $this->class->get_href();
203 |
204 | $this->assertSame($result, 'hello-title');
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/HTTPResponseTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\HierarchyElement;
14 | use PHPDraft\Model\HTTPResponse;
15 | use PHPDraft\Model\Transition;
16 | use PHPUnit\Framework\MockObject\MockObject;
17 | use ReflectionClass;
18 |
19 | /**
20 | * Class HTTPResponseTest
21 | * @covers \PHPDraft\Model\HTTPResponse
22 | */
23 | class HTTPResponseTest extends LunrBaseTest
24 | {
25 |
26 | private HTTPResponse $class;
27 |
28 | /**
29 | * Mock of the parent class
30 | *
31 | * @var HierarchyElement|MockObject
32 | */
33 | protected mixed $parent;
34 |
35 | /**
36 | * Mock of the parent class
37 | *
38 | * @var Transition|MockObject
39 | */
40 | protected mixed $parent_transition;
41 |
42 | /**
43 | * Set up
44 | */
45 | public function setUp(): void
46 | {
47 | $this->parent_transition = $this->getMockBuilder('\PHPDraft\Model\Transition')
48 | ->disableOriginalConstructor()
49 | ->getMock();
50 | $this->parent = $this->getMockBuilder('\PHPDraft\Model\Transition')
51 | ->disableOriginalConstructor()
52 | ->getMock();
53 | $this->mock_function('microtime', fn() => '1000');
54 | $this->class = new HTTPResponse($this->parent_transition);
55 | $this->unmock_function('microtime');
56 | $this->baseSetUp($this->class);
57 | }
58 |
59 | /**
60 | * Tear down
61 | */
62 | public function tearDown(): void
63 | {
64 | parent::tearDown();
65 | }
66 |
67 | /**
68 | * Test if the value the class is initialized with is correct
69 | */
70 | public function testSetupCorrectly(): void
71 | {
72 | $this->assertSame($this->parent_transition, $this->get_reflection_property_value('parent'));
73 | $this->assertSame('a9b7ba70783b617e9998dc4dd82eb3c5', $this->get_reflection_property_value('id'));
74 | }
75 |
76 | /**
77 | * Tests if get_id returns the correct ID.
78 | */
79 | public function testGetId(): void
80 | {
81 | $this->assertSame('a9b7ba70783b617e9998dc4dd82eb3c5', $this->class->get_id());
82 | }
83 |
84 | /**
85 | * Test basic parse functions
86 | */
87 | public function testParseIsCalled(): void
88 | {
89 | $this->set_reflection_property_value('parent', $this->parent);
90 |
91 | $obj = '{"attributes":{"statusCode":1000, "headers":{"content":[]}}, "content":[]}';
92 |
93 | $this->class->parse(json_decode($obj));
94 |
95 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
96 | $this->assertSame(1000, $this->get_reflection_property_value('statuscode'));
97 | }
98 |
99 | /**
100 | * Test basic parse functions
101 | */
102 | public function testParseIsCalledExtraHeaders(): void
103 | {
104 | $this->set_reflection_property_value('parent', $this->parent);
105 |
106 | $obj = '{"attributes":{"statusCode":1000, "headers":{"content":[{"content":{"key":{"content":"contentKEY"}, "value":{"content":"contentVALUE"}}}]}}, "content":[]}';
107 |
108 | $this->class->parse(json_decode($obj));
109 |
110 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
111 | $this->assertSame(['contentKEY' => 'contentVALUE'], $this->get_reflection_property_value('headers'));
112 | }
113 |
114 | /**
115 | * Test basic parse functions
116 | */
117 | public function testParseIsCalledWOAttributes(): void
118 | {
119 | $this->set_reflection_property_value('parent', $this->parent);
120 | $this->set_reflection_property_value('statuscode', 200);
121 |
122 | $obj = '{"content":[]}';
123 |
124 | $this->class->parse(json_decode($obj));
125 |
126 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
127 | $this->assertSame($this->get_reflection_property_value('statuscode'), 200);
128 | }
129 |
130 | /**
131 | * Test basic parse functions
132 | */
133 | public function testParseIsCalledCopyContent(): void
134 | {
135 | $this->set_reflection_property_value('parent', $this->parent);
136 |
137 | $obj = '{"content":[{"element":"copy", "content":""}]}';
138 |
139 | $this->class->parse(json_decode($obj));
140 |
141 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
142 | $this->assertSame('', $this->get_reflection_property_value('description'));
143 | }
144 |
145 | /**
146 | * Test basic parse functions
147 | */
148 | public function testParseIsCalledStructContentEmpty(): void
149 | {
150 | $this->set_reflection_property_value('parent', $this->parent);
151 |
152 | $obj = '{"content":[{"element":"dataStructure", "content":{"content": {}}}]}';
153 |
154 | $this->class->parse(json_decode($obj));
155 |
156 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
157 | $this->assertEmpty($this->get_reflection_property_value('structure'));
158 | }
159 |
160 | /**
161 | * Test basic parse functions
162 | */
163 | public function testParseIsCalledStructContent(): void
164 | {
165 | $this->set_reflection_property_value('parent', $this->parent);
166 |
167 | $obj = '{"content":[{"element":"dataStructure", "content":{"content": [{}]}}]}';
168 |
169 | $this->class->parse(json_decode($obj));
170 |
171 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
172 | $this->assertNotEmpty($this->get_reflection_property_value('structure'));
173 | }
174 |
175 | /**
176 | * Test basic parse functions
177 | */
178 | public function testParseIsCalledStructContentHasAttr(): void
179 | {
180 | $this->set_reflection_property_value('parent', $this->parent);
181 |
182 | $obj = '{"content":[{"content":"hello", "attributes":{"contentType":"content"}, "element":"asset"}]}';
183 |
184 | $this->class->parse(json_decode($obj));
185 | $prop = $this->get_reflection_property_value('content');
186 | $this->assertArrayHasKey('content', $prop);
187 | $this->assertSame('hello', $prop['content']);
188 | }
189 |
190 | /**
191 | * Test basic is_equal_to functions
192 | */
193 | public function testEqualOnStatusCode(): void
194 | {
195 | $this->set_reflection_property_value('statuscode', 200);
196 |
197 | $obj = '{"statuscode":200, "description":"hello"}';
198 |
199 | $return = $this->class->is_equal_to(json_decode($obj));
200 |
201 | $this->assertFalse($return);
202 | }
203 |
204 | /**
205 | * Test basic is_equal_to functions
206 | */
207 | public function testEqualOnDesc(): void
208 | {
209 | $this->set_reflection_property_value('description', 'hello');
210 |
211 | $obj = '{"statuscode":300, "description":"hello"}';
212 |
213 | $return = $this->class->is_equal_to(json_decode($obj));
214 |
215 | $this->assertFalse($return);
216 | }
217 |
218 | /**
219 | * Test basic is_equal_to functions
220 | */
221 | public function testEqualOnBoth(): void
222 | {
223 | $this->set_reflection_property_value('statuscode', 200);
224 | $this->set_reflection_property_value('description', 'hello');
225 |
226 | $obj = '{"attributes":{"statusCode":200}, "content":[{"element":"copy", "content": "hello"}]}';
227 | $b = new HTTPResponse($this->parent_transition);
228 | $b->parse(json_decode($obj));
229 |
230 | $return = $this->class->is_equal_to($b);
231 |
232 | $this->assertTrue($return);
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/HierarchyElementChildTestBase.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\HierarchyElement;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 |
16 | /**
17 | * Class HierarchyElementChildTest
18 | * @package PHPDraft\Model\Tests
19 | */
20 | abstract class HierarchyElementChildTestBase extends LunrBaseTest
21 | {
22 | /**
23 | * Mock of the parent class
24 | *
25 | * @var HierarchyElement|MockObject
26 | */
27 | protected HierarchyElement|MockObject $parent;
28 |
29 | public function setUp(): void
30 | {
31 | $this->parent = $this->getMockBuilder('\PHPDraft\Model\HierarchyElement')
32 | ->getMock();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/HierarchyElementTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\HierarchyElement;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 | use ReflectionClass;
16 |
17 | /**
18 | * Class HierarchyElementTest
19 | * @covers \PHPDraft\Model\HierarchyElement
20 | */
21 | class HierarchyElementTest extends LunrBaseTest
22 | {
23 | private HierarchyElement $class;
24 |
25 | /**
26 | * Mock of the parent class
27 | *
28 | * @var HierarchyElement|MockObject
29 | */
30 | protected mixed $parent;
31 |
32 | /**
33 | * Set up
34 | */
35 | public function setUp(): void
36 | {
37 |
38 | $this->parent = $this->getMockBuilder('\PHPDraft\Model\Transition')
39 | ->disableOriginalConstructor()
40 | ->getMock();
41 | $this->class = $this->getMockForAbstractClass('PHPDraft\Model\HierarchyElement');
42 | $this->baseSetUp($this->class);
43 | }
44 |
45 | /**
46 | * Tear down
47 | */
48 | public function tearDown(): void
49 | {
50 | parent::tearDown();
51 | }
52 |
53 | /**
54 | * Test if the value the class is initialized with is correct
55 | */
56 | public function testSetupCorrectly(): void
57 | {
58 | $this->assertPropertyEquals('parent', NULL);
59 | }
60 |
61 | /**
62 | * Test basic parse functions
63 | */
64 | public function testParseIsCalled(): void
65 | {
66 | $this->set_reflection_property_value('parent', $this->parent);
67 |
68 | $obj = '{"meta":{"title":"TEST"}, "content":""}';
69 |
70 | $this->class->parse(json_decode($obj));
71 |
72 | $this->assertPropertySame('parent', $this->parent);
73 | $this->assertPropertySame('title', 'TEST');
74 | }
75 |
76 | /**
77 | * Test basic parse functions
78 | */
79 | public function testParseIsCalledLoop(): void
80 | {
81 | $this->set_reflection_property_value('parent', $this->parent);
82 |
83 | $obj = '{"meta":{"title":"TEST"}, "content":[{"element":"copy", "content":"hello"}]}';
84 |
85 | $this->class->parse(json_decode($obj));
86 |
87 | $this->assertPropertySame('parent', $this->parent);
88 | $this->assertPropertySame('title', 'TEST');
89 | $this->assertPropertySame('description', 'hello');
90 | }
91 |
92 | /**
93 | * Test basic parse functions
94 | */
95 | public function testParseIsCalledSlice(): void
96 | {
97 | $this->set_reflection_property_value('parent', $this->parent);
98 |
99 | $obj = '{"meta":{"title":"TEST"}, "content":[{"element":"copy", "content":"hello"}, {"element":"test", "content":"hello"}]}';
100 |
101 | $this->class->parse(json_decode($obj));
102 |
103 | $this->assertPropertySame('parent', $this->parent);
104 | $this->assertPropertySame('title', 'TEST');
105 | $this->assertPropertySame('description', 'hello');
106 | }
107 |
108 |
109 | /**
110 | * Test basic get_href
111 | */
112 | public function testGetHrefIsCalledWithParent(): void
113 | {
114 | $this->set_reflection_property_value('parent', $this->parent);
115 | $this->set_reflection_property_value('title', 'title');
116 |
117 | $this->parent->expects($this->once())
118 | ->method('get_href')
119 | ->willReturn('hello');
120 |
121 | $result = $this->class->get_href();
122 |
123 | $this->assertSame($result, 'hello-title');
124 | }
125 |
126 | /**
127 | * Test basic get_href
128 | */
129 | public function testGetHrefIsCalledWithoutParent(): void
130 | {
131 | $this->set_reflection_property_value('title', 'title');
132 | $result = $this->class->get_href();
133 |
134 | $this->assertSame($result, 'title');
135 | }
136 |
137 | /**
138 | * Test basic get_href
139 | */
140 | public function testGetHrefIsCalledWithTitleWithSpaces(): void
141 | {
142 | $this->set_reflection_property_value('title', 'some title');
143 | $this->set_reflection_property_value('parent', $this->parent);
144 |
145 | $this->parent->expects($this->once())
146 | ->method('get_href')
147 | ->willReturn('hello');
148 |
149 | $result = $this->class->get_href();
150 |
151 | $this->assertSame($result, 'hello-some-title');
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/ObjectElementTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\Elements\ObjectStructureElement;
14 |
15 | /**
16 | * Class ObjectElementTest
17 | */
18 | class ObjectElementTest extends LunrBaseTest
19 | {
20 |
21 | private ObjectStructureElement $class;
22 |
23 | /**
24 | * Set up
25 | */
26 | public function setUp(): void
27 | {
28 | $this->class = new ObjectStructureElement();
29 | $this->baseSetUp($this->class);
30 | }
31 |
32 | /**
33 | * Tear down
34 | */
35 | public function tearDown(): void
36 | {
37 | unset($this->parent);
38 | parent::tearDown();
39 | }
40 |
41 | /**
42 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
43 | */
44 | public function testKeySetup(): void
45 | {
46 | $this->assertSame(null, $this->class->key);
47 | }
48 |
49 | /**
50 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
51 | */
52 | public function testTypeSetup(): void
53 | {
54 | $this->assertSame(null, $this->class->type);
55 | }
56 |
57 | /**
58 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
59 | */
60 | public function testDescriptionSetup(): void
61 | {
62 | $this->assertSame(null, $this->class->description);
63 | }
64 |
65 | /**
66 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
67 | */
68 | public function testElementSetup(): void
69 | {
70 | $this->assertSame(null, $this->class->element);
71 | }
72 |
73 | /**
74 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
75 | */
76 | public function testValueSetup(): void
77 | {
78 | $this->assertSame(null, $this->class->value);
79 | }
80 |
81 | /**
82 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
83 | */
84 | public function testStatusSetup(): void
85 | {
86 | $this->assertSame([], $this->class->status);
87 | }
88 |
89 | /**
90 | * @covers \PHPDraft\Model\Elements\ObjectStructureElement
91 | */
92 | public function testDepsSetup(): void
93 | {
94 | $this->assertSame(null, $this->class->deps);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/PHPDraft/Model/Tests/ResourceTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Model\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Model\HierarchyElement;
14 | use PHPDraft\Model\Resource;
15 | use PHPUnit\Framework\MockObject\MockObject;
16 | use ReflectionClass;
17 |
18 | /**
19 | * Class ResourceTest
20 | * @covers \PHPDraft\Model\Resource
21 | */
22 | class ResourceTest extends LunrBaseTest
23 | {
24 | private Resource $class;
25 |
26 | /**
27 | * Mock of the parent class
28 | *
29 | * @var HierarchyElement|MockObject
30 | */
31 | protected mixed $parent;
32 |
33 | /**
34 | * Set up
35 | */
36 | public function setUp(): void
37 | {
38 | $this->parent = $this->getMockBuilder('\PHPDraft\Model\Category')
39 | ->getMock();
40 |
41 | $this->parent->href = null;
42 | $this->class = new Resource($this->parent);
43 | $this->baseSetUp($this->class);
44 | }
45 |
46 | /**
47 | * Tear down
48 | */
49 | public function tearDown(): void
50 | {
51 | parent::tearDown();
52 | }
53 |
54 | /**
55 | * Test if the value the class is initialized with is correct
56 | */
57 | public function testSetupCorrectly(): void
58 | {
59 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
60 | }
61 |
62 | /**
63 | * Test basic parse functions
64 | */
65 | public function testParseIsCalled(): void
66 | {
67 | $this->set_reflection_property_value('parent', $this->parent);
68 |
69 | $obj = '{"attributes":{"href": "something", "hrefVariables":{"content": [{}]}}, "content":[]}';
70 |
71 | $this->class->parse(json_decode($obj));
72 |
73 | $this->assertPropertyEquals('href', 'something');
74 | }
75 |
76 | /**
77 | * Test basic parse functions
78 | *
79 | * @covers \PHPDraft\Model\Resource::parse
80 | */
81 | public function testParseIsCalledNoHREF(): void
82 | {
83 | $this->set_reflection_property_value('parent', $this->parent);
84 | $this->set_reflection_property_value('href', null);
85 |
86 | $obj = '{"content":[]}';
87 |
88 | $this->class->parse(json_decode($obj));
89 |
90 | $this->assertNull($this->get_reflection_property_value('href'));
91 | }
92 |
93 | /**
94 | * Test basic parse functions
95 | */
96 | public function testParseIsCalledIsCopy(): void
97 | {
98 | $this->set_reflection_property_value('parent', $this->parent);
99 | $this->set_reflection_property_value('href', null);
100 |
101 | $obj = '{"content":[{"element":"copy", "content":""},{"element":"hello", "content":""}, {"element":"hello", "content":""}]}';
102 |
103 | $this->class->parse(json_decode($obj));
104 |
105 | $this->assertNull($this->get_reflection_property_value('href'));
106 | }
107 |
108 | /**
109 | * Test basic parse functions
110 | */
111 | public function testParseIsCalledIsNotCopy(): void
112 | {
113 | $this->set_reflection_property_value('parent', $this->parent);
114 | $this->set_reflection_property_value('href', null);
115 | $this->assertEmpty($this->get_reflection_property_value('children'));
116 |
117 | $obj = '{"content":[{"element":"hello", "content":""}]}';
118 |
119 | $this->class->parse(json_decode($obj));
120 |
121 | $this->assertSame($this->parent, $this->get_reflection_property_value('parent'));
122 | $this->assertNotEmpty($this->get_reflection_property_value('children'));
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/BaseTemplateRenderer.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Out;
14 |
15 | use Lukasoppermann\Httpstatus\Httpstatus;
16 | use PHPDraft\Model\Category;
17 | use PHPDraft\Model\Elements\BasicStructureElement;
18 |
19 | abstract class BaseTemplateRenderer
20 | {
21 | /**
22 | * Type of sorting to do on objects.
23 | *
24 | * @var int
25 | */
26 | public int $sorting;
27 |
28 | /**
29 | * JSON representation of an API Blueprint.
30 | *
31 | * @var object
32 | */
33 | protected object $object;
34 |
35 | /**
36 | * The base data of the API.
37 | *
38 | * @var array
39 | */
40 | protected array $base_data = [];
41 |
42 | /**
43 | * JSON object of the API blueprint.
44 | *
45 | * @var Category[]
46 | */
47 | protected array $categories = [];
48 | /**
49 | * Structures used in all data.
50 | *
51 | * @var BasicStructureElement[]
52 | */
53 | protected array $base_structures = [];
54 |
55 | /**
56 | * Parse base data
57 | *
58 | * @param object $object
59 | */
60 | protected function parse_base_data(object $object): void
61 | {
62 | //Prepare base data
63 | if (!is_array($object->content[0]->content)) {
64 | return;
65 | }
66 |
67 | $this->base_data['TITLE'] = $object->content[0]->meta->title->content ?? '';
68 |
69 | foreach ($object->content[0]->attributes->metadata->content as $meta) {
70 | $this->base_data[$meta->content->key->content] = $meta->content->value->content;
71 | }
72 |
73 | foreach ($object->content[0]->content as $value) {
74 | if ($value->element === 'copy') {
75 | $this->base_data['DESC'] = $value->content;
76 | continue;
77 | }
78 |
79 | $cat = new Category();
80 | $cat = $cat->parse($value);
81 |
82 | if (($value->meta->classes->content[0]->content ?? null) === 'dataStructures') {
83 | $this->base_structures = array_merge($this->base_structures, $cat->structures);
84 | } else {
85 | $this->categories[] = $cat;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/category.twig:
--------------------------------------------------------------------------------
1 |
2 | {% if category.title %}
3 |
4 | {% endif %}
5 | {% if category.description %}
6 | {{ category.description|markdown_to_html }}
7 | {% endif %}
8 | {% for resource in category.children %}
9 | {% include 'resource.twig' %}
10 | {% endfor %}
11 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/main.css:
--------------------------------------------------------------------------------
1 | var.url-param {
2 | font-weight: bold;
3 | }
4 |
5 | var.url-value {
6 | color: #c90c00;
7 | padding: 2px;
8 | }
9 |
10 | div.main-url {
11 | text-align: justify;
12 | line-break: loose;
13 | word-break: break-all;
14 | }
15 |
16 | button.extra-info.btn {
17 | position: fixed;
18 | bottom: 10px;
19 | right: 10px;
20 | border-radius: 30px;
21 | z-index: 999;
22 | }
23 |
24 | span.variable-info {
25 | margin-left: 10px;
26 | background: rgba(0,0,0,.5);
27 | padding: 5px 10px;
28 | border-radius: 25px;
29 | font-size: 0.7em;
30 | }
31 |
32 | button.extra-info.btn:focus {
33 | border-width: 0px;
34 | box-shadow: none;
35 | outline: 0;
36 | }
37 |
38 | .request-card > code, .popover-content {
39 | word-break: break-all;
40 | }
41 |
42 | body .media h1.media-heading {
43 | margin-top: 20px;
44 | margin-bottom: 10px;
45 | }
46 |
47 | body .media h1.media-heading .form-control {
48 | display: flex;
49 | width: auto;
50 | float: right;
51 | }
52 |
53 | div.card {
54 | margin: 10px auto;
55 | }
56 |
57 | .card-title var, h4.response var {
58 | padding: 6px 12px;
59 | margin-right: 12px;
60 | border-radius: 3px;
61 | display: inline-block;
62 | background: rgba(0, 0, 0, 0.1);
63 | }
64 |
65 | h4.response.warning var {
66 | background: rgba(255, 103, 8, 0.2);
67 | }
68 |
69 | h4.response.error var {
70 | background: rgba(201, 12, 0, 0.1);
71 | }
72 |
73 | .card-title code {
74 | color: #000;
75 | background-color: rgba(255, 255, 255, 0.7);
76 | padding: 1px 4px;
77 | margin-top: 2px;
78 | border: 1px solid transparent;
79 | border-radius: 3px
80 | }
81 |
82 | .card-title a.transition-title {
83 | padding: 6px 0;
84 | }
85 |
86 | body .col-md-10 h2:first-child,
87 | body .col-md-10 h3:first-child {
88 | margin-top: 0;
89 | }
90 |
91 | @media (min-width: 768px) {
92 | .method-nav {
93 | max-height: 100vh;
94 | overflow-x: visible;
95 | overflow-y: scroll;
96 | top: 0;
97 | box-shadow: 0px -5px 5px -5px rgba(0, 0, 0, 0.2) inset, 0px 0px 1px rgba(102, 102, 102, 0.4);
98 | }
99 | }
100 | .method-nav {
101 | padding: 0px 10px;
102 | }
103 | .method-nav nav {
104 | width: 100%;
105 | }
106 | .method-nav nav.category,
107 | .method-nav nav.structures,
108 | .method-nav nav.structures a.nav-link,
109 | .method-nav nav.resource a.nav-link {
110 | padding-left: 0px;
111 | }
112 |
113 | .method-nav nav.resource .transition a.nav-link {
114 | padding-left: .5rem;
115 | }
116 |
117 |
118 | .method-nav a.nav-link {
119 | line-height: 26px;
120 | }
121 |
122 | .method-nav a.nav-link i {
123 | color: #fff;
124 | padding: 0 5px;
125 | box-sizing: content-box;
126 | border-radius: 15px;
127 | line-height: 22px;
128 | }
129 |
130 | .main-content {
131 | position: relative;
132 | }
133 |
134 | .example-value {
135 | color: rgba(0, 0, 0, 0.4);
136 | text-align: right;
137 | }
138 |
139 | a.code {
140 | padding: 2px 4px;
141 | font-size: 90%;
142 | background-color: #f9f2f4;
143 | border-radius: 4px;
144 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
145 | }
146 |
147 | .popover {
148 | width: auto;
149 | max-width: 50%;
150 | }
151 |
152 | .popover-content {
153 | width: auto;
154 | max-width: 100%;
155 | line-break: normal;
156 | white-space: pre-wrap;
157 | }
158 |
159 | .card-body {
160 | position: relative;
161 | }
162 |
163 | .curl.btn {
164 | z-index: 999;
165 | position: absolute;
166 | top: 15px;
167 | }
168 |
169 | .curl.btn {
170 | right: 15px;
171 | border-top-left-radius: 0px;
172 | border-bottom-left-radius: 0px
173 | }
174 |
175 | a.variable-key {
176 | color: inherit;
177 | }
178 |
179 | body {
180 | --put-color: rgb(248, 148, 6);
181 | --post-color: rgb(98, 196, 98);
182 | --get-color: rgb(91, 192, 222);
183 | --delete-color: rgb(238, 95, 91);
184 | --head-color: rgb(222, 121, 91);
185 | --patch-color: rgb(196, 98, 196);
186 | }
187 |
188 | .PUT:not(.structure) > .card-header{
189 | background: var(--put-color);
190 | }
191 | span.PUT {
192 | color: var(--put-color);
193 | }
194 |
195 | .POST:not(.structure) > .card-header{
196 | background: var(--post-color);
197 | }
198 | span.POST {
199 | color: var(--post-color);
200 | }
201 |
202 | .GET:not(.structure) > .card-header{
203 | background: var(--get-color);
204 | }
205 | span.GET {
206 | color: var(--get-color);
207 | }
208 |
209 | .DELETE:not(.structure) > .card-header{
210 | background: var(--delete-color);
211 | }
212 | span.DELETE {
213 | color: var(--delete-color);
214 | }
215 |
216 | .HEAD:not(.structure) > .card-header{
217 | background: var(--head-color);
218 | }
219 | span.HEAD {
220 | color: var(--head-color);
221 | }
222 |
223 | .PATCH:not(.structure) > .card-header{
224 | background: var(--patch-color);
225 | }
226 | span.PATCH {
227 | color: var(--patch-color);
228 | }
229 |
230 | h1.media-heading {
231 | max-width: 80%;
232 | word-break: break-word;
233 | }
234 |
235 | .host-information {
236 | position: absolute;
237 | top: 0;
238 | right: 0;
239 | }
240 |
241 | .host-information label.host-dropdown {
242 | }
243 |
244 | .host-information label.host-dropdown select {
245 | border: 0 none rgba(0,0,0,0);
246 | display: inherit;
247 | width: auto;
248 | }
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/main.js:
--------------------------------------------------------------------------------
1 | function getParameters() {
2 | let result = {};
3 | let tmp = [];
4 |
5 | if (location.search === '') { return result; }
6 |
7 | location.search
8 | .substr(1)
9 | .split("&")
10 | .forEach(function (item) {tmp = item.split("="); result[tmp[0]] = decodeURIComponent(tmp[1]); });
11 | return result;
12 | };
13 |
14 | function trigger_popover() {
15 | $('[data-toggle="popover"]').popover({
16 | html: true,
17 | sanitize: false,
18 | });
19 | }
20 |
21 | function escapeRegExp(str) { return str.replace(/[-\[\]/{}()*+?.\\^$|]/g, "\\$&"); };
22 |
23 | $(function () {
24 | $('[data-toggle="tooltip"]').tooltip();
25 | $('body').on('click', function (e) {
26 | $('[data-toggle="popover"]').each(function () {
27 | if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
28 | $(this).popover('hide');
29 | }
30 | });
31 | });
32 | let contentDom = $('body>div>div.row');
33 |
34 | let formControlDom = $('h1.media-heading select.form-control');
35 | let selectedhost = formControlDom.val();
36 | formControlDom.on('change', function () {
37 | let html = contentDom.html();
38 | let re = new RegExp(escapeRegExp(selectedhost), 'g');
39 | let new_html = html.replace(re, formControlDom.val());
40 | selectedhost = formControlDom.val();
41 | contentDom.html(new_html);
42 | trigger_popover();
43 | });
44 |
45 | $('table:not(.table)').each(function () {
46 | $(this).addClass('table');
47 | });
48 |
49 | let parameters = getParameters();
50 | Object.keys(parameters).forEach(function(key) {
51 | let html = contentDom.html();
52 |
53 | const regex = `${key} : [a-zA-Z0-9\ \\\-\/]* `;
54 | let list_re = new RegExp(regex, 'g');
55 |
56 | const curl_regex = `-H '${key}: [a-zA-Z0-9\ \\\-\/]*'`;
57 | let curl_re = new RegExp(curl_regex, 'g');
58 |
59 | let new_html = html.replace(list_re, `${key} : ${parameters[key]} `)
60 | .replace(curl_re, `-H '${key}: ${parameters[key]}'`);
61 | contentDom.html(new_html);
62 | });
63 | trigger_popover();
64 | });
65 |
66 | $('.collapse.request-card').on('shown.bs.collapse', function () {
67 | $(this).parent().find('h6.request .fas.indicator').removeClass('fa-angle-up').addClass('fa-angle-down');
68 | }).on('hidden.bs.collapse', function () {
69 | $(this).parent().find('h6.request .fas.indicator').removeClass('fa-angle-down').addClass('fa-angle-up');
70 | });
71 |
72 | $('.collapse.response-card').on('shown.bs.collapse', function () {
73 | $(this).parent().find('h6.response .fas.indicator').removeClass('fa-angle-up').addClass("fa-angle-down");
74 | }).on('hidden.bs.collapse', function () {
75 | $(this).parent().find('h6.response .fas.indicator').removeClass('fa-angle-down').addClass("fa-angle-up");
76 | });
77 |
78 | $('pre.collapse.response-body').on('shown.bs.collapse', function () {
79 | $(this).parent().find('h6.response-body .fas.indicator').removeClass('fa-angle-up').addClass('fa-angle-down');
80 | }).on('hidden.bs.collapse', function () {
81 | $(this).parent().find('h6.response-body .fas.indicator').removeClass('fa-angle-down').addClass('fa-angle-up');
82 | });
83 |
84 | anchors.options = {
85 | placement: 'left',
86 | visible: 'touch',
87 | };
88 | anchors.add('.main-content h1, .main-content h2, .main-content h3, .main-content .card-header a');
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/main.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ data.TITLE }}
5 |
6 |
7 |
8 |
9 | {% for style in css %}
10 |
11 | {% endfor %}
12 |
13 |
14 |
15 |
16 |
46 |
47 | {% include 'nav.twig' %}
48 |
49 | {% for category in categories %}
50 | {% include 'category.twig' %}
51 | {% endfor %}
52 | {% if structures|length > 0 %}
53 |
54 | {% for name,structure in structures %}
55 | {% include 'structure.twig' %}
56 | {% endfor %}
57 | {% endif %}
58 |
59 |
60 |
61 | {% if extra_data|length > 1 %}
62 |
72 | {% endif %}
73 | {% for script in js %}
74 |
75 | {% endfor %}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/nav.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for category in categories %}
3 | {% if category.children|length > 0 %}
4 |
5 | {{ category.title }}
6 | {% for resource in category.children %}
7 |
8 | {{resource.title}}
9 | {% for transition in resource.children %}
10 |
11 |
12 | {{transition.title}}
13 |
14 |
15 |
16 | {% endfor %}
17 |
18 | {% endfor %}
19 |
20 | {% endif %}
21 | {% endfor %}
22 | {% if structures|length > 0 %}
23 |
24 | Data Structures
25 | {% for title,structure in structures %}
26 |
27 | {{ title }}
28 |
29 | {% endfor %}
30 |
31 | {% endif %}
32 | {{ data.HOST }}
33 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/resource.twig:
--------------------------------------------------------------------------------
1 |
2 |
10 | {% if resource.description %}
11 | {{ resource.description|markdown_to_html }}
12 | {% endif %}
13 | {% if resource.url_variables %}
14 | URI Parameters
15 |
16 |
17 | key type status description value
18 |
19 | {% for url_variable in resource.url_variables %}{{ url_variable|raw }}{% endfor %}
20 |
21 |
22 |
23 | {% endif %}
24 | {% for transition in resource.children %}
25 | {% include 'transition.twig' %}
26 | {% endfor %}
27 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/structure.twig:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | {% if structure.ref %}
9 |
Inherits from {{ structure.ref }}
10 | {% endif %}
11 | {% if structure is inheriting %}
Inherits from {{ structure.element }}
{% endif %}
12 | {% if structure.description %}{{ structure.description|markdown_to_html }}{% endif %}
13 |
14 | {% if structure.value is iterable %}
15 | {% if structure is object_type %}
16 |
17 | Object: {{ name }}
18 | {% for value in structure.value %}
19 | {{ value|raw }}
20 | {% endfor %}
21 |
22 | {% elseif structure is array_type or structure is enum_type %}
23 |
24 | {% for value in structure.value %}
25 | {{ value|raw }}
26 | {% endfor %}
27 |
28 | {% endif %}
29 | {% else %}
30 | {{ value|raw }}
31 | {% endif %}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/default/value.twig:
--------------------------------------------------------------------------------
1 | {% if value is bool %}
2 | {% if value %}true{% else %}false{% endif %}
3 | {% elseif value is array_type %}
4 |
5 | {% if value.value is iterable %}
6 |
7 | {% for value in value.value %}
8 | {% include 'value.twig' %}
9 | {% endfor %}
10 |
11 | {% elseif value.value is string %}
12 |
{{ value.key }} {{ value.get_element_as_html(value.type) }} {{ value.description }}
13 | {% endif %}
14 |
15 | {% elseif value is enum_type %}
16 |
17 | {% if value.value is iterable %}
18 |
19 | {% for value in value.value %}
20 | {% include 'value.twig' %}
21 | {% endfor %}
22 |
23 | {% elseif value.value is string %}
24 |
{{ value.key.value }} {{ value.get_element_as_html(value.type) }} {{ value.description }}
25 | {% endif %}
26 |
27 | {% elseif value is string %}
28 | {{ value }}
29 | {% else %}
30 | {{ value|raw }}
31 | {% endif %}
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/material/main.css:
--------------------------------------------------------------------------------
1 | div#navbar {
2 | height: 100vh;
3 | position: sticky;
4 | top:0;
5 | }
6 |
7 | var.url-param {
8 | font-weight: bold;
9 | }
10 |
11 | var.url-value {
12 | color: #c90c00;
13 | padding: 2px;
14 | }
15 |
16 | div.main-url {
17 | text-align: justify;
18 | line-break: loose;
19 | word-break: break-all;
20 | }
21 |
22 | a.extra-info.btn {
23 | position: fixed;
24 | bottom: 10px;
25 | right: 10px;
26 | border-radius: 30px;
27 | z-index: 999;
28 | }
29 |
30 | span.variable-info {
31 | margin-left: 10px;
32 | background: rgba(0,0,0,.5);
33 | padding: 5px 10px;
34 | border-radius: 25px;
35 | font-size: 0.7em;
36 | }
37 |
38 | button.extra-info.btn:focus {
39 | border-width: 0px;
40 | box-shadow: none;
41 | outline: 0;
42 | }
43 |
44 | .request-card > code, .popover-content {
45 | word-break: break-all;
46 | }
47 |
48 | body .media h1.media-heading {
49 | margin-top: 20px;
50 | margin-bottom: 10px;
51 | }
52 |
53 | body .media h1.media-heading .form-control {
54 | display: flex;
55 | width: auto;
56 | float: right;
57 | }
58 |
59 | div.card {
60 | margin: 10px auto;
61 | }
62 |
63 | .card-title a {
64 | color: #000;
65 | }
66 | .card-title var, h4.response var {
67 | padding: 6px 12px;
68 | margin-right: 12px;
69 | border-radius: 3px;
70 | display: inline-block;
71 | background: rgba(0, 0, 0, 0.1);
72 | }
73 |
74 | h4.response.warning var {
75 | background: rgba(255, 103, 8, 0.2);
76 | }
77 |
78 | h4.response.error var {
79 | background: rgba(201, 12, 0, 0.1);
80 | }
81 |
82 | .card-title code {
83 | color: #000;
84 | background-color: rgba(255, 255, 255, 0.7);
85 | padding: 1px 4px;
86 | margin-top: 2px;
87 | border: 1px solid transparent;
88 | border-radius: 3px
89 | }
90 |
91 | .card-title a.transition-title {
92 | padding: 6px 0;
93 | }
94 |
95 | body .col-md-10 h2:first-child,
96 | body .col-md-10 h3:first-child {
97 | margin-top: 0;
98 | }
99 |
100 | @media (min-width: 768px) {
101 | .method-nav {
102 | max-height: 100vh;
103 | overflow-x: visible;
104 | overflow-y: scroll;
105 | top: 0;
106 | box-shadow: 0px -5px 5px -5px rgba(0, 0, 0, 0.2) inset, 0px 0px 1px rgba(102, 102, 102, 0.4);
107 | }
108 | }
109 | .method-nav {
110 | padding: 0px 10px;
111 | }
112 | .method-nav nav {
113 | width: 100%;
114 | }
115 | .method-nav nav.category,
116 | .method-nav nav.structures,
117 | .method-nav nav.structures a.nav-link,
118 | .method-nav nav.resource a.nav-link {
119 | padding-left: 0px;
120 | }
121 |
122 | .method-nav nav.resource .transition a.nav-link {
123 | padding-left: .5rem;
124 | }
125 |
126 | .method-nav a.nav-link {
127 | line-height: 26px;
128 | width: 100%;
129 | display: block;
130 | }
131 |
132 | .method-nav a.nav-link span {
133 | float: right;
134 | }
135 |
136 | .method-nav a.nav-link i {
137 | color: #fff;
138 | padding: 0 5px;
139 | border-radius: 15px;
140 | line-height: 22px;
141 | }
142 |
143 | .main-content {
144 | position: relative;
145 | }
146 |
147 | .example-value {
148 | color: rgba(0, 0, 0, 0.4);
149 | text-align: right;
150 | }
151 |
152 | a.code {
153 | padding: 2px 4px;
154 | font-size: 90%;
155 | background-color: #f9f2f4;
156 | border-radius: 4px;
157 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
158 | }
159 |
160 | .popover {
161 | width: auto;
162 | max-width: 50%;
163 | }
164 |
165 | .popover-content {
166 | width: auto;
167 | max-width: 100%;
168 | line-break: normal;
169 | white-space: pre-wrap;
170 | }
171 |
172 | ul.collapsible li div.collapsible-body {
173 | position: relative;
174 | }
175 |
176 | .modal textarea {
177 | height: 18rem;
178 | }
179 |
180 | .curl.btn {
181 | z-index: 999;
182 | position: absolute;
183 | top: 15px;
184 | right: 15px;
185 | border-top-left-radius: 0px;
186 | border-bottom-left-radius: 0px
187 | }
188 |
189 | a.variable-key {
190 | color: inherit;
191 | }
192 |
193 | body {
194 | --put-color: rgb(248, 148, 6);
195 | --post-color: rgb(98, 196, 98);
196 | --get-color: rgb(91, 192, 222);
197 | --delete-color: rgb(238, 95, 91);
198 | --head-color: rgb(222, 121, 91);
199 | --patch-color: rgb(196, 98, 196);
200 | }
201 |
202 | .PUT:not(.structure) > .card-header{
203 | background: var(--put-color);
204 | }
205 | span.PUT {
206 | color: var(--put-color);
207 | }
208 |
209 | .POST:not(.structure) > .card-header{
210 | background: var(--post-color);
211 | }
212 | span.POST {
213 | color: var(--post-color);
214 | }
215 |
216 | .GET:not(.structure) > .card-header{
217 | background: var(--get-color);
218 | }
219 | span.GET {
220 | color: var(--get-color);
221 | }
222 |
223 | .DELETE:not(.structure) > .card-header{
224 | background: var(--delete-color);
225 | }
226 | span.DELETE {
227 | color: var(--delete-color);
228 | }
229 |
230 | .HEAD:not(.structure) > .card-header{
231 | background: var(--head-color);
232 | }
233 | span.HEAD {
234 | color: var(--head-color);
235 | }
236 |
237 | .PATCH:not(.structure) > .card-header{
238 | background: var(--patch-color);
239 | }
240 | span.PATCH {
241 | color: var(--patch-color);
242 | }
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/material/main.js:
--------------------------------------------------------------------------------
1 | function getParameters() {
2 | let result = {};
3 | let tmp = [];
4 |
5 | if (location.search === '') { return result; }
6 |
7 | location.search
8 | .substr(1)
9 | .split("&")
10 | .forEach(function (item) {tmp = item.split("="); result[tmp[0]] = decodeURIComponent(tmp[1]); });
11 | return result;
12 | }
13 |
14 | function escapeRegExp(str) { return str.replace(/[-\[\]/{}()*+?.\\^$|]/g, "\\$&"); };
15 |
16 | $(document).ready(function(){
17 | $('[data-toggle="tooltip"]').tooltip();
18 | $('.collapsible').collapsible();
19 | $('.modal').modal();
20 | if (!localStorage.getItem('visited')) {
21 | $('.tap-target').tapTarget().tapTarget('open');
22 | }
23 | localStorage.setItem('visited', true);
24 |
25 | let contentDom = $('body>div>div.row');
26 |
27 | let formControlDom = $('h1.media-heading select.form-control');
28 | let selectedhost = formControlDom.val();
29 | formControlDom.on('change', function () {
30 | let html = contentDom.html();
31 | let re = new RegExp(escapeRegExp(selectedhost), 'g');
32 | let new_html = html.replace(re, formControlDom.val());
33 | selectedhost = formControlDom.val();
34 | contentDom.html(new_html);
35 | trigger_popover();
36 | });
37 |
38 | let parameters = getParameters();
39 | Object.keys(parameters).forEach(function(key) {
40 | let html = contentDom.html();
41 |
42 | const regex = `${key} : [a-zA-Z0-9\ \\\-\/]* `;
43 | let list_re = new RegExp(regex, 'g');
44 |
45 | const curl_regex = `-H '${key}: [a-zA-Z0-9\ \\\-\/]*'`;
46 | let curl_re = new RegExp(curl_regex, 'g');
47 |
48 | let new_html = html.replace(list_re, `${key} : ${parameters[key]} `)
49 | .replace(curl_re, `-H '${key}: ${parameters[key]}'`);
50 | contentDom.html(new_html);
51 | });
52 | });
53 |
54 | anchors.options = {
55 | placement: 'left',
56 | visible: 'touch',
57 | };
58 | anchors.add('.main-content h1, .main-content h2, .main-content h3, .main-content .card-header a');
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/material/main.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ data.TITLE }}
5 |
6 |
7 |
8 |
9 |
10 | {% for style in css %}
11 |
12 | {% endfor %}
13 |
14 |
15 |
16 |
17 |
46 |
47 | {% include 'nav.twig' %}
48 |
49 | {% for category in categories %}
50 | {% include 'category.twig' %}
51 | {% endfor %}
52 | {% if structures|length > 0 %}
53 |
54 | {% for name,structure in structures %}
55 | {% include 'structure.twig' %}
56 | {% endfor %}
57 | {% endif %}
58 |
59 |
60 |
61 | {% if extra_data|length > 1 %}
62 |
63 |
64 |
65 |
66 |
Metadata
67 |
This button shows the metadata for the API
68 |
69 |
70 |
71 |
72 |
API Metadata:
73 | {% for key,value in extra_data %}
{{key}}:{{value}}
{% endfor %}
74 |
75 |
78 |
79 | {% endif %}
80 | {% for script in js %}
81 |
82 | {% endfor %}
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/material/nav.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for category in categories %}
3 | {% if category.children|length > 0 %}
4 |
18 | {% endif %}
19 | {% endfor %}
20 | {% if structures|length > 0 %}
21 |
29 | {% endif %}
30 |
{{ data.HOST }}
31 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/HTML/material/structure.twig:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | {% if structure.ref %}
9 |
Inherits from {{ structure.ref }}
10 | {% endif %}
11 | {% if structure.description %}{{ structure.description|markdown_to_html }}{% endif %}
12 |
13 | {% if structure.value is iterable %}
14 | {% if structure is object_type %}
15 |
16 | {% for value in structure.value %}
17 | {{ value|raw }}
18 | {% endfor %}
19 |
20 | {% elseif structure is array_type or structure is enum_type %}
21 |
22 | {% for value in structure.value %}
23 | {{ value|raw }}
24 | {% endfor %}
25 |
26 | {% endif %}
27 | {% else %}
28 | {{ value|raw }}
29 | {% endif %}
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/OpenAPI/Tests/OpenApiRendererTest.php:
--------------------------------------------------------------------------------
1 | class = new OpenApiRenderer();
21 | $this->baseSetUp($this->class);
22 | }
23 |
24 | public function tearDown(): void
25 | {
26 | parent::tearDown();
27 | }
28 |
29 | public function testWrite(): void
30 | {
31 | $this->class->init((object)[]);
32 |
33 | $tmpfile = tempnam(sys_get_temp_dir(), 'fdsfds');
34 | $this->class->write($tmpfile);
35 | $this->assertFileEquals(TEST_STATICS . '/openapi/empty.json', $tmpfile);
36 | }
37 |
38 | public function testGetTags(): void
39 | {
40 | $method = $this->get_reflection_method('getTags');
41 | $result = $method->invokeArgs($this->class, []);
42 |
43 | $this->assertArrayEmpty($result);
44 | }
45 |
46 | public function testGetSecurity(): void
47 | {
48 | $method = $this->get_reflection_method('getSecurity');
49 | $result = $method->invokeArgs($this->class, []);
50 |
51 | $this->assertArrayEmpty($result);
52 | }
53 |
54 | public function testGetComponents(): void
55 | {
56 | $method = $this->get_reflection_method('getComponents');
57 | $result = $method->invokeArgs($this->class, []);
58 |
59 | $this->assertEquals((object)['schemas' => []],$result);
60 | }
61 |
62 | public function testGetDocs(): void
63 | {
64 | $this->markTestSkipped('Not implemented');
65 |
66 | $method = $this->get_reflection_method('getDocs');
67 | $result = $method->invokeArgs($this->class, []);
68 |
69 | $this->assertEquals((object)[],$result);
70 | }
71 |
72 | public function testGetPaths(): void
73 | {
74 | $method = $this->get_reflection_method('getPaths');
75 | $result = $method->invokeArgs($this->class, []);
76 |
77 | $this->assertEquals((object)[],$result);
78 | }
79 |
80 | public function testGetServers(): void
81 | {
82 | $method = $this->get_reflection_method('getServers');
83 | $result = $method->invokeArgs($this->class, []);
84 |
85 | $this->assertEquals([['url' => null,'description' => 'Main host'], ['url' => '']],$result);
86 | }
87 |
88 | public function testGetApiInfo(): void
89 | {
90 | $method = $this->get_reflection_method('getApiInfo');
91 | $result = $method->invokeArgs($this->class, []);
92 |
93 | $this->assertEquals([
94 | 'title' => null,
95 | 'version' => '1.0.0',
96 | 'summary' => ' generated from API Blueprint',
97 | 'description' => null,
98 | ],$result);
99 | }
100 |
101 | public function testToResponses(): void
102 | {
103 | $method = $this->get_reflection_method('toResponses');
104 | $result = $method->invokeArgs($this->class, [[]]);
105 |
106 | $this->assertEquals([],$result);
107 | }
108 |
109 | public function testToBody(): void
110 | {
111 | $mock = $this->getMockBuilder(HttpRequest::class)
112 | ->disableOriginalConstructor()
113 | ->getMock();
114 |
115 | $method = $this->get_reflection_method('toBody');
116 | $result = $method->invokeArgs($this->class, [$mock]);
117 |
118 | $this->assertEquals([],$result);
119 | }
120 |
121 | public function testToParameters(): void
122 | {
123 | $mock = $this->getMockBuilder(HttpRequest::class)
124 | ->disableOriginalConstructor()
125 | ->getMock();
126 |
127 | $method = $this->get_reflection_method('toParameters');
128 | $result = $method->invokeArgs($this->class, [[], 'href']);
129 |
130 | $this->assertEquals([],$result);
131 | }
132 | }
--------------------------------------------------------------------------------
/src/PHPDraft/Out/Sorting.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Out;
14 |
15 | /**
16 | * Sorting constants.
17 | */
18 | enum Sorting: int
19 | {
20 | /**
21 | * Sets sorting to all parts.
22 | */
23 | case PHPD_SORT_ALL = 3;
24 |
25 | /**
26 | * Sets sorting to all webservices.
27 | */
28 | case PHPD_SORT_WEBSERVICES = 2;
29 |
30 | /**
31 | * Sets sorting to all data structures.
32 | */
33 | case PHPD_SORT_STRUCTURES = 1;
34 |
35 | /**
36 | * Sets sorting to no data structures.
37 | */
38 | case PHPD_SORT_NONE = -1;
39 |
40 | /**
41 | * Check if structures should be sorted.
42 | *
43 | * @param int $sort The sorting level.
44 | *
45 | * @return bool
46 | */
47 | public static function sortStructures(int $sort): bool
48 | {
49 | return $sort === Sorting::PHPD_SORT_ALL->value || $sort === Sorting::PHPD_SORT_STRUCTURES->value;
50 | }
51 |
52 | /**
53 | * Check if services should be sorted.
54 | *
55 | * @param int $sort The sorting level.
56 | *
57 | * @return bool
58 | */
59 | public static function sortServices(int $sort): bool
60 | {
61 | return $sort === Sorting::PHPD_SORT_ALL->value || $sort === Sorting::PHPD_SORT_WEBSERVICES->value;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/Tests/SortingTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Out\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Out\Sorting;
14 | use PHPDraft\Out\Version;
15 | use ReflectionClass;
16 |
17 | /**
18 | * Class SortingTest
19 | *
20 | * @covers \PHPDraft\Out\Sorting
21 | */
22 | class SortingTest extends LunrBaseTest
23 | {
24 | /**
25 | * Test if service sorting is determined correctly.
26 | *
27 | * @covers \PHPDraft\Out\Sorting::sortServices
28 | */
29 | public function testSortsServicesIfNeeded(): void
30 | {
31 | $this->assertTrue(Sorting::sortServices(3));
32 | $this->assertTrue(Sorting::sortServices(2));
33 | $this->assertFalse(Sorting::sortServices(-1));
34 | $this->assertFalse(Sorting::sortServices(1));
35 | $this->assertFalse(Sorting::sortServices(0));
36 | }
37 |
38 | /**
39 | * Test if structure sorting is determined correctly.
40 | *
41 | * @covers \PHPDraft\Out\Sorting::sortStructures
42 | */
43 | public function testSortsStructureIfNeeded(): void
44 | {
45 | $this->assertTrue(Sorting::sortStructures(3));
46 | $this->assertTrue(Sorting::sortStructures(1));
47 | $this->assertFalse(Sorting::sortStructures(-1));
48 | $this->assertFalse(Sorting::sortStructures(2));
49 | $this->assertFalse(Sorting::sortStructures(0));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/Tests/TwigFactoryTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Environment::class, TwigFactory::get($loader));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/Tests/VersionTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Out\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Out\Version;
14 | use ReflectionClass;
15 |
16 | /**
17 | * Class VersionTest
18 | *
19 | * @covers \PHPDraft\Out\Version
20 | */
21 | class VersionTest extends LunrBaseTest
22 | {
23 | /**
24 | * Set up tests
25 | *
26 | * @return void
27 | */
28 | public function setUp(): void
29 | {
30 | $this->class = new Version();
31 | $this->reflection = new ReflectionClass('PHPDraft\Out\Version');
32 | }
33 |
34 | /**
35 | * Test if the value the class is initialized with is correct
36 | */
37 | public function testReleaseIDIsNull(): void
38 | {
39 | $this->constant_redefine('VERSION', '0');
40 | $this->mock_function('exec', fn() => '12');
41 | $return = $this->class->release_id();
42 | $this->assertSame('12', $return);
43 | $this->unmock_function('exec');
44 | }
45 |
46 | /**
47 | * Test if the value the class is initialized with is correct
48 | */
49 | public function testReleaseIDIsNotNull(): void
50 | {
51 | $this->constant_redefine('VERSION', '1.2.3');
52 | $return = $this->class->release_id();
53 | $this->assertSame('1.2.3', $return);
54 | }
55 |
56 | /**
57 | * Test if the value the class is initialized with is correct
58 | */
59 | public function testVersion(): void
60 | {
61 | $this->constant_redefine('VERSION', '1.2.4');
62 | $this->class->version();
63 | $this->expectOutputString('PHPDraft: 1.2.4');
64 | }
65 |
66 | /**
67 | * Test if the value the class is initialized with is correct
68 | */
69 | public function testSeries(): void
70 | {
71 | $this->constant_redefine('VERSION', '1.2.4');
72 | $return = $this->class->series();
73 | $this->assertSame('1.2', $return);
74 | }
75 |
76 | /**
77 | * Test if the value the class is initialized with is correct
78 | */
79 | public function testReleaseChannel(): void
80 | {
81 | $this->constant_redefine('VERSION', '1.2.4-beta');
82 | $return = $this->class->getReleaseChannel();
83 | $this->assertSame('-nightly', $return);
84 | }
85 |
86 | /**
87 | * Test if the value the class is initialized with is correct
88 | */
89 | public function testReleaseChannelNormal(): void
90 | {
91 | $this->constant_redefine('VERSION', '1.2.4');
92 | $return = $this->class->getReleaseChannel();
93 | $this->assertSame('', $return);
94 | }
95 |
96 | /**
97 | * Test if the value the class is initialized with is correct
98 | */
99 | public function testSeriesNightly(): void
100 | {
101 | $this->constant_redefine('VERSION', '1.2.4-beta');
102 | $return = $this->class->series();
103 | $this->assertSame('1.2', $return);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/TwigFactory.php:
--------------------------------------------------------------------------------
1 | addFilter(new TwigFilter('method_icon', fn(string $string) => HtmlTemplateRenderer::get_method_icon($string)));
29 | $twig->addFilter(new TwigFilter('strip_link_spaces', fn(string $string) => HtmlTemplateRenderer::strip_link_spaces($string)));
30 | $twig->addFilter(new TwigFilter('response_status', fn(string $string) => HtmlTemplateRenderer::get_response_status((int) $string)));
31 | $twig->addFilter(new TwigFilter('status_reason', fn(int $code) => (new Httpstatus())->getReasonPhrase($code)));
32 | $twig->addFilter(new TwigFilter('minify_css', function (string $string) {
33 | $minify = new Css();
34 | $minify->add($string);
35 | return $minify->minify();
36 | }));
37 | $twig->addFilter(new TwigFilter('minify_js', function (string $string) {
38 | $minify = new JS();
39 | $minify->add($string);
40 | return $minify->minify();
41 | }));
42 |
43 | $twig->addTest(new TwigTest('enum_type', fn(object $object) => $object instanceof EnumStructureElement));
44 | $twig->addTest(new TwigTest('object_type', fn(object $object) => $object instanceof ObjectStructureElement));
45 | $twig->addTest(new TwigTest('array_type', fn(object $object) => $object instanceof ArrayStructureElement));
46 | $twig->addTest(new TwigTest('bool', fn($object) => is_bool($object)));
47 | $twig->addTest(new TwigTest('string', fn($object) => is_string($object)));
48 | $twig->addTest(new TwigTest('variable_type', fn(BasicStructureElement $object) => $object->is_variable));
49 | $twig->addTest(new TwigTest('inheriting', function (BasicStructureElement $object): bool {
50 | $options = array_merge(StructureElement::DEFAULTS, ['member', 'select', 'option', 'ref', 'T', 'hrefVariables']);
51 | return !(is_null($object->element) || in_array($object->element, $options, true));
52 | }));
53 |
54 | $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
55 | public function load(string $class): ?object
56 | {
57 | if (MarkdownRuntime::class === $class) {
58 | return new MarkdownRuntime(new DefaultMarkdown());
59 | }
60 | return null;
61 | }
62 | });
63 | $twig->addExtension(new MarkdownExtension());
64 |
65 | return $twig;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/PHPDraft/Out/Version.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Out;
14 |
15 | /**
16 | * Class Version.
17 | */
18 | class Version
19 | {
20 | /**
21 | * Return the version.
22 | *
23 | * @return void
24 | */
25 | public static function version(): void
26 | {
27 | $version = self::release_id();
28 | echo 'PHPDraft: ' . $version;
29 | }
30 |
31 | /**
32 | * Get the version number.
33 | *
34 | * @return string
35 | */
36 | public static function release_id(): string
37 | {
38 | $env_id = getenv('PHPDRAFT_RELEASE_ID');
39 | if ($env_id !== FALSE) {
40 | return $env_id;
41 | }
42 |
43 | return VERSION !== '0' ? VERSION : @exec('git describe --tags 2>&1');
44 | }
45 |
46 | /**
47 | * Print the series of the update.
48 | *
49 | * @return string Series
50 | */
51 | public function series(): string
52 | {
53 | if (strpos(self::release_id(), '-')) {
54 | $version = explode('-', self::release_id())[0];
55 | } else {
56 | $version = self::release_id();
57 | }
58 |
59 | return implode('.', array_slice(explode('.', $version), 0, 2));
60 | }
61 |
62 | /**
63 | * Get the manner of releasing.
64 | *
65 | * @return string
66 | */
67 | public function getReleaseChannel(): string
68 | {
69 | if (str_contains(self::release_id(), '-')) {
70 | return '-nightly';
71 | }
72 |
73 | return '';
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/BaseHtmlGenerator.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use Stringable;
16 |
17 | abstract class BaseHtmlGenerator implements Stringable
18 | {
19 | /**
20 | * Type of sorting to do.
21 | *
22 | * @var int
23 | */
24 | public int $sorting;
25 |
26 | /**
27 | * JSON representation of an API Blueprint.
28 | *
29 | * @var object
30 | */
31 | protected object $object;
32 |
33 | /**
34 | * Rendered HTML
35 | *
36 | * @var string
37 | */
38 | protected string $html;
39 |
40 | /**
41 | * Constructor.
42 | *
43 | * @param object $json Representation of an API Blueprint
44 | *
45 | * @return self
46 | */
47 | public function init(object $json): self
48 | {
49 | $this->object = $json;
50 |
51 | return $this;
52 | }
53 |
54 | /**
55 | * Build the HTML representation of the object.
56 | *
57 | * @param string $template Type of template to display.
58 | * @param string|null $image Image to use as a logo
59 | * @param string|null $css CSS to load
60 | * @param string|null $js JS to load
61 | *
62 | * @return void
63 | *
64 | * @throws ExecutionException As a runtime exception
65 | */
66 | abstract public function build_html(string $template = 'default', ?string $image = null, ?string $css = null, ?string $js = null): void;
67 |
68 |
69 | /**
70 | * Get the HTML representation of the object.
71 | *
72 | * @return string
73 | */
74 | abstract public function __toString(): string;
75 | }
76 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/BaseParser.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use PHPDraft\In\ApibFileParser;
16 |
17 | /**
18 | * Class BaseParser.
19 | *
20 | * @package PHPDraft\Parse
21 | */
22 | abstract class BaseParser
23 | {
24 | /**
25 | * The API Blueprint output (JSON).
26 | *
27 | * @var object|null
28 | */
29 | public ?object $json;
30 |
31 | /**
32 | * Temp directory.
33 | *
34 | * @var string
35 | */
36 | protected string $tmp_dir;
37 |
38 | /**
39 | * The API Blueprint input.
40 | *
41 | * @var ApibFileParser
42 | */
43 | protected ApibFileParser $apib;
44 |
45 | /**
46 | * BaseParser constructor.
47 | *
48 | * @param ApibFileParser $apib API Blueprint text
49 | *
50 | * @return self
51 | */
52 | public function init(ApibFileParser $apib): self
53 | {
54 | $this->apib = $apib;
55 | $this->tmp_dir = sys_get_temp_dir() . '/drafter';
56 |
57 | return $this;
58 | }
59 |
60 | /**
61 | * BaseParser destructor.
62 | */
63 | public function __destruct()
64 | {
65 | unset($this->apib);
66 | unset($this->json);
67 | unset($this->tmp_dir);
68 | }
69 |
70 | /**
71 | * Parse the API Blueprint text to JSON.
72 | *
73 | * @throws ExecutionException When the JSON is invalid or warnings are thrown in parsing
74 | *
75 | * @return object JSON output.
76 | */
77 | public function parseToJson(): object
78 | {
79 | if (!file_exists($this->tmp_dir)) {
80 | mkdir($this->tmp_dir, 0777, true);
81 | }
82 |
83 | file_put_contents($this->tmp_dir . '/index.apib', $this->apib->content());
84 |
85 | $this->parse();
86 |
87 | if (json_last_error() !== JSON_ERROR_NONE) {
88 | file_put_contents('php://stderr', 'ERROR: invalid json in ' . $this->tmp_dir . '/index.json');
89 |
90 | throw new ExecutionException('Drafter generated invalid JSON (' . json_last_error_msg() . ')', 2);
91 | }
92 |
93 | $warnings = false;
94 | foreach ($this->json->content as $item) {
95 | if ($item->element === 'annotation') {
96 | $warnings = true;
97 | $line = $item->attributes->sourceMap->content[0]->content[0]->content[0]->attributes->line->content ?? 'UNKNOWN';
98 | $prefix = (is_array($item->meta->classes)) ? strtoupper($item->meta->classes[0]) : strtoupper($item->meta->classes->content[0]->content);
99 | $error = $item->content;
100 | file_put_contents('php://stderr', "$prefix: $error (line $line)\n");
101 | file_put_contents('php://stdout', "$prefix: $error (line $line) \n");
102 | }
103 | }
104 |
105 | if ($warnings) {
106 | throw new ExecutionException('Parsing encountered errors and stopped', 2);
107 | }
108 |
109 | return $this->json;
110 | }
111 |
112 | /**
113 | * Parses the apib for the selected method.
114 | *
115 | * @return void
116 | */
117 | abstract protected function parse(): void;
118 |
119 | /**
120 | * Check if a given parser is available.
121 | *
122 | * @return bool
123 | */
124 | abstract public static function available(): bool;
125 | }
126 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/Drafter.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use PHPDraft\In\ApibFileParser;
16 | use RuntimeException;
17 | use UnexpectedValueException;
18 |
19 | class Drafter extends BaseParser
20 | {
21 | /**
22 | * The location of the drafter executable.
23 | *
24 | * @var string
25 | */
26 | protected string $drafter;
27 |
28 | /**
29 | * ApibToJson constructor.
30 | *
31 | * @param ApibFileParser $apib API Blueprint text
32 | *
33 | * @return BaseParser
34 | */
35 | public function init(ApibFileParser $apib): BaseParser
36 | {
37 | parent::init($apib);
38 | $loc = self::location();
39 | if ($loc === false) {
40 | throw new UnexpectedValueException("Could not find drafter location!");
41 | }
42 | $this->drafter = $loc;
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * Return drafter location if found.
49 | *
50 | * @return false|string
51 | */
52 | public static function location(): false|string
53 | {
54 | $returnVal = shell_exec('which drafter 2> /dev/null');
55 | if ($returnVal === NULL)
56 | {
57 | return false;
58 | }
59 |
60 | $returnVal = preg_replace('/^\s+|\n|\r|\s+$/m', '', $returnVal);
61 |
62 | return $returnVal === null || $returnVal === '' ? false : $returnVal;
63 | }
64 |
65 | /**
66 | * Parses the apib for the selected method.
67 | *
68 | * @return void
69 | */
70 | protected function parse(): void
71 | {
72 | shell_exec("$this->drafter $this->tmp_dir/index.apib -f json -o $this->tmp_dir/index.json 2> /dev/null");
73 | $content = file_get_contents($this->tmp_dir . '/index.json');
74 | if (!is_string($content)) {
75 | throw new RuntimeException('Could not read intermediary APIB file!');
76 | }
77 |
78 | $this->json = json_decode($content);
79 | }
80 |
81 | /**
82 | * Check if a given parser is available.
83 | *
84 | * @return bool
85 | */
86 | public static function available(): bool
87 | {
88 | $path = self::location();
89 |
90 | $version = shell_exec('drafter -v 2> /dev/null');
91 | if ($version === NULL)
92 | {
93 | return false;
94 | }
95 |
96 | $version = preg_match('/^v([45])/', $version);
97 |
98 | return $path && $version === 1;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/DrafterAPI.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use CurlHandle;
16 | use PHPDraft\In\ApibFileParser;
17 |
18 | class DrafterAPI extends BaseParser
19 | {
20 | /**
21 | * ApibToJson constructor.
22 | *
23 | * @param ApibFileParser $apib API Blueprint text
24 | *
25 | * @return BaseParser
26 | */
27 | public function init(ApibFileParser $apib): BaseParser
28 | {
29 | parent::init($apib);
30 |
31 | return $this;
32 | }
33 |
34 | /**
35 | * Parses the apib for the selected method.
36 | *
37 | * @return void
38 | */
39 | protected function parse(): void
40 | {
41 | $ch = self::curl_init_drafter($this->apib->content());
42 |
43 | $response = curl_exec($ch);
44 |
45 | if (curl_errno($ch) !== 0) {
46 | throw new ResourceException('Drafter webservice failed to parse input', 1);
47 | }
48 |
49 | $this->json = json_decode($response);
50 | }
51 |
52 | /**
53 | * Init curl for drafter webservice.
54 | *
55 | * @param string $message API blueprint to parse
56 | *
57 | * @return CurlHandle
58 | */
59 | public static function curl_init_drafter(string $message): CurlHandle
60 | {
61 | $ch = curl_init();
62 |
63 | curl_setopt($ch, CURLOPT_URL, 'https://api.apiblueprint.org/parser');
64 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
65 | curl_setopt($ch, CURLOPT_HEADER, false);
66 |
67 | curl_setopt($ch, CURLOPT_POST, true);
68 |
69 | curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
70 |
71 | curl_setopt($ch, CURLOPT_HTTPHEADER, [
72 | 'Content-Type: text/vnd.apiblueprint',
73 | 'Accept: application/vnd.refract.parse-result+json',
74 | ]);
75 |
76 | return $ch;
77 | }
78 |
79 | /**
80 | * Check if a given parser is available.
81 | *
82 | * @return bool
83 | */
84 | public static function available(): bool
85 | {
86 | if (!defined('DRAFTER_ONLINE_MODE') || DRAFTER_ONLINE_MODE !== 1) {
87 | return false;
88 | }
89 |
90 | $ch = self::curl_init_drafter('# Hello API
91 | ## /message
92 | ### GET
93 | + Response 200 (text/plain)
94 |
95 | Hello World!');
96 |
97 | curl_exec($ch);
98 |
99 | if (curl_errno($ch) !== CURLE_OK) {
100 | return false;
101 | }
102 | curl_close($ch);
103 |
104 | return true;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/ExecutionException.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use Exception;
16 |
17 | /**
18 | * Class ExecutionException.
19 | *
20 | * @package Parse
21 | */
22 | class ExecutionException extends Exception
23 | {
24 | }
25 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/HtmlGenerator.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use Twig\Error\LoaderError;
16 | use Twig\Error\RuntimeError;
17 | use Twig\Error\SyntaxError;
18 | use PHPDraft\Out\HtmlTemplateRenderer;
19 |
20 | /**
21 | * Class HtmlGenerator.
22 | */
23 | class HtmlGenerator extends BaseHtmlGenerator
24 | {
25 | /**
26 | * Get the HTML representation of the JSON object.
27 | *
28 | * @param string $template Type of template to display.
29 | * @param string|null $image Image to use as a logo
30 | * @param string|null $css CSS to load
31 | * @param string|null $js JS to load
32 | *
33 | * @return void HTML template to display
34 | *
35 | * @throws ExecutionException As a runtime exception
36 | * @throws LoaderError
37 | * @throws RuntimeError
38 | * @throws SyntaxError
39 | */
40 | public function build_html(string $template = 'default', ?string $image = null, ?string $css = null, ?string $js = null): void
41 | {
42 | $gen = new HtmlTemplateRenderer($template, $image);
43 |
44 | if (!is_null($css)) {
45 | $gen->css = explode(',', $css);
46 | }
47 |
48 | if (!is_null($js)) {
49 | $gen->js = explode(',', $js);
50 | }
51 |
52 | $gen->sorting = $this->sorting;
53 |
54 | $this->html = $gen->get($this->object);
55 | }
56 |
57 | /**
58 | * Returns the generated HTML.
59 | *
60 | * @return string
61 | */
62 | public function __toString(): string
63 | {
64 | return $this->html;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/ParserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 |
13 | namespace PHPDraft\Parse;
14 |
15 | use RuntimeException;
16 |
17 | /**
18 | * Class ResourceException.
19 | *
20 | * @package Parse
21 | */
22 | class ResourceException extends RuntimeException
23 | {
24 | }
25 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/Tests/BaseParserTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Parse\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\In\ApibFileParser;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 | use ReflectionClass;
16 |
17 | /**
18 | * Class BaseParserTest
19 | * @covers \PHPDraft\Parse\BaseParser
20 | */
21 | class BaseParserTest extends LunrBaseTest
22 | {
23 | /**
24 | * Shared instance of the file parser.
25 | *
26 | * @var ApibFileParser&MockObject
27 | */
28 | private $parser;
29 |
30 | /**
31 | * Set up
32 | */
33 | public function setUp(): void
34 | {
35 | $this->mock_function('sys_get_temp_dir', fn() => TEST_STATICS);
36 | $this->mock_function('shell_exec', fn() => "/some/dir/drafter\n");
37 |
38 | $this->parser = $this->getMockBuilder('\PHPDraft\In\ApibFileParser')
39 | ->disableOriginalConstructor()
40 | ->getMock();
41 | $this->class = $this->getMockBuilder('\PHPDraft\Parse\BaseParser')
42 | ->getMockForAbstractClass();
43 |
44 | $this->parser->set_apib_content(file_get_contents(TEST_STATICS . '/drafter/apib/index.apib'));
45 |
46 | $this->class->init($this->parser);
47 | $this->baseSetUp($this->class);
48 |
49 | $this->unmock_function('shell_exec');
50 | $this->unmock_function('sys_get_temp_dir');
51 | }
52 |
53 | /**
54 | * Tear down
55 | */
56 | public function tearDown(): void
57 | {
58 | unset($this->class);
59 | unset($this->reflection);
60 | unset($this->parser);
61 | }
62 |
63 | /**
64 | * Test if the value the class is initialized with is correct
65 | *
66 | * @covers \PHPDraft\Parse\BaseParser::parseToJson()
67 | */
68 | public function testSetupCorrectly(): void
69 | {
70 | $this->assertInstanceOf('\PHPDraft\In\ApibFileParser', $this->get_reflection_property_value('apib'));
71 | }
72 |
73 | /**
74 | * Check if parsing the APIB to JSON gives the expected result
75 | *
76 | * @covers \PHPDraft\Parse\BaseParser::parseToJson()
77 | */
78 | public function testParseToJSON(): void
79 | {
80 | $this->class->expects($this->once())
81 | ->method('parse');
82 |
83 | $this->class->json = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'));
84 | $this->class->parseToJson();
85 | $this->assertEquals(json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json')), $this->class->json);
86 | }
87 |
88 | /**
89 | * Check if parsing the APIB to JSON gives the expected result
90 | *
91 | * @covers \PHPDraft\Parse\BaseParser::parseToJson()
92 | */
93 | public function testParseToJSONMkDir(): void
94 | {
95 | $this->class->expects($this->once())
96 | ->method('parse');
97 |
98 | $this->class->json = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'));
99 | $this->class->parseToJson();
100 | $this->assertEquals(json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json')), $this->class->json);
101 | }
102 |
103 | /**
104 | * Check if parsing the APIB to JSON gives the expected result
105 | *
106 | * @covers \PHPDraft\Parse\BaseParser::parseToJson()
107 | */
108 | public function testParseToJSONMkTmp(): void
109 | {
110 | $tmp_dir = dirname(TEST_STATICS, 2) . '/build/tmp';
111 | if (file_exists($tmp_dir . DIRECTORY_SEPARATOR . 'index.apib')) {
112 | unlink($tmp_dir . DIRECTORY_SEPARATOR . 'index.apib');
113 | }
114 | if (file_exists($tmp_dir)) {
115 | rmdir($tmp_dir);
116 | }
117 |
118 | $this->set_reflection_property_value('tmp_dir', $tmp_dir);
119 |
120 | $this->class->expects($this->once())
121 | ->method('parse');
122 |
123 | $this->class->json = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'));
124 | $this->class->parseToJson();
125 | $this->assertDirectoryExists($tmp_dir);
126 | $this->assertEquals(json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json')), $this->class->json);
127 | }
128 |
129 | /**
130 | * Check if parsing the fails when invalid JSON
131 | *
132 | * @covers \PHPDraft\Parse\BaseParser::parseToJson()
133 | */
134 | public function testParseToJSONWithInvalidJSON(): void
135 | {
136 | $this->class->expects($this->once())
137 | ->method('parse');
138 |
139 | $this->expectException('\PHPDraft\Parse\ExecutionException');
140 | $this->expectExceptionMessage('Drafter generated invalid JSON (ERROR)');
141 | $this->expectExceptionCode(2);
142 |
143 | $this->mock_function('json_last_error', fn() => JSON_ERROR_DEPTH);
144 | $this->mock_function('json_last_error_msg', fn() => "ERROR");
145 | $this->class->parseToJson();
146 | $this->expectOutputString('ERROR: invalid json in /tmp/drafter/index.json');
147 | $this->unmock_function('json_last_error_msg');
148 | $this->unmock_function('json_last_error');
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/Tests/DrafterAPITest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Parse\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\In\ApibFileParser;
14 | use PHPDraft\Parse\DrafterAPI;
15 | use PHPUnit\Framework\MockObject\MockObject;
16 | use ReflectionClass;
17 |
18 | /**
19 | * Class DrafterAPITest
20 | * @covers \PHPDraft\Parse\DrafterAPI
21 | */
22 | class DrafterAPITest extends LunrBaseTest
23 | {
24 | /**
25 | * Shared instance of the file parser.
26 | *
27 | * @var ApibFileParser|MockObject
28 | */
29 | private mixed $parser;
30 |
31 | private DrafterAPI $class;
32 |
33 | /**
34 | * Basic setup
35 | */
36 | public function setUp(): void
37 | {
38 | $this->mock_function('sys_get_temp_dir', fn() => TEST_STATICS);
39 |
40 | $this->parser = $this->getMockBuilder('\PHPDraft\In\ApibFileParser')
41 | ->disableOriginalConstructor()
42 | ->getMock();
43 |
44 | $this->parser->set_apib_content(file_get_contents(TEST_STATICS . '/drafter/apib/index.apib'));
45 |
46 | $this->class = new DrafterAPI();
47 | $this->baseSetUp($this->class);
48 |
49 | $this->class->init($this->parser);
50 |
51 | $this->unmock_function('sys_get_temp_dir');
52 | }
53 |
54 | /**
55 | * Tear down
56 | */
57 | public function tearDown(): void
58 | {
59 | parent::tearDown();
60 | unset($this->parser);
61 | }
62 |
63 | /**
64 | * Test if the value the class is initialized with is correct
65 | *
66 | * @covers \PHPDraft\Parse\DrafterAPI::parseToJson()
67 | */
68 | public function testSetupCorrectly(): void
69 | {
70 | $this->assertInstanceOf('\PHPDraft\In\ApibFileParser', $this->get_reflection_property_value('apib'));
71 | }
72 |
73 | /**
74 | * Test if the drafter api can be used
75 | *
76 | * @covers \PHPDraft\Parse\DrafterAPI::parseToJson()
77 | */
78 | public function testAvailableFails(): void
79 | {
80 | $this->mock_function('curl_exec', fn() => "/some/dir/drafter\n");
81 | $this->mock_function('curl_errno', fn() => 1);
82 |
83 | $this->assertFalse(DrafterAPI::available());
84 |
85 | $this->unmock_function('curl_errno');
86 | $this->unmock_function('curl_exec');
87 | }
88 |
89 | /**
90 | * Test if the drafter api can be used
91 | *
92 | * @covers \PHPDraft\Parse\DrafterAPI::parseToJson()
93 | */
94 | public function testAvailableSuccess(): void
95 | {
96 | $this->mock_function('curl_exec', fn() => "/some/dir/drafter\n");
97 | $this->mock_function('curl_errno', fn() => 0);
98 |
99 | $this->assertFalse(DrafterAPI::available());
100 |
101 | $this->unmock_function('curl_errno');
102 | $this->unmock_function('curl_exec');
103 | }
104 |
105 | /**
106 | * Check if parsing the fails without drafter
107 | *
108 | * @covers \PHPDraft\Parse\DrafterAPI::parseToJson()
109 | */
110 | public function testParseWithFailingWebservice(): void
111 | {
112 | $this->expectException('\PHPDraft\Parse\ResourceException');
113 | $this->expectExceptionMessage('Drafter webservice failed to parse input');
114 | $this->expectExceptionCode(1);
115 |
116 | $this->mock_function('curl_errno', fn() => 1);
117 | $this->class->parseToJson();
118 | $this->unmock_function('curl_errno');
119 | }
120 |
121 | /**
122 | * Check if parsing the succeeds
123 | *
124 | * @covers \PHPDraft\Parse\DrafterAPI::parseToJson()
125 | */
126 | public function testParseSuccess(): void
127 | {
128 | $this->mock_function('json_last_error', fn() => 0);
129 | $this->mock_function('curl_errno', fn() => 0);
130 | $this->mock_function('curl_exec', fn() => '{"content":[{"element":"world"}]}');
131 |
132 | $this->class->parseToJson();
133 |
134 | $this->unmock_function('curl_exec');
135 | $this->unmock_function('curl_errno');
136 | $this->unmock_function('json_last_error');
137 |
138 | $obj = (object)[];
139 | $obj2 = (object)[];
140 | $obj2->element = 'world';
141 | $obj->content = [ $obj2 ];
142 | $this->assertEquals($obj, $this->class->json);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/Tests/DrafterTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Parse\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\In\ApibFileParser;
14 | use PHPDraft\Parse\Drafter;
15 | use PHPDraft\Parse\ExecutionException;
16 | use PHPUnit\Framework\MockObject\MockObject;
17 | use ReflectionClass;
18 |
19 | /**
20 | * Class DrafterTest
21 | *
22 | * @covers \PHPDraft\Parse\Drafter
23 | */
24 | class DrafterTest extends LunrBaseTest
25 | {
26 | /**
27 | * Shared instance of the file parser.
28 | *
29 | * @var ApibFileParser|MockObject
30 | */
31 | private mixed $parser;
32 |
33 | /**
34 | * Set up
35 | */
36 | public function setUp(): void
37 | {
38 | $this->mock_function('sys_get_temp_dir', fn() => TEST_STATICS);
39 | $this->mock_function('shell_exec', fn() => "/some/dir/drafter\n");
40 |
41 | $this->parser = $this->getMockBuilder('\PHPDraft\In\ApibFileParser')
42 | ->disableOriginalConstructor()
43 | ->getMock();
44 |
45 | $this->parser->set_apib_content(file_get_contents(TEST_STATICS . '/drafter/apib/index.apib'));
46 |
47 | $this->class = new Drafter();
48 | $this->baseSetUp($this->class);
49 |
50 | $this->class->init($this->parser);
51 |
52 | $this->unmock_function('shell_exec');
53 | $this->unmock_function('sys_get_temp_dir');
54 | }
55 |
56 | /**
57 | * Tear down
58 | */
59 | public function tearDown(): void
60 | {
61 | if (file_exists(TEST_STATICS . '/drafter/index.json')) {
62 | unlink(TEST_STATICS . '/drafter/index.json');
63 | }
64 | if (file_exists(TEST_STATICS . '/drafter/index.apib')) {
65 | unlink(TEST_STATICS . '/drafter/index.apib');
66 | }
67 | unset($this->class);
68 | unset($this->reflection);
69 | unset($this->parser);
70 | }
71 |
72 | /**
73 | * Test if the value the class is initialized with is correct
74 | *
75 | * @covers \PHPDraft\Parse\Drafter::parseToJson()
76 | */
77 | public function testSetupCorrectly(): void
78 | {
79 | $this->assertInstanceOf('\PHPDraft\In\ApibFileParser', $this->get_reflection_property_value('apib'));
80 | }
81 |
82 | /**
83 | * Check if parsing the APIB to JSON gives the expected result
84 | */
85 | public function testParseToJSON(): void
86 | {
87 | $this->mock_function('json_last_error', fn() => JSON_ERROR_NONE);
88 | $this->mock_function('shell_exec', fn() => "");
89 | file_put_contents(
90 | TEST_STATICS . '/drafter/index.json',
91 | file_get_contents(TEST_STATICS . '/drafter/json/index.json')
92 | );
93 | $this->class->parseToJson();
94 | $this->assertEquals(
95 | json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json')),
96 | $this->class->json
97 | );
98 | $this->unmock_function('shell_exec');
99 | $this->unmock_function('json_last_error');
100 | }
101 |
102 | /**
103 | * Check if parsing the APIB to JSON gives the expected result with inheritance
104 | *
105 | * @covers \PHPDraft\Parse\Drafter::parseToJson()
106 | */
107 | public function testParseToJSONInheritance(): void
108 | {
109 | $this->mock_function('json_last_error', fn() => JSON_ERROR_NONE);
110 | $this->mock_function('shell_exec', fn() => '');
111 | file_put_contents(
112 | TEST_STATICS . '/drafter/index.json',
113 | file_get_contents(TEST_STATICS . '/drafter/json/inheritance.json')
114 | );
115 | $this->class->parseToJson();
116 | $this->assertEquals(
117 | json_decode(file_get_contents(TEST_STATICS . '/drafter/json/inheritance.json')),
118 | $this->class->json
119 | );
120 | $this->unmock_function('shell_exec');
121 | $this->unmock_function('json_last_error');
122 | }
123 |
124 | /**
125 | * Check if parsing the APIB to JSON gives the expected result
126 | *
127 | * @covers \PHPDraft\Parse\Drafter::parseToJson()
128 | */
129 | public function testParseToJSONWithErrors(): void
130 | {
131 | $this->expectException('\PHPDraft\Parse\ExecutionException');
132 | $this->expectExceptionMessage('Parsing encountered errors and stopped');
133 | $this->expectExceptionCode(2);
134 |
135 | $this->mock_function('shell_exec', fn() => '');
136 | file_put_contents(
137 | TEST_STATICS . '/drafter/index.json',
138 | file_get_contents(TEST_STATICS . '/drafter/json/error.json')
139 | );
140 | $this->class->parseToJson();
141 | $this->expectOutputString("WARNING: ignoring unrecognized block\nWARNING: no headers specified\nWARNING: ignoring unrecognized block\nWARNING: empty request message-body");
142 | $this->unmock_function('shell_exec');
143 | }
144 |
145 | /**
146 | * Check if parsing the fails without drafter
147 | *
148 | * @covers \PHPDraft\Parse\Drafter::available()
149 | */
150 | public function testSetupWithoutDrafter(): void
151 | {
152 | $this->mock_function('shell_exec', fn() => '');
153 | $this->assertFalse(Drafter::available());
154 | $this->unmock_function('shell_exec');
155 | }
156 |
157 | /**
158 | * Check if parsing the fails when invalid JSON
159 | *
160 | * @covers \PHPDraft\Parse\Drafter::parseToJson()
161 | */
162 | public function testParseToJSONWithInvalidJSON(): void
163 | {
164 | $this->expectException('\PHPDraft\Parse\ExecutionException');
165 | $this->expectExceptionMessage('Drafter generated invalid JSON (ERROR)');
166 | $this->expectExceptionCode(2);
167 |
168 | $this->mock_function('json_last_error', fn() => JSON_ERROR_DEPTH);
169 | $this->mock_function('json_last_error_msg', fn() => 'ERROR');
170 | file_put_contents(TEST_STATICS . '/drafter/index.json', '["hello: \'world}');
171 | $this->class->parseToJson();
172 | $this->expectOutputString('ERROR: invalid json in /tmp/drafter/index.json');
173 | $this->unmock_function('json_last_error_msg');
174 | $this->unmock_function('json_last_error');
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/PHPDraft/Parse/Tests/HtmlGeneratorTest.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace PHPDraft\Parse\Tests;
11 |
12 | use Lunr\Halo\LunrBaseTest;
13 | use PHPDraft\Parse\HtmlGenerator;
14 | use ReflectionClass;
15 |
16 | /**
17 | * Class JsonToHTMLTest
18 | * @covers \PHPDraft\Parse\HtmlGenerator
19 | */
20 | class HtmlGeneratorTest extends LunrBaseTest
21 | {
22 | /**
23 | * Test Class
24 | * @var HtmlGenerator
25 | */
26 | protected HtmlGenerator $class;
27 |
28 | /**
29 | * Set up
30 | * @requires ext-uopz
31 | */
32 | public function setUp(): void
33 | {
34 | define('ID_STATIC', 'SOME_ID');
35 | $data = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'));
36 | $this->class = new HtmlGenerator();
37 |
38 | $this->baseSetUp($this->class);
39 | $this->class->init($data);
40 |
41 | $this->class->sorting = -1;
42 | }
43 |
44 | /**
45 | * Tear down
46 | */
47 | public function tearDown(): void
48 | {
49 | $this->constant_undefine('ID_STATIC');
50 | unset($this->class);
51 | unset($this->reflection);
52 | }
53 |
54 | /**
55 | * Tests if the constructor sets the property correctly
56 | *
57 | * @requires ext-uopz
58 | */
59 | public function testSetupCorrectly(): void
60 | {
61 | $json = json_decode(file_get_contents(TEST_STATICS . '/drafter/json/index.json'));
62 | $this->assertEquals($json, $this->get_reflection_property_value('object'));
63 | }
64 |
65 | /**
66 | * Tests if the constructor sets the property correctly
67 | * @requires ext-uopz
68 | */
69 | public function testGetHTML(): void
70 | {
71 | $this->class->build_html();
72 | if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
73 | $this->assertStringEqualsFile(TEST_STATICS . '/drafter/html/basic.html', $this->class->__toString());
74 | } else {
75 | $this->assertStringEqualsFile(TEST_STATICS . '/drafter/html/basic_old.html', $this->class->__toString());
76 | }
77 | }
78 |
79 | /**
80 | * Tests if the constructor sets the property correctly
81 | * @requires ext-uopz
82 | */
83 | public function testGetHTMLMaterial(): void
84 | {
85 | $this->class->build_html('material');
86 | if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
87 | $this->assertStringEqualsFile(TEST_STATICS . '/drafter/html/material.html', $this->class->__toString());
88 | } else {
89 | $this->assertStringEqualsFile(TEST_STATICS . '/drafter/html/material_old.html', $this->class->__toString());
90 | }
91 | }
92 |
93 | /**
94 | * Tests if the constructor sets the property correctly
95 | * @requires ext-uopz
96 | */
97 | public function testGetHTMLAdvanced(): void
98 | {
99 | $this->class->build_html('material', 'img.jpg', 'test.css,index.css', 'index.js,test.js');
100 |
101 | $this->assertMatchesRegularExpression('/ /', $this->class->__toString());
102 | $this->assertMatchesRegularExpression('/