├── tests
├── bootstrap.php
├── Functional
│ ├── expected
│ │ ├── classes
│ │ │ └── PhpDocumentorMarkdown
│ │ │ │ └── Example
│ │ │ │ ├── Arrayable.md
│ │ │ │ ├── AbstractProduct.md
│ │ │ │ ├── ReviewableTrait.md
│ │ │ │ ├── Pizza
│ │ │ │ ├── Sauce.md
│ │ │ │ └── Base.md
│ │ │ │ ├── ProductInterface.md
│ │ │ │ ├── ManyInterfaces.md
│ │ │ │ └── Pizza.md
│ │ ├── functions
│ │ │ ├── getDatabaseConfig.md
│ │ │ └── mockFunctionWithParameters.md
│ │ └── Home.md
│ ├── FunctionalTestCase.php
│ ├── PhpdocOutputTest.php
│ └── Service
│ │ └── MarkdownGeneratorService.php
└── Unit
│ └── Twig
│ ├── templates
│ └── macros.test.twig
│ ├── UnitTestCase.php
│ └── Macro
│ ├── MacroTestCase.php
│ └── MacroTest.php
├── themes
└── markdown
│ ├── partials
│ ├── footer.md.twig
│ ├── toc.md.twig
│ ├── header.md.twig
│ ├── description.md.twig
│ ├── tags.md.twig
│ └── inheritance.md.twig
│ ├── template.xml
│ ├── property.md.twig
│ ├── function.md.twig
│ ├── trait.md.twig
│ ├── interface.md.twig
│ ├── method.md.twig
│ ├── class.md.twig
│ ├── index.md.twig
│ └── include
│ └── macros.twig
├── .gitignore
├── src
└── Example
│ ├── Arrayable.php
│ ├── ManyInterfaces.php
│ ├── AbstractProduct.php
│ ├── ReviewableTrait.php
│ ├── Pizza
│ ├── Sauce.php
│ └── Base.php
│ ├── ProductInterface.php
│ ├── functions.php
│ └── Pizza.php
├── .wiki
├── classes
│ └── PhpDocumentorMarkdown
│ │ └── Example
│ │ ├── Arrayable.md
│ │ ├── AbstractProduct.md
│ │ ├── ReviewableTrait.md
│ │ ├── Pizza
│ │ ├── Sauce.md
│ │ └── Base.md
│ │ ├── ProductInterface.md
│ │ ├── ManyInterfaces.md
│ │ └── Pizza.md
├── functions
│ ├── getDatabaseConfig.md
│ └── mockFunctionWithParameters.md
└── Home.md
├── patches
└── phpdocumentor-resource-path.patch
├── .github
└── workflows
│ ├── docs.yml
│ └── test.yml
├── phpunit.dist.xml
├── phpdoc.dist.xml
├── LICENSE
├── composer.json
├── CHANGELOG.md
└── README.md
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | Automatically generated on {{ "now"|date("Y-m-d") }}
3 |
--------------------------------------------------------------------------------
/themes/markdown/partials/toc.md.twig:
--------------------------------------------------------------------------------
1 | {# GitLab supports automatic table of contents generation #}
2 | {% if parameter.markdownFlavour == 'gitlab' %}
3 |
4 | ## Table of contents
5 |
6 | [[_TOC_]]
7 | {% endif %}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # PhpDocumentor
2 | .phpdoc/
3 | phpdoc.xml
4 | ast.dump
5 | var/
6 | .tests
7 |
8 | # PHPUnit
9 | phpunit.xml
10 | .phpunit*
11 |
12 | # IDE
13 | .idea
14 |
15 | # Composer
16 | /vendor/
17 |
18 | # Misc
19 | tmp/
20 |
--------------------------------------------------------------------------------
/src/Example/Arrayable.php:
--------------------------------------------------------------------------------
1 | name = $name;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Generate docs
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | generate-docs:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v2
16 |
17 | - name: Set up environment
18 | run: |
19 | cp phpdoc.dist.xml phpdoc.xml
20 | wget https://phpdoc.org/phpDocumentor.phar
21 | chmod +x phpDocumentor.phar
22 |
23 | - name: Generate documentation
24 | run: ./phpDocumentor.phar
25 |
--------------------------------------------------------------------------------
/themes/markdown/partials/tags.md.twig:
--------------------------------------------------------------------------------
1 | {% set sees = tags.see | default([]) | filter(see => see.reference | trim) %}
2 | {% set links = tags.link | default([]) | filter(link => link.link | trim) %}
3 | {% if sees is not empty or links is not empty %}
4 |
5 | **See Also:**
6 |
7 | {% for see in sees %}
8 | * {{ see.reference }}{% if see.description | trim %} - {{ see.description | raw }}{% endif %}{{- '\n' -}}
9 | {% endfor %}
10 | {% for link in links %}
11 | * {{ link.link }}{% if link.description | trim and link.description != link.link %} - {{ link.description | raw }}{% endif %}{{- '\n' -}}
12 | {% endfor %}
13 | {% endif %}
--------------------------------------------------------------------------------
/themes/markdown/partials/inheritance.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {%- for otherNode in others -%}
3 | {%- if loop.index0 > 0 -%}
4 | {{- ',\n ' -}}
5 | {%- else -%}
6 | {{- ' ' -}}
7 | {%- endif -%}
8 | {%- if '0' == macros.mdNestingLevel(otherNode.FullyQualifiedStructuralElementName) -%}
9 | {{- '`' ~ otherNode.FullyQualifiedStructuralElementName | default(otherNode.name) ~ '`' -}}
10 | {%- else -%}
11 | {{- macros.mdLink(otherNode, macros.mdClassPath(node), otherNode.FullyQualifiedStructuralElementName, 'class') -}}
12 | {%- endif -%}
13 | {%- endfor -%}
14 |
--------------------------------------------------------------------------------
/phpunit.dist.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ./tests/Unit
5 |
6 |
7 | ./tests/Functional
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/ReviewableTrait.md:
--------------------------------------------------------------------------------
1 | # ReviewableTrait
2 |
3 | A trait for reviewable objects.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\ReviewableTrait`
8 |
9 | ## Constants
10 |
11 | | Constant | Visibility | Type | Value |
12 | |--------------|------------|------|-------|
13 | | `REVIEWABLE` | public | | true |
14 |
15 | ## Properties
16 |
17 | ### reviews
18 |
19 | ```php
20 | public static array $reviews
21 | ```
22 |
23 | * This property is **static**.
24 |
25 | ***
26 |
27 | ## Methods
28 |
29 | ### isReviewed
30 |
31 | Whether the object has been reviewed.
32 |
33 | ```php
34 | public isReviewed(): bool
35 | ```
36 |
37 | ***
38 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/ReviewableTrait.md:
--------------------------------------------------------------------------------
1 | # ReviewableTrait
2 |
3 | A trait for reviewable objects.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\ReviewableTrait`
8 |
9 | ## Constants
10 |
11 | | Constant | Visibility | Type | Value |
12 | |--------------|------------|------|-------|
13 | | `REVIEWABLE` | public | | true |
14 |
15 | ## Properties
16 |
17 | ### reviews
18 |
19 | ```php
20 | public static array $reviews
21 | ```
22 |
23 | * This property is **static**.
24 |
25 | ***
26 |
27 | ## Methods
28 |
29 | ### isReviewed
30 |
31 | Whether the object has been reviewed.
32 |
33 | ```php
34 | public isReviewed(): bool
35 | ```
36 |
37 | ***
38 |
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/Pizza/Sauce.md:
--------------------------------------------------------------------------------
1 | # Sauce
2 |
3 | Pizza sauce class.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza\Sauce`
8 |
9 | ## Properties
10 |
11 | ### name
12 |
13 | Sauce name.
14 |
15 | ```php
16 | protected string $name
17 | ```
18 |
19 | ***
20 |
21 | ### tomatoSupplier
22 |
23 | Tomato supplier.
24 |
25 | ```php
26 | public static string $tomatoSupplier
27 | ```
28 |
29 | * This property is **static**.
30 |
31 | ***
32 |
33 | ## Methods
34 |
35 | ### __construct
36 |
37 | ```php
38 | public __construct(string $name): mixed
39 | ```
40 |
41 | **Parameters:**
42 |
43 | | Parameter | Type | Description |
44 | |-----------|------------|-------------|
45 | | `$name` | **string** | |
46 |
47 | ***
48 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/Pizza/Sauce.md:
--------------------------------------------------------------------------------
1 | # Sauce
2 |
3 | Pizza sauce class.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza\Sauce`
8 |
9 | ## Properties
10 |
11 | ### name
12 |
13 | Sauce name.
14 |
15 | ```php
16 | protected string $name
17 | ```
18 |
19 | ***
20 |
21 | ### tomatoSupplier
22 |
23 | Tomato supplier.
24 |
25 | ```php
26 | public static string $tomatoSupplier
27 | ```
28 |
29 | * This property is **static**.
30 |
31 | ***
32 |
33 | ## Methods
34 |
35 | ### __construct
36 |
37 | ```php
38 | public __construct(string $name): mixed
39 | ```
40 |
41 | **Parameters:**
42 |
43 | | Parameter | Type | Description |
44 | |-----------|------------|-------------|
45 | | `$name` | **string** | |
46 |
47 | ***
48 |
--------------------------------------------------------------------------------
/src/Example/ProductInterface.php:
--------------------------------------------------------------------------------
1 | getenv('MYSQL_DATABASE') ?: 'pizza',
27 | 'DB_USER' => getenv('MYSQL_USER'),
28 | 'DB_PASSWORD' => getenv('MYSQL_PASSWORD'),
29 | 'DB_HOST' => getenv('MYSQL_HOST'),
30 | ];
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | test:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - name: Set up PHP with Xdebug
21 | uses: shivammathur/setup-php@v2
22 | with:
23 | php-version: '8.2'
24 | coverage: xdebug
25 |
26 | - name: Validate composer.json and composer.lock
27 | run: composer validate --strict
28 |
29 | - name: Cache Composer packages
30 | id: composer-cache
31 | uses: actions/cache@v3
32 | with:
33 | path: vendor
34 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
35 | restore-keys: |
36 | ${{ runner.os }}-php-
37 |
38 | - name: Install dependencies
39 | run: composer install --prefer-dist --no-progress
40 |
41 | - name: Run test suite
42 | run: vendor/bin/phpunit
43 |
--------------------------------------------------------------------------------
/themes/markdown/template.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | markdown
4 | Sakri Koskimies
5 | sakri.koskimies@hotmail.com
6 | 1.0.0
7 |
8 |
9 |
11 |
13 |
15 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/templates/macros.test.twig:
--------------------------------------------------------------------------------
1 | {% import (relativeIncludePath ~ '/macros.twig') as macros %}
2 | {% if key == 'mdEsc' %}
3 | {{- macros.mdEsc(args[0]) -}}
4 | {% endif %}
5 | {% if key == 'mdGetRelativePath' %}
6 | {{- macros.mdGetRelativePath(args[0], args[1]) -}}
7 | {% endif %}
8 | {% if key == 'mdNodePath' %}
9 | {{- macros.mdNodePath(args[0]) -}}
10 | {% endif %}
11 | {% if key == 'mdClassPath' %}
12 | {{- macros.mdClassPath(args[0]) -}}
13 | {% endif %}
14 | {% if key == 'mdFunctionPath' %}
15 | {{- macros.mdFunctionPath(args[0]) -}}
16 | {% endif %}
17 | {% if key == 'mdLink' %}
18 | {{- macros.mdLink(args[0], args[1], args[2], args[3]) -}}
19 | {% endif %}
20 | {% if key == 'mdRepeat' %}
21 | {{- macros.mdRepeat(args[0], args[1]) -}}
22 | {% endif %}
23 | {% if key == 'mdPadRight' %}
24 | {{- macros.mdPadRight(args[0], args[1]) -}}
25 | {% endif %}
26 | {% if key == 'mdTable' %}
27 | {{- macros.mdTable(args[0], args[1]) -}}
28 | {% endif %}
29 | {% if key == 'mdNestingLevel' %}
30 | {{- macros.mdNestingLevel(args[0]) -}}
31 | {% endif %}
32 |
--------------------------------------------------------------------------------
/phpdoc.dist.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 | Pizza Place (Example documentation)
8 |
9 |
10 |
11 |
12 |
13 |
14 | src/Example
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/themes/markdown/property.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | ### {{ property.name }}
4 | {% if property.summary | trim %}
5 |
6 | {{ macros.mdEsc(property.summary) | raw }}
7 | {% endif %}
8 |
9 | ```php
10 | {{ property.visibility ~ ' ' }}{% if property.static %}{{ 'static' ~ ' ' }}{% endif %}{% if property.type and property.type is not empty %}{{ property.type ~ ' ' }}{% endif %}{{ '$' ~ property.name }}
11 | ```
12 | {% include 'partials/description.md.twig' with { node: property } %}
13 | {% if property.static or property.deprecated %}
14 |
15 | {% if property.static %}* This property is **static**.
16 | {% endif %}
17 | {% if property.deprecated %}* **Warning:** this property is **deprecated**. This means that this property will likely be removed in a future version.
18 | {% endif %}
19 | {% endif %}
20 | {% if property.type and property.type.classes %}
21 |
22 | **Type:** {% for typeClass in property.type.classes %}{{ macros.mdLink(typeClass, path, null, 'class') }}{% if not loop.last %} | {% endif %}{% endfor %}
23 | {% endif %}
24 | {% include 'partials/tags.md.twig' with { tags: property.tags } only %}
25 |
26 | ***
27 | {% endautoescape %}
--------------------------------------------------------------------------------
/tests/Unit/Twig/UnitTestCase.php:
--------------------------------------------------------------------------------
1 | testTemplatePath = __DIR__ . '/templates';
24 | $this->relativeIncludePath = 'themes/markdown/include';
25 | }
26 |
27 | /**
28 | * Get the path to test templates.
29 | *
30 | * @return string
31 | */
32 | public function getTestTemplatePath(): string
33 | {
34 | return $this->testTemplatePath;
35 | }
36 |
37 | /**
38 | * Get the relative path to production template include dir.
39 | *
40 | * @return string
41 | */
42 | public function getRelativeIncludePath(): string
43 | {
44 | return $this->relativeIncludePath;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright: (c) 2022-present Sakri Koskimies
4 | (c) 2020 Dejan Markic
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/Example/Pizza/Base.php:
--------------------------------------------------------------------------------
1 | sauce = $sauce;
37 | $this->yeast = $yeast;
38 | }
39 |
40 | /**
41 | * @return Sauce
42 | */
43 | public function getSauce(): Sauce
44 | {
45 | return $this->sauce;
46 | }
47 |
48 | /**
49 | * @return int
50 | * @throws Exception
51 | */
52 | public function getYeast(): int
53 | {
54 | return $this->yeast;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/ProductInterface.md:
--------------------------------------------------------------------------------
1 | # ProductInterface
2 |
3 | Interface for a product.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\ProductInterface`
8 | * Parent interfaces:
9 | [`\PhpDocumentorMarkdown\Example\Arrayable`](./Arrayable.md)
10 |
11 | ## Methods
12 |
13 | ### __construct
14 |
15 | ```php
16 | public __construct(string $name, float $price): mixed
17 | ```
18 |
19 | **Parameters:**
20 |
21 | | Parameter | Type | Description |
22 | |-----------|------------|----------------|
23 | | `$name` | **string** | Product name. |
24 | | `$price` | **float** | Product price. |
25 |
26 | ***
27 |
28 | ### getName
29 |
30 | Get the name of the product.
31 |
32 | ```php
33 | public getName(): string
34 | ```
35 |
36 | ***
37 |
38 | ### getPrice
39 |
40 | Get the price of the product.
41 |
42 | ```php
43 | public getPrice(): float
44 | ```
45 |
46 | ***
47 |
48 | ### getTaxRate
49 |
50 | Get the tax rate for this product.
51 |
52 | ```php
53 | public getTaxRate(): float
54 | ```
55 |
56 | ***
57 |
58 | ## Inherited methods
59 |
60 | ### toArray
61 |
62 | Get the instance as an array.
63 |
64 | ```php
65 | public toArray(): array
66 | ```
67 |
68 | ***
69 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/ProductInterface.md:
--------------------------------------------------------------------------------
1 | # ProductInterface
2 |
3 | Interface for a product.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\ProductInterface`
8 | * Parent interfaces:
9 | [`\PhpDocumentorMarkdown\Example\Arrayable`](./Arrayable)
10 |
11 | ## Methods
12 |
13 | ### __construct
14 |
15 | ```php
16 | public __construct(string $name, float $price): mixed
17 | ```
18 |
19 | **Parameters:**
20 |
21 | | Parameter | Type | Description |
22 | |-----------|------------|----------------|
23 | | `$name` | **string** | Product name. |
24 | | `$price` | **float** | Product price. |
25 |
26 | ***
27 |
28 | ### getName
29 |
30 | Get the name of the product.
31 |
32 | ```php
33 | public getName(): string
34 | ```
35 |
36 | ***
37 |
38 | ### getPrice
39 |
40 | Get the price of the product.
41 |
42 | ```php
43 | public getPrice(): float
44 | ```
45 |
46 | ***
47 |
48 | ### getTaxRate
49 |
50 | Get the tax rate for this product.
51 |
52 | ```php
53 | public getTaxRate(): float
54 | ```
55 |
56 | ***
57 |
58 | ## Inherited methods
59 |
60 | ### toArray
61 |
62 | Get the instance as an array.
63 |
64 | ```php
65 | public toArray(): array
66 | ```
67 |
68 | ***
69 |
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/ManyInterfaces.md:
--------------------------------------------------------------------------------
1 | # ManyInterfaces
2 |
3 | A ManyInterfaces
4 |
5 | ManyInterfaces description
6 |
7 | - **See:** \PhpDocumentorMarkdown\Example\AbstractProduct
8 |
9 | ***
10 |
11 | * Full name: `\PhpDocumentorMarkdown\Example\ManyInterfaces`
12 | * Parent interfaces:
13 | [`\PhpDocumentorMarkdown\Example\ProductInterface`](./ProductInterface.md),
14 | `JsonSerializable`
15 |
16 | ## Inherited methods
17 |
18 | ### toArray
19 |
20 | Get the instance as an array.
21 |
22 | ```php
23 | public toArray(): array
24 | ```
25 |
26 | ***
27 |
28 | ### __construct
29 |
30 | ```php
31 | public __construct(string $name, float $price): mixed
32 | ```
33 |
34 | **Parameters:**
35 |
36 | | Parameter | Type | Description |
37 | |-----------|------------|----------------|
38 | | `$name` | **string** | Product name. |
39 | | `$price` | **float** | Product price. |
40 |
41 | ***
42 |
43 | ### getName
44 |
45 | Get the name of the product.
46 |
47 | ```php
48 | public getName(): string
49 | ```
50 |
51 | ***
52 |
53 | ### getPrice
54 |
55 | Get the price of the product.
56 |
57 | ```php
58 | public getPrice(): float
59 | ```
60 |
61 | ***
62 |
63 | ### getTaxRate
64 |
65 | Get the tax rate for this product.
66 |
67 | ```php
68 | public getTaxRate(): float
69 | ```
70 |
71 | ***
72 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/ManyInterfaces.md:
--------------------------------------------------------------------------------
1 | # ManyInterfaces
2 |
3 | A ManyInterfaces
4 |
5 | ManyInterfaces description
6 |
7 | - **See:** \PhpDocumentorMarkdown\Example\AbstractProduct
8 |
9 | ***
10 |
11 | * Full name: `\PhpDocumentorMarkdown\Example\ManyInterfaces`
12 | * Parent interfaces:
13 | [`\PhpDocumentorMarkdown\Example\ProductInterface`](./ProductInterface),
14 | `JsonSerializable`
15 |
16 | ## Inherited methods
17 |
18 | ### toArray
19 |
20 | Get the instance as an array.
21 |
22 | ```php
23 | public toArray(): array
24 | ```
25 |
26 | ***
27 |
28 | ### __construct
29 |
30 | ```php
31 | public __construct(string $name, float $price): mixed
32 | ```
33 |
34 | **Parameters:**
35 |
36 | | Parameter | Type | Description |
37 | |-----------|------------|----------------|
38 | | `$name` | **string** | Product name. |
39 | | `$price` | **float** | Product price. |
40 |
41 | ***
42 |
43 | ### getName
44 |
45 | Get the name of the product.
46 |
47 | ```php
48 | public getName(): string
49 | ```
50 |
51 | ***
52 |
53 | ### getPrice
54 |
55 | Get the price of the product.
56 |
57 | ```php
58 | public getPrice(): float
59 | ```
60 |
61 | ***
62 |
63 | ### getTaxRate
64 |
65 | Get the tax rate for this product.
66 |
67 | ```php
68 | public getTaxRate(): float
69 | ```
70 |
71 | ***
72 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "saggre/phpdocumentor-markdown",
3 | "description": "Markdown template for phpDocumentor3",
4 | "license": "MIT",
5 | "type": "library",
6 | "authors": [
7 | {
8 | "name": "Saggre",
9 | "email": "sakri.koskimies@hotmail.com"
10 | }
11 | ],
12 | "keywords": [
13 | "phpdocumentor",
14 | "phpdoc",
15 | "markdown",
16 | "ci",
17 | "ai",
18 | "documentation"
19 | ],
20 | "require": {
21 | },
22 | "require-dev": {
23 | "php": ">=8.1",
24 | "ext-json": "*",
25 | "cweagans/composer-patches": "^1.7",
26 | "phpdocumentor/phpdocumentor": "3.8.0",
27 | "phpunit/phpunit": "^10",
28 | "symfony/filesystem": "^6.4"
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "PhpDocumentorMarkdown\\": "src/"
33 | }
34 | },
35 | "autoload-dev": {
36 | "psr-4": {
37 | "PhpDocumentorMarkdown\\Test\\": "tests/"
38 | }
39 | },
40 | "scripts": {
41 | "test": "vendor/bin/phpunit",
42 | "create-docs": "phpdoc",
43 | "create-docs:debug": "phpdoc -vvv"
44 | },
45 | "config": {
46 | "sort-packages": true,
47 | "allow-plugins": {
48 | "cweagans/composer-patches": true
49 | }
50 | },
51 | "extra": {
52 | "patches": {
53 | "phpdocumentor/phpdocumentor": [
54 | "./patches/phpdocumentor-resource-path.patch"
55 | ]
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/themes/markdown/function.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | {% block content %}
4 | {% include 'partials/header.md.twig' with {
5 | node: node,
6 | titleSuffix: '()'
7 | } %}
8 |
9 | ***
10 |
11 | {% if node.deprecated %}* **Warning:** this function is **deprecated**. This means that this function will likely be removed in a future version.{% endif %}
12 | * Full name: `{{ node.name }}`
13 | * Defined in: `{{ node.file.path }}`
14 | {% include 'partials/tags.md.twig' with { tags: node.tags } only %}
15 |
16 | ## Parameters
17 |
18 | {% if node.arguments is not empty %}
19 | {% set rows = [] %}
20 | {% for argument in node.arguments %}
21 | {% set rows = rows | merge([{
22 | parameter: '`$' ~ argument.name ~ '`',
23 | type: argument.type ? '**' ~ argument.type ~ '**' : '',
24 | description: argument.description
25 | }]) %}
26 | {% endfor %}
27 | {{ macros.mdTable(rows, [
28 | { label: 'Parameter', key: 'parameter' },
29 | { label: 'Type', key: 'type' },
30 | { label: 'Description', key: 'description' }
31 | ]) -}}
32 | {% else %}
33 | This function has no parameters.
34 | {% endif %}
35 |
36 | ## Return Value
37 | {% if node.response %}
38 | {% if node.response.type | trim %}
39 |
40 | **{{ node.response.type }}**
41 | {% endif %}
42 | {% if node.response.description | trim %}
43 |
44 | {{ node.response.description | raw }}
45 | {% endif %}
46 | {% else %}
47 | This function does not return a value.
48 |
49 | {% endif %}
50 | {% if parameter.includeFooter == 'yes' %}
51 | {% include 'partials/footer.md.twig' %}
52 | {% endif %}
53 | {% endblock %}
54 | {% endautoescape %}
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/Pizza/Base.md:
--------------------------------------------------------------------------------
1 | # Base
2 |
3 | Represents a pizza base.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza\Base`
8 |
9 | ## Constants
10 |
11 | | Constant | Visibility | Type | Value |
12 | |---------------------------|------------|------|-------|
13 | | `YEAST_SOURDOUGH_STARTER` | private | | 0b1 |
14 | | `YEAST_FRESH` | public | | 0b10 |
15 | | `YEAST_ACTIVE_DRY` | public | | 0b11 |
16 |
17 | ## Properties
18 |
19 | ### sauce
20 |
21 | The sauce used.
22 |
23 | ```php
24 | protected \PhpDocumentorMarkdown\Example\Pizza\Sauce $sauce
25 | ```
26 |
27 | ***
28 |
29 | ### yeast
30 |
31 | Type of yeast used.
32 |
33 | ```php
34 | protected int $yeast
35 | ```
36 |
37 | ***
38 |
39 | ## Methods
40 |
41 | ### __construct
42 |
43 | ```php
44 | public __construct(\PhpDocumentorMarkdown\Example\Pizza\Sauce $sauce, int $yeast = self::YEAST_SOURDOUGH_STARTER): mixed
45 | ```
46 |
47 | **Parameters:**
48 |
49 | | Parameter | Type | Description |
50 | |-----------|------------------------------------------------|-------------|
51 | | `$sauce` | **\PhpDocumentorMarkdown\Example\Pizza\Sauce** | |
52 | | `$yeast` | **int** | |
53 |
54 | ***
55 |
56 | ### getSauce
57 |
58 | ```php
59 | public getSauce(): \PhpDocumentorMarkdown\Example\Pizza\Sauce
60 | ```
61 |
62 | ***
63 |
64 | ### getYeast
65 |
66 | ```php
67 | public getYeast(): int
68 | ```
69 |
70 | **Throws:**
71 |
72 | - [`Exception`](../../../Exception.md)
73 |
74 | ***
75 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/Pizza/Base.md:
--------------------------------------------------------------------------------
1 | # Base
2 |
3 | Represents a pizza base.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza\Base`
8 |
9 | ## Constants
10 |
11 | | Constant | Visibility | Type | Value |
12 | |---------------------------|------------|------|-------|
13 | | `YEAST_SOURDOUGH_STARTER` | private | | 0b1 |
14 | | `YEAST_FRESH` | public | | 0b10 |
15 | | `YEAST_ACTIVE_DRY` | public | | 0b11 |
16 |
17 | ## Properties
18 |
19 | ### sauce
20 |
21 | The sauce used.
22 |
23 | ```php
24 | protected \PhpDocumentorMarkdown\Example\Pizza\Sauce $sauce
25 | ```
26 |
27 | ***
28 |
29 | ### yeast
30 |
31 | Type of yeast used.
32 |
33 | ```php
34 | protected int $yeast
35 | ```
36 |
37 | ***
38 |
39 | ## Methods
40 |
41 | ### __construct
42 |
43 | ```php
44 | public __construct(\PhpDocumentorMarkdown\Example\Pizza\Sauce $sauce, int $yeast = self::YEAST_SOURDOUGH_STARTER): mixed
45 | ```
46 |
47 | **Parameters:**
48 |
49 | | Parameter | Type | Description |
50 | |-----------|------------------------------------------------|-------------|
51 | | `$sauce` | **\PhpDocumentorMarkdown\Example\Pizza\Sauce** | |
52 | | `$yeast` | **int** | |
53 |
54 | ***
55 |
56 | ### getSauce
57 |
58 | ```php
59 | public getSauce(): \PhpDocumentorMarkdown\Example\Pizza\Sauce
60 | ```
61 |
62 | ***
63 |
64 | ### getYeast
65 |
66 | ```php
67 | public getYeast(): int
68 | ```
69 |
70 | **Throws:**
71 |
72 | - [`Exception`](../../../Exception)
73 |
74 | ***
75 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/Macro/MacroTestCase.php:
--------------------------------------------------------------------------------
1 | loadMacroTemplate();
24 | } catch (Throwable $e) {
25 | self::fail($e->getMessage());
26 | }
27 | }
28 |
29 | /**
30 | * Load the template.
31 | *
32 | * @return void
33 | * @throws RuntimeError
34 | * @throws LoaderError
35 | *
36 | * @throws SyntaxError
37 | */
38 | protected function loadMacroTemplate(): void
39 | {
40 | // Allow Twig to load files from both /tests and /themes
41 | $loader = new FilesystemLoader(ROOT_DIR);
42 | $twig = new Environment($loader);
43 |
44 | $this->template = $twig->load('tests/Unit/Twig/templates/macros.test.twig');
45 | }
46 |
47 | /**
48 | * @param string $key
49 | * @param array $args
50 | *
51 | * @return string Macro output
52 | */
53 | protected function renderTemplate(string $key, array $args): string
54 | {
55 | $variables = [
56 | 'relativeIncludePath' => $this->getRelativeIncludePath(),
57 | 'key' => $key,
58 | 'args' => $args,
59 | ];
60 |
61 | return $this->template->render($variables);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Functional/FunctionalTestCase.php:
--------------------------------------------------------------------------------
1 | runPhpDocWithDir(self::getProjectRootPath() . '/src/Example');
22 | }
23 |
24 | public function getMarkdownGenerator(): MarkdownGeneratorService
25 | {
26 | return self::$markdownGenerator;
27 | }
28 |
29 | final public static function assertMarkdownFileEquals(string $expectedPath, string $actualPath, $message = ''): void
30 | {
31 | $expected = file_get_contents($expectedPath);
32 | $actual = self::$markdownGenerator->loadContents($actualPath);
33 | self::assertMarkdownEquals($expected, $actual, $message);
34 | }
35 |
36 | final public static function assertMarkdownEquals(string $expected, string $actual, $message = ''): void
37 | {
38 | self::assertEquals(
39 | $expected,
40 | $actual,
41 | $message ?: 'Markdown does not match expected output.'
42 | );
43 | }
44 |
45 | public static function tearDownAfterClass(): void
46 | {
47 | parent::tearDownAfterClass();
48 | self::$markdownGenerator->cleanup();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Functional/PhpdocOutputTest.php:
--------------------------------------------------------------------------------
1 | 'Home.md',
12 | ),
13 | array(
14 | 'path' => 'classes/PhpDocumentorMarkdown/Example/AbstractProduct.md',
15 | ),
16 | array(
17 | 'path' => 'classes/PhpDocumentorMarkdown/Example/Arrayable.md',
18 | ),
19 | array(
20 | 'path' => 'classes/PhpDocumentorMarkdown/Example/Pizza.md',
21 | ),
22 | array(
23 | 'path' => 'classes/PhpDocumentorMarkdown/Example/ProductInterface.md',
24 | ),
25 | array(
26 | 'path' => 'classes/PhpDocumentorMarkdown/Example/ReviewableTrait.md',
27 | ),
28 | array(
29 | 'path' => 'classes/PhpDocumentorMarkdown/Example/ManyInterfaces.md',
30 | ),
31 | array(
32 | 'path' => 'classes/PhpDocumentorMarkdown/Example/Pizza/Base.md',
33 | ),
34 | array(
35 | 'path' => 'classes/PhpDocumentorMarkdown/Example/Pizza/Sauce.md',
36 | ),
37 | array(
38 | 'path' => 'functions/getDatabaseConfig.md',
39 | ),
40 | array(
41 | 'path' => 'functions/mockFunctionWithParameters.md',
42 | ),
43 | );
44 | }
45 |
46 | /**
47 | * @dataProvider dataProviderTestFiles
48 | */
49 | public function testFiles(string $path)
50 | {
51 | self::assertMarkdownFileEquals(
52 | __DIR__ . "/expected/$path",
53 | $path,
54 | "Markdown at $path does not match expected output"
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Example/Pizza.php:
--------------------------------------------------------------------------------
1 | name = $name;
50 | $this->price = $price;
51 | $this->base = $base;
52 | }
53 |
54 | /**
55 | * Get the name of the product.
56 | *
57 | * @return string The name of the product.
58 | */
59 | public function getName(): string
60 | {
61 | return $this->name;
62 | }
63 |
64 | /**
65 | * Get the price of the product.
66 | *
67 | * @return float
68 | */
69 | public function getPrice(): float
70 | {
71 | return $this->price;
72 | }
73 |
74 | public function jsonSerialize()
75 | {
76 | // TODO: Implement jsonSerialize() method.
77 | }
78 |
79 | public function toArray(): array
80 | {
81 | // TODO: Implement toArray() method.
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/themes/markdown/trait.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | {% block content %}
4 | {% include 'partials/header.md.twig' with { node: node } %}
5 |
6 | ***
7 |
8 | * Full name: `{{ node.FullyQualifiedStructuralElementName }}`
9 | {% if node.parent and node.parent is not empty %}* Parent trait: {{ macros.mdLink(node.parent, macros.mdClassPath(node), node.parent.FullyQualifiedStructuralElementName, 'class') }}
10 | {% endif %}
11 | {% if node.final %}* This trait is marked as **final** and can't be subclassed
12 | {% endif %}
13 | {% if node.deprecated %}* **Warning:** this trait is **deprecated**. This means that this class will likely be removed in a future version.
14 | {% endif %}
15 | {% if node.interfaces is not empty %}* This trait implements:
16 | {% include 'partials/inheritance.md.twig' with { node: node, others: node.interfaces } only %}
17 |
18 | {% endif %}
19 | {% include 'partials/tags.md.twig' with { tags: node.tags } only %}
20 | {% if node.constants | length > 0 %}
21 |
22 | ## Constants
23 |
24 | {% set constants = [] %}
25 | {% for constant in node.constants %}
26 | {% set constants = constants | merge([
27 | {
28 | name: '`' ~ constant.name ~ '`',
29 | visibility: constant.visibility | default('*default*'),
30 | type: constant.type ? constant.type : '',
31 | value: constant.value
32 | }
33 | ]) %}
34 | {% endfor %}
35 | {{ macros.mdTable(constants, [
36 | { label: 'Constant', key: 'name' },
37 | { label: 'Visibility', key: 'visibility' },
38 | { label: 'Type', key: 'type' },
39 | { label: 'Value', key: 'value' },
40 | ]) -}}
41 | {% endif %}
42 | {% if node.properties | length > 0 %}
43 |
44 | ## Properties
45 |
46 | {% for property in node.properties %}
47 | {% include 'property.md.twig' %}
48 | {% endfor %}
49 | {% endif %}
50 | {% if node.methods | length > 0 %}
51 |
52 | ## Methods
53 |
54 | {% for method in node.methods %}
55 | {% include 'method.md.twig' %}
56 | {% endfor %}
57 | {% endif %}
58 | {% if parameter.includeFooter == 'yes' %}
59 | {% include 'partials/footer.md.twig' %}
60 | {% endif %}
61 | {% endblock %}
62 | {% endautoescape %}
--------------------------------------------------------------------------------
/themes/markdown/interface.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | {% block content %}
4 | {% include 'partials/header.md.twig' with { node: node } %}
5 |
6 | ***
7 |
8 | * Full name: `{{ node.FullyQualifiedStructuralElementName }}`
9 | {% if node.parent and node.parent is not empty %}* Parent interfaces:
10 | {% include 'partials/inheritance.md.twig' with { node: node, others: node.parent } only %}
11 |
12 | {% endif %}
13 | {% if node.final %}* This interface is marked as **final** and can't be subclassed
14 | {% endif %}
15 | {% if node.deprecated %}* **Warning:** this interface is **deprecated**. This means that this interface will likely be removed in a future version.
16 | {% endif %}
17 | {% if node.interfaces is not empty %}* This interface implements:
18 | {% include 'partials/inheritance.md.twig' with { node: node, others: node.interfaces } only %}
19 |
20 | {% endif %}
21 | {% include 'partials/tags.md.twig' with { tags: node.tags } only %}
22 | {% if node.constants | length > 0 %}
23 |
24 | ## Constants
25 |
26 | {% set constants = [] %}
27 | {% for constant in node.constants %}
28 | {% set constants = constants | merge([
29 | {
30 | name: '`' ~ constant.name ~ '`',
31 | visibility: constant.visibility | default('*default*'),
32 | type: constant.type ? macros.mdEsc(constant.type) : '',
33 | value: constant.value
34 | }
35 | ]) %}
36 | {% endfor %}
37 |
38 | {{ macros.mdTable(constants, [
39 | { label: 'Constant', key: 'name' },
40 | { label: 'Visibility', key: 'visibility' },
41 | { label: 'Type', key: 'type' },
42 | { label: 'Value', key: 'value' },
43 | ]) -}}
44 | {%- endif %}
45 | {% if node.methods | length > 0 %}
46 |
47 | ## Methods
48 |
49 | {% for method in node.methods -%}
50 | {% if loop.index0 > 0 %}{{ '\n' }}{% endif %}{% include 'method.md.twig' %}
51 | {% endfor %}
52 | {% endif %}
53 | {% if node.InheritedMethods | length > 0 %}
54 |
55 | ## Inherited methods
56 |
57 | {% for method in node.InheritedMethods -%}
58 | {% if loop.index0 > 0 %}{{ '\n' }}{% endif %}{% include 'method.md.twig' %}
59 | {% endfor %}
60 | {% endif %}
61 | {% if parameter.includeFooter == 'yes' %}
62 | {% include 'partials/footer.md.twig' %}
63 | {% endif %}
64 | {% endblock %}
65 | {% endautoescape %}
--------------------------------------------------------------------------------
/tests/Functional/Service/MarkdownGeneratorService.php:
--------------------------------------------------------------------------------
1 | projectRootPath = $projectRootPath;
20 | $this->filesystem = new Filesystem();
21 | $this->workingDir = tempnam(sys_get_temp_dir(), 'phpdoc');
22 | $this->filesystem->remove($this->workingDir);
23 |
24 | if (!mkdir($concurrentDirectory = $this->workingDir) && !is_dir($concurrentDirectory)) {
25 | throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
26 | }
27 | }
28 |
29 | public function cleanup(): void
30 | {
31 | $this->filesystem->remove($this->workingDir);
32 | }
33 |
34 | protected function getProjectRootPath(): string
35 | {
36 | return $this->projectRootPath;
37 | }
38 |
39 | protected function getPhpDocBinaryPath(): string
40 | {
41 | return "{$this->getProjectRootPath()}/vendor/bin/phpdoc";
42 | }
43 |
44 | public function runPhpDocWithDir(string $path, array $arguments = []): Process
45 | {
46 | if (!is_dir($path)) {
47 | throw new InvalidArgumentException(sprintf('The path "%s" is not a directory.', $path));
48 | }
49 |
50 | $this->filesystem->mirror($path, "$this->workingDir/test");
51 |
52 | $process = new Process(
53 | array_merge(
54 | [
55 | PHP_BINARY,
56 | $this->getPhpDocBinaryPath(),
57 | '-vvv',
58 | "--config={$this->getProjectRootPath()}/phpdoc.dist.xml",
59 | '--force',
60 | "--target=$this->workingDir/output",
61 | ],
62 | $arguments
63 | ),
64 | $this->getProjectRootPath()
65 | );
66 |
67 | return $process->mustRun();
68 | }
69 |
70 | public function loadContents($filename): string
71 | {
72 | return file_get_contents($this->workingDir . '/output/' . $filename);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/themes/markdown/method.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | ### {{ method.name }}
4 | {% if method.summary | trim %}
5 |
6 | {{ macros.mdEsc(method.summary) | raw }}
7 | {% endif %}
8 |
9 | {# Method signature #}
10 | ```php
11 | {% if method.final %}{{ 'final' ~ ' ' }}{% endif %}{{ method.visibility ~ ' ' }}{% if method.static %}{{ 'static' ~ ' ' }}{% endif %}{{ method.name }}({% for argument in method.arguments %}
12 | {{- argument.type }}
13 | {{- argument.byReference ? '&' }} $
14 | {{- argument.name }}{{ argument.default ? ' = ' ~ argument.default | raw }}
15 | {%- if not loop.last %}, {% endif %}
16 | {%- endfor %})
17 | {{- method.response.type ? ': ' ~ method.response.type }}
18 | ```
19 | {% include 'partials/description.md.twig' with { node: method } %}
20 | {% if method.static or method.abstract or method.final or method.deprecated %}
21 |
22 | {% if method.static %}* This method is **static**.{% endif -%}
23 | {% if method.abstract %}* This method is **abstract**.{% endif -%}
24 | {% if method.final %}* This method is **final**.{% endif -%}
25 | {% if method.deprecated %}* **Warning:** this method is **deprecated**. This means that this method will likely be removed in a future version.{% endif -%}
26 | {% endif -%}
27 | {%- if method.arguments is not empty %}
28 |
29 | **Parameters:**
30 |
31 | {% set parameters = [] %}
32 | {% for argument in method.arguments %}
33 | {% set parameters = parameters | merge([
34 | {
35 | name: '`$' ~ argument.name ~ '`',
36 | type: argument.type ? '**' ~ argument.type ~ '**' : '',
37 | description: argument.description | raw
38 | }
39 | ]) %}
40 | {% endfor -%}
41 | {{ macros.mdTable(parameters, [
42 | { label: 'Parameter', key: 'name' },
43 | { label: 'Type', key: 'type' },
44 | { label: 'Description', key: 'description' },
45 | ]) }}
46 | {%- endif -%}
47 | {% if method.response.description and method.response.description is not empty %}
48 |
49 | **Return Value:**
50 | {% include 'partials/description.md.twig' with { node: method.response } %}
51 | {% endif %}
52 | {% if method.tags.throws | length > 0 or method.tags.throw | length > 0 %}
53 |
54 | **Throws:**
55 |
56 | {% for exception in method.tags.throws %}
57 | {% if exception.description | trim %}
58 | {{ exception.description | raw }}
59 | {% endif %}
60 | - {{ macros.mdLink(exception.type | raw, macros.mdClassPath(node), exception.type | shortFQSEN, 'class' ) }}
61 | {% endfor %}
62 | {% endif -%}
63 | {% include 'partials/tags.md.twig' with { tags: method.tags } only %}
64 |
65 | ***
66 | {% endautoescape %}
--------------------------------------------------------------------------------
/themes/markdown/class.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | {% block content %}
4 | {% include 'partials/header.md.twig' with { node: node } %}
5 |
6 | ***
7 |
8 | * Full name: `{{ node.FullyQualifiedStructuralElementName }}`
9 | {% if node.parent and node.parent is not empty %}* Parent class: {{ macros.mdLink(node.parent, macros.mdClassPath(node), node.parent.FullyQualifiedStructuralElementName, 'class') }}
10 | {% endif %}
11 | {% if node.final %}* This class is marked as **final** and can't be subclassed
12 | {% endif %}
13 | {% if node.deprecated %}* **Warning:** this class is **deprecated**. This means that this class will likely be removed in a future version.
14 | {% endif %}
15 | {% if node.interfaces is not empty %}* This class implements:
16 | {% include 'partials/inheritance.md.twig' with { node: node, others: node.interfaces } only %}
17 |
18 | {% endif %}
19 | {% if node.abstract %}* This class is an **Abstract class**
20 | {% endif %}
21 | {% if node.final %}* This class is a **Final class**
22 | {% endif %}
23 | {% include 'partials/tags.md.twig' with { tags: node.tags } only %}
24 | {% if node.constants | length > 0 %}
25 |
26 | ## Constants
27 |
28 | {% set constants = [] %}
29 | {% for constant in node.constants %}
30 | {% set constants = constants | merge([
31 | {
32 | name: '`' ~ constant.name ~ '`',
33 | visibility: constant.visibility | default('*default*'),
34 | type: constant.type ? constant.type : '',
35 | value: constant.value
36 | }
37 | ]) %}
38 | {% endfor %}
39 | {{ macros.mdTable(constants, [
40 | { label: 'Constant', key: 'name' },
41 | { label: 'Visibility', key: 'visibility' },
42 | { label: 'Type', key: 'type' },
43 | { label: 'Value', key: 'value' },
44 | ]) -}}
45 | {% endif %}
46 | {% if node.properties | length > 0 %}
47 |
48 | ## Properties
49 |
50 | {% for property in node.properties %}
51 | {% if loop.index0 > 0 %}{{ '\n' }}{% endif %}{% include 'property.md.twig' %}
52 | {% endfor %}
53 | {% endif %}
54 | {% if node.methods | length > 0 %}
55 |
56 | ## Methods
57 |
58 | {% for method in node.methods %}
59 | {% if loop.index0 > 0 %}{{ '\n' }}{% endif %}{% include 'method.md.twig' %}
60 | {% endfor %}
61 | {% endif %}
62 | {% if node.InheritedMethods | length > 0 %}
63 |
64 | ## Inherited methods
65 |
66 | {% for method in node.InheritedMethods %}
67 | {% if loop.index0 > 0 %}{{ '\n' }}{% endif %}{% include 'method.md.twig' %}
68 | {% endfor %}
69 | {% endif %}
70 | {% if parameter.includeFooter == 'yes' %}
71 | {% include 'partials/footer.md.twig' %}
72 | {% endif %}
73 | {% endblock %}
74 | {% endautoescape %}
--------------------------------------------------------------------------------
/.wiki/classes/PhpDocumentorMarkdown/Example/Pizza.md:
--------------------------------------------------------------------------------
1 | # Pizza
2 |
3 | A pizza \| pie's.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza`
8 | * Parent class: [`\PhpDocumentorMarkdown\Example\AbstractProduct`](./AbstractProduct.md)
9 | * This class implements:
10 | [`\PhpDocumentorMarkdown\Example\ProductInterface`](./ProductInterface.md),
11 | `JsonSerializable`
12 |
13 | ## Properties
14 |
15 | ### name
16 |
17 | Product name.
18 |
19 | ```php
20 | private string $name
21 | ```
22 |
23 | ***
24 |
25 | ### price
26 |
27 | Product price.
28 |
29 | ```php
30 | protected float $price
31 | ```
32 |
33 | ***
34 |
35 | ### base
36 |
37 | Pizza base.
38 |
39 | ```php
40 | protected \PhpDocumentorMarkdown\Example\Pizza\Base|null $base
41 | ```
42 |
43 | Property base description
44 |
45 | - **See:** \PhpDocumentorMarkdown\Example\ManyInterfaces
46 |
47 | ***
48 |
49 | ## Methods
50 |
51 | ### __construct
52 |
53 | Constructor title
54 |
55 | ```php
56 | public __construct(string $name = '', float $price = 10.0, \PhpDocumentorMarkdown\Example\Pizza\Base|null $base = null): mixed
57 | ```
58 |
59 | Constructor description
60 |
61 | - **See:** \PhpDocumentorMarkdown\Example\ManyInterfaces
62 | - **See:** https://example.com
63 |
64 | **Parameters:**
65 |
66 | | Parameter | Type | Description |
67 | |-----------|-----------------------------------------------------|----------------|
68 | | `$name` | **string** | Product name. |
69 | | `$price` | **float** | Product price. |
70 | | `$base` | **\PhpDocumentorMarkdown\Example\Pizza\Base\|null** | Pizza's base. |
71 |
72 | ***
73 |
74 | ### getName
75 |
76 | Get the name of the product.
77 |
78 | ```php
79 | public getName(): string
80 | ```
81 |
82 | **Return Value:**
83 |
84 | The name of the product.
85 |
86 | ***
87 |
88 | ### getPrice
89 |
90 | Get the price of the product.
91 |
92 | ```php
93 | public getPrice(): float
94 | ```
95 |
96 | ***
97 |
98 | ### jsonSerialize
99 |
100 | ```php
101 | public jsonSerialize(): mixed
102 | ```
103 |
104 | ***
105 |
106 | ### toArray
107 |
108 | Get the instance as an array.
109 |
110 | ```php
111 | public toArray(): array
112 | ```
113 |
114 | ***
115 |
116 | ## Inherited methods
117 |
118 | ### getTaxRate
119 |
120 | Get the tax rate for the product.
121 |
122 | ```php
123 | public getTaxRate(): float
124 | ```
125 |
126 | ***
127 |
128 | ### isReviewed
129 |
130 | Whether the object has been reviewed.
131 |
132 | ```php
133 | public isReviewed(): bool
134 | ```
135 |
136 | ***
137 |
--------------------------------------------------------------------------------
/tests/Functional/expected/Home.md:
--------------------------------------------------------------------------------
1 | # Pizza Place (Example documentation)
2 |
3 | This is an automatically generated documentation for **Pizza Place (Example documentation)**.
4 |
5 | ## Namespaces
6 |
7 | ### \PhpDocumentorMarkdown\Example
8 |
9 | #### Classes
10 |
11 | | Class | Description |
12 | |------------------------------------------------------------------------------|------------------------------|
13 | | [`AbstractProduct`](./classes/PhpDocumentorMarkdown/Example/AbstractProduct) | Base class for all products. |
14 | | [`Pizza`](./classes/PhpDocumentorMarkdown/Example/Pizza) | A pizza \| pie's. |
15 |
16 | #### Traits
17 |
18 | | Trait | Description |
19 | |------------------------------------------------------------------------------|---------------------------------|
20 | | [`ReviewableTrait`](./classes/PhpDocumentorMarkdown/Example/ReviewableTrait) | A trait for reviewable objects. |
21 |
22 | #### Interfaces
23 |
24 | | Interface | Description |
25 | |--------------------------------------------------------------------------------|--------------------------|
26 | | [`Arrayable`](./classes/PhpDocumentorMarkdown/Example/Arrayable) | |
27 | | [`ManyInterfaces`](./classes/PhpDocumentorMarkdown/Example/ManyInterfaces) | A ManyInterfaces |
28 | | [`ProductInterface`](./classes/PhpDocumentorMarkdown/Example/ProductInterface) | Interface for a product. |
29 |
30 | ### \PhpDocumentorMarkdown\Example\Pizza
31 |
32 | #### Classes
33 |
34 | | Class | Description |
35 | |----------------------------------------------------------------|--------------------------|
36 | | [`Base`](./classes/PhpDocumentorMarkdown/Example/Pizza/Base) | Represents a pizza base. |
37 | | [`Sauce`](./classes/PhpDocumentorMarkdown/Example/Pizza/Sauce) | Pizza sauce class. |
38 |
39 | ### \PhpDocumentorMarkdown\Functions
40 |
41 | #### Functions
42 |
43 | | Function | Description |
44 | |--------------------------------------------------------------------------|--------------------------------------------------|
45 | | [`mockFunctionWithParameters()`](./functions/mockFunctionWithParameters) | Mock function to demonstrate parameter handling. |
46 | | [`getDatabaseConfig()`](./functions/getDatabaseConfig) | Get database configuration. |
47 |
--------------------------------------------------------------------------------
/tests/Functional/expected/classes/PhpDocumentorMarkdown/Example/Pizza.md:
--------------------------------------------------------------------------------
1 | # Pizza
2 |
3 | A pizza \| pie's.
4 |
5 | ***
6 |
7 | * Full name: `\PhpDocumentorMarkdown\Example\Pizza`
8 | * Parent class: [`\PhpDocumentorMarkdown\Example\AbstractProduct`](./AbstractProduct)
9 | * This class implements:
10 | [`\PhpDocumentorMarkdown\Example\ProductInterface`](./ProductInterface),
11 | `JsonSerializable`
12 |
13 | ## Properties
14 |
15 | ### name
16 |
17 | Product name.
18 |
19 | ```php
20 | private string $name
21 | ```
22 |
23 | ***
24 |
25 | ### price
26 |
27 | Product price.
28 |
29 | ```php
30 | protected float $price
31 | ```
32 |
33 | ***
34 |
35 | ### base
36 |
37 | Pizza base.
38 |
39 | ```php
40 | protected \PhpDocumentorMarkdown\Example\Pizza\Base|null $base
41 | ```
42 |
43 | Property base description
44 |
45 | - **See:** \PhpDocumentorMarkdown\Example\ManyInterfaces
46 |
47 | ***
48 |
49 | ## Methods
50 |
51 | ### __construct
52 |
53 | Constructor title
54 |
55 | ```php
56 | public __construct(string $name = '', float $price = 10.0, \PhpDocumentorMarkdown\Example\Pizza\Base|null $base = null): mixed
57 | ```
58 |
59 | Constructor description
60 |
61 | - **See:** \PhpDocumentorMarkdown\Example\ManyInterfaces
62 | - **See:** https://example.com
63 |
64 | **Parameters:**
65 |
66 | | Parameter | Type | Description |
67 | |-----------|-----------------------------------------------------|----------------|
68 | | `$name` | **string** | Product name. |
69 | | `$price` | **float** | Product price. |
70 | | `$base` | **\PhpDocumentorMarkdown\Example\Pizza\Base\|null** | Pizza's base. |
71 |
72 | ***
73 |
74 | ### getName
75 |
76 | Get the name of the product.
77 |
78 | ```php
79 | public getName(): string
80 | ```
81 |
82 | **Return Value:**
83 |
84 | The name of the product.
85 |
86 | ***
87 |
88 | ### getPrice
89 |
90 | Get the price of the product.
91 |
92 | ```php
93 | public getPrice(): float
94 | ```
95 |
96 | ***
97 |
98 | ### jsonSerialize
99 |
100 | ```php
101 | public jsonSerialize(): mixed
102 | ```
103 |
104 | ***
105 |
106 | ### toArray
107 |
108 | Get the instance as an array.
109 |
110 | ```php
111 | public toArray(): array
112 | ```
113 |
114 | ***
115 |
116 | ## Inherited methods
117 |
118 | ### getTaxRate
119 |
120 | Get the tax rate for the product.
121 |
122 | ```php
123 | public getTaxRate(): float
124 | ```
125 |
126 | ***
127 |
128 | ### isReviewed
129 |
130 | Whether the object has been reviewed.
131 |
132 | ```php
133 | public isReviewed(): bool
134 | ```
135 |
136 | ***
137 |
--------------------------------------------------------------------------------
/.wiki/Home.md:
--------------------------------------------------------------------------------
1 | # Pizza Place (Example documentation)
2 |
3 | This is an automatically generated documentation for **Pizza Place (Example documentation)**.
4 |
5 | ## Namespaces
6 |
7 | ### \PhpDocumentorMarkdown\Example
8 |
9 | #### Classes
10 |
11 | | Class | Description |
12 | |---------------------------------------------------------------------------------|------------------------------|
13 | | [`AbstractProduct`](./classes/PhpDocumentorMarkdown/Example/AbstractProduct.md) | Base class for all products. |
14 | | [`Pizza`](./classes/PhpDocumentorMarkdown/Example/Pizza.md) | A pizza \| pie's. |
15 |
16 | #### Traits
17 |
18 | | Trait | Description |
19 | |---------------------------------------------------------------------------------|---------------------------------|
20 | | [`ReviewableTrait`](./classes/PhpDocumentorMarkdown/Example/ReviewableTrait.md) | A trait for reviewable objects. |
21 |
22 | #### Interfaces
23 |
24 | | Interface | Description |
25 | |-----------------------------------------------------------------------------------|--------------------------|
26 | | [`Arrayable`](./classes/PhpDocumentorMarkdown/Example/Arrayable.md) | |
27 | | [`ManyInterfaces`](./classes/PhpDocumentorMarkdown/Example/ManyInterfaces.md) | A ManyInterfaces |
28 | | [`ProductInterface`](./classes/PhpDocumentorMarkdown/Example/ProductInterface.md) | Interface for a product. |
29 |
30 | ### \PhpDocumentorMarkdown\Example\Pizza
31 |
32 | #### Classes
33 |
34 | | Class | Description |
35 | |-------------------------------------------------------------------|--------------------------|
36 | | [`Base`](./classes/PhpDocumentorMarkdown/Example/Pizza/Base.md) | Represents a pizza base. |
37 | | [`Sauce`](./classes/PhpDocumentorMarkdown/Example/Pizza/Sauce.md) | Pizza sauce class. |
38 |
39 | ### \PhpDocumentorMarkdown\Functions
40 |
41 | #### Functions
42 |
43 | | Function | Description |
44 | |-----------------------------------------------------------------------------|--------------------------------------------------|
45 | | [`mockFunctionWithParameters()`](./functions/mockFunctionWithParameters.md) | Mock function to demonstrate parameter handling. |
46 | | [`getDatabaseConfig()`](./functions/getDatabaseConfig.md) | Get database configuration. |
47 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [1.0.0] - 2025-07-10
11 |
12 | ### Added
13 |
14 | - Option for removing page titles
15 | - Tags template
16 | - Table of contents template
17 | - Inheritance template
18 | - Functional tests
19 | - Unit tests for new macros
20 |
21 | ### Changed
22 |
23 | - Rewrote markdown templates for improved formatting
24 | - Updated GitHub Actions workflows and workflow names
25 | - Moved page titles to header partial template
26 | - Defaulted to using URLs without file extensions
27 |
28 | ### Fixed
29 |
30 | - Escaping issues in templates, tables and method signatures
31 | - Global parameters path resolution
32 |
33 | ### Removed
34 |
35 | - Links from global classes and interfaces
36 |
37 | ## [0.1.4] - 2023-11-24
38 |
39 | ### Added
40 |
41 | - Add wiki repo as git submodule
42 | - Composer helper scripts
43 | - Add throws section to output
44 |
45 | ### Changed
46 |
47 | - Set index Markdown file name to Home.md
48 | - Removed watermark from footer
49 |
50 | ### Fixed
51 |
52 | - Fixed issue with interface extends
53 |
54 | ## [0.1.3] - 2022-04-20
55 |
56 | ### Added
57 |
58 | - Option for leaving out .md file extensions from urls (used for GitLab wiki pages).
59 |
60 | ## [0.1.2] - 2022-04-17
61 |
62 | ### Added
63 |
64 | - Added links to implemented interface md files
65 | - Add documentation to Twig macros
66 | - Add tests for Twig macros
67 | - Add PHP wrapper for Twig macros
68 | - Run tests and generate docs (test) with GitHub Actions
69 | - Add properties to classes and traits documentation
70 |
71 | ### Changed
72 |
73 | - Removed method return section when return value is not documented
74 |
75 | ### Fixed
76 |
77 | - Fixed issue with empty table cells
78 |
79 | ## [0.1.1] - 2022-04-15
80 |
81 | ### Changed
82 |
83 | - Regenerated examples
84 | - Renamed header.twig to header.md.twig
85 |
86 | ### Fixed
87 |
88 | - Fixed Markdown horizontal separator breaking in GitHub
89 |
90 | ## [0.1.0] - 2022-04-15
91 |
92 | ### Added
93 |
94 | - Tests for Twig macros
95 | - Made the project a Composer package
96 | - Added example docs source dode
97 |
98 | ### Changed
99 |
100 | - Rewrote examples
101 |
102 | ### Fixed
103 |
104 | - Fixed malformed Markdown
105 |
106 | ## [0.0.2] - 2020-06-18
107 |
108 | ### Added
109 |
110 | - Constants
111 | - Interfaces
112 | - Traits
113 | - Inherited methods
114 |
115 | ## [0.0.1] - 2020-05-05
116 |
117 | ### Added
118 |
119 | - Initial version
120 |
--------------------------------------------------------------------------------
/themes/markdown/index.md.twig:
--------------------------------------------------------------------------------
1 | {% import 'include/macros.twig' as macros %}
2 | {% autoescape false %}
3 | {% block content %}
4 | {% include 'partials/header.md.twig' with { node: project } %}
5 |
6 | This is an automatically generated documentation for **{{project.name}}**.
7 |
8 | ## Namespaces
9 | {% for namespace in project.indexes.namespaces | sort((a,b) => a.FullyQualifiedStructuralElementName <=> b.FullyQualifiedStructuralElementName) %}
10 | {% if (namespace.classes | length) > 0 or (namespace.traits | length) > 0 or (namespace.interfaces | length) > 0 or (namespace.functions | length) > 0 %}
11 |
12 | ### {{ namespace.FullyQualifiedStructuralElementName }}
13 | {% if namespace.classes | length > 0 %}
14 |
15 | #### Classes
16 |
17 | {% set parameters = [] %}
18 | {% for class in namespace.classes %}
19 | {% set parameters = parameters | merge([
20 | {
21 | link: macros.mdLink(class.FullyQualifiedStructuralElementName, null, null, 'class'),
22 | description: class.summary | raw
23 | }
24 | ]) %}
25 | {% endfor -%}
26 | {{ macros.mdTable(parameters, [
27 | { label: 'Class', key: 'link' },
28 | { label: 'Description', key: 'description' },
29 | ]) }}
30 | {%- endif %}
31 | {% if namespace.traits | length > 0 %}
32 |
33 | #### Traits
34 |
35 | {% set parameters = [] %}
36 | {% for trait in namespace.traits %}
37 | {% set parameters = parameters | merge([
38 | {
39 | link: macros.mdLink(trait.FullyQualifiedStructuralElementName, null, null, 'class'),
40 | description: trait.summary | raw
41 | }
42 | ]) %}
43 | {% endfor -%}
44 | {{ macros.mdTable(parameters, [
45 | { label: 'Trait', key: 'link' },
46 | { label: 'Description', key: 'description' },
47 | ]) }}
48 | {%- endif %}
49 | {% if namespace.interfaces | length > 0 %}
50 |
51 | #### Interfaces
52 |
53 | {% set parameters = [] %}
54 | {% for interface in namespace.interfaces %}
55 | {% set parameters = parameters | merge([
56 | {
57 | link: macros.mdLink(interface.FullyQualifiedStructuralElementName, null, null, 'class'),
58 | description: interface.summary | raw
59 | }
60 | ]) %}
61 | {% endfor -%}
62 | {{ macros.mdTable(parameters, [
63 | { label: 'Interface', key: 'link' },
64 | { label: 'Description', key: 'description' },
65 | ]) }}
66 | {%- endif %}
67 | {% if namespace.functions | length > 0 %}
68 |
69 | #### Functions
70 |
71 | {% set parameters = [] %}
72 | {% for function in namespace.functions %}
73 | {% set parameters = parameters | merge([
74 | {
75 | link: macros.mdLink(function.name, null, function.name ~ '()', 'function'),
76 | description: function.summary | raw
77 | }
78 | ]) %}
79 | {% endfor -%}
80 | {{ macros.mdTable(parameters, [
81 | { label: 'Function', key: 'link' },
82 | { label: 'Description', key: 'description' },
83 | ]) }}
84 | {%- endif %}
85 | {% endif %}
86 | {% endfor %}
87 | {% if parameter.includeFooter == 'yes' %}
88 | {% include 'partials/footer.md.twig' %}
89 | {% endif %}
90 | {% endblock %}
91 | {% endautoescape %}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # phpDocumentor-markdown - Automatically generate Markdown documentation
2 |
3 | Use cases: In-repository documentation, GitHub/GitLab wikis, AI prompt context.
4 |
5 | ## Markdown template for phpDocumentor3
6 |
7 | 
8 | 
9 |
10 | Wish there was an easy way to generate PHP source code documentation? \
11 | Using [`phpDocumentor`](https://www.phpdoc.org/) with `phpDocumentor-markdown` template, you can automatically generate GitHub/GitLab-ready Markdown documentation from PHP source code.
12 |
13 | This template can be used to document:
14 |
15 | | | Name | Descriptions | Inheritance | Implements | Constants | Properties | Methods | Inherited methods |
16 | |------------|------|--------------|-------------|------------|-----------|------------|---------|-------------------|
17 | | Classes | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
18 | | Interfaces | ✅ | ✅ | ✅ | ➖ | ✅ | ✅ | ✅ | ➖ |
19 | | Traits | ✅ | ✅ | ➖ | ➖ | ✅ | ✅ | ✅ | ➖ |
20 |
21 | | | Name | Descriptions | Types (parameter, return, etc.) | Access modifiers |
22 | |------------|------|--------------|---------------------------------|------------------|
23 | | Functions | ✅ | ✅ | ✅ | ✅ |
24 | | Methods | ✅ | ✅ | ✅ | ✅ |
25 | | Properties | ✅ | ✅ | ✅ | ✅ |
26 |
27 | ## Example
28 | Examples are available in the [.wiki](.wiki/Home.md) directory.
29 |
30 | ## Installation & Usage
31 | - Please refer to [this guide](https://docs.phpdoc.org/guide/getting-started/installing.html) for instructions on installing phpDocumentor.
32 | - Usage instructions assume that `phpdoc` is the phpDocumentor binary.
33 |
34 | ### Running manually
35 | ```bash
36 | # Run phpDocumentor with --template argument pointed to this directory's markdown template
37 | phpdoc --directory=src --target=docs --template=
38 | ```
39 |
40 | ### Using Composer
41 |
42 | #### Installation via Composer
43 | ```bash
44 | # Require this package. You probably want it as a dev dependency
45 | composer require --dev saggre/phpdocumentor-markdown
46 | ```
47 |
48 | #### Running manually after installing via Composer
49 | ```bash
50 | # Run phpDocumentor with --template argument pointed to markdown template inside vendor directory
51 | phpdoc --directory=src --target=docs --template="vendor/saggre/phpdocumentor-markdown/themes/markdown"
52 | ```
53 |
54 | ### Installation & Usage tips
55 |
56 | #### Adding a Composer helper script
57 |
58 | Add this script to your `composer.json` and run `composer create-docs` to generate the documentation.
59 |
60 | ```json
61 | "scripts": {
62 | "create-docs": "phpdoc --directory=src --target=docs --template='vendor/saggre/phpdocumentor-markdown/themes/markdown'"
63 | },
64 | ```
65 |
66 | #### Using with PhpDocumentor XML config
67 |
68 | Add a template element to your phpDocumentor XML config and run `phpDocumentor` to generate the documentation.
69 |
70 | ```xml
71 |
72 |
73 |
74 |
75 | ```
76 |
77 | You can also check out the [config file](./phpdoc.dist.xml) used for generating this repository's example documentation for a full example.
78 |
79 | #### GitLab wiki
80 |
81 | The output of this template can be used directly as a GitLab wiki.
82 |
83 | #### GitHub wiki
84 |
85 | ⚠️ GitHub wiki [uses a flat directory structure](https://github.com/orgs/community/discussions/14020) for linking, so the internal links in the resulting documentation will not work as expected.
86 |
87 | #### CI pipelines
88 |
89 | You can use the [PhpDocumentor Docker image](https://hub.docker.com/r/phpdoc/phpdoc) with this template in your CI pipelines to generate documentation automatically.
90 |
91 | #### AI integration
92 |
93 | The generated documentation can be used as prompt context for AI models that work with source code. For other use cases you'll likely want structured data, like JSON.
94 |
95 | ## Running tests
96 | ```bash
97 | # Clone the repository
98 | git clone git@github.com:Saggre/phpDocumentor-markdown.git
99 |
100 | # Go to the cloned repository
101 | cd phpDocumentor-markdown
102 |
103 | # Install dependencies
104 | composer install
105 |
106 | # Set up PHPUnit configuration
107 | cp phpunit.xml.dist phpunit.xml
108 |
109 | # Run PHPUnit in project root directory
110 | composer test
111 | ```
112 |
113 | ## Contributing
114 | - There are unit tests and functional tests available in the `tests` directory.
115 | - Unit tests are used to test individual Twig macros
116 | - Functional tests are used to test the whole documentation generation process
117 |
118 | ## Inspired by:
119 |
120 | * [dmarkic/phpdoc3-template-md](https://github.com/dmarkic/phpdoc3-template-md)
121 | * [cvuorinen/phpdoc-markdown-public](https://github.com/cvuorinen/phpdoc-markdown-public)
122 | * [evert/phpdoc-md](https://github.com/evert/phpdoc-md)
123 | * [heimrichhannot/phpdoc-github-template](https://github.com/heimrichhannot/phpdoc-github-template)
124 |
--------------------------------------------------------------------------------
/themes/markdown/include/macros.twig:
--------------------------------------------------------------------------------
1 | {#
2 | # Computes a relative Markdown path from one file to another.
3 | # Example: from 'classes/Foo/Bar.md' to 'classes/Foo/Baz/Qux.md' => 'Baz/Qux.md'
4 | # from 'classes/Foo/Bar.md' to 'classes/Utils/Helper.md' => '../Utils/Helper.md'
5 | # @param relativeTo string Path of the file you're linking from
6 | # @param path string Absolute path of the file you're linking to
7 | # @return string Relative path from `relativeTo` to `path`
8 | #}
9 | {% macro mdGetRelativePath(relativeTo, path) -%}
10 | {# Split paths into arrays of directory parts #}
11 | {%- set from = relativeTo | split('/') -%}
12 | {%- set to = path | split('/') -%}
13 | {%- set relPath = to -%}
14 | {%- set break = false -%}
15 |
16 | {# Compare each directory level #}
17 | {%- for dir in from -%}
18 | {%- if not break -%}
19 | {%- set depth = loop.index0 -%}
20 | {%- if dir == to[depth] -%}
21 | {# Same directory level #}
22 | {%- set relPath = relPath | slice(1) -%}
23 | {%- else -%}
24 | {%- set remaining = from | length - depth -%}
25 | {%- if remaining > 1 -%}
26 | {# Divergent directories #}
27 | {%- set padLength = remaining - 1 -%}
28 | {%- for i in 1..padLength -%}
29 | {%- set relPath = '..' | split('/') | merge(relPath) -%}
30 | {%- endfor -%}
31 | {%- set break = true -%}
32 | {%- else -%}
33 | {%- set relPath0 = './' ~ relPath[0] -%}
34 | {%- set relPathRest = relPath | slice(1) -%}
35 | {%- set relPath = relPath0 | split('/') | merge(relPathRest) -%}
36 | {%- endif -%}
37 | {%- endif -%}
38 | {%- endif -%}
39 | {%- endfor -%}
40 |
41 | {# Join segments back into a path #}
42 | {{- relPath | join('/') -}}
43 | {%- endmacro %}
44 |
45 | {#
46 | # Get full md link for a node or for a namespace without /classes or /functions directory
47 | # @param $nodeOrNamespace object|string The node to get the link for or a PHP class namespace string
48 | #}
49 | {% macro mdNodePath(nodeOrNamespace) -%}
50 | {{- nodeOrNamespace.FullyQualifiedStructuralElementName | default(nodeOrNamespace) | replace({'\\':'/'}) | trim | trim('/') ~ (parameter.urlFileExtensions == 'yes' ? '.md' : '') -}}
51 | {%- endmacro %}
52 |
53 | {#
54 | # Get full link to a class from documentation root directory
55 | # @param $nodeOrNamespace object|string The node to get the link for or a PHP class namespace string
56 | #}
57 | {% macro mdClassPath(nodeOrNamespace) -%}
58 | {{- 'classes/' ~ _self.mdNodePath(nodeOrNamespace) | trim -}}
59 | {%- endmacro %}
60 |
61 | {#
62 | # Get full link to a function from documentation root directory
63 | # @param $nodeOrNamespace object|string The node to get the link for or a PHP namespace string
64 | #}
65 | {% macro mdFunctionPath(nodeOrNamespace) -%}
66 | {{- 'functions/' ~ _self.mdNodePath(nodeOrNamespace) | trim -}}
67 | {%- endmacro %}
68 |
69 | {#
70 | # Create a relative md link to a class or function
71 | # @param $nodeOrNamespace object|string The node to get the link for or a PHP namespace string
72 | # @param $relativeTo string The path to make relative to (usually path of the md file that this is being printed to)
73 | # @param $name string|null Link text
74 | # @param $type string Either 'class' or 'function'
75 | #}
76 | {% macro mdLink(nodeOrNamespace, relativeTo, name = null, type = 'class') -%}
77 | [{{- '`' ~ name | default(nodeOrNamespace.name) | default('Unknown') ~ '`' -}}]({{- _self.mdGetRelativePath(relativeTo, type == 'function' ? _self.mdFunctionPath(nodeOrNamespace) : _self.mdClassPath(nodeOrNamespace)) -}})
78 | {%- endmacro %}
79 |
80 | {#
81 | # Escape markdown special characters
82 | # @param $text string The text to escape
83 | #}
84 | {% macro mdEsc(text) -%}
85 | {{- text | replace({'|':'\\|'}) | raw -}}
86 | {%- endmacro %}
87 |
88 | {#
89 | # Repeat a character a given number of times
90 | # @param $char string The character to repeat
91 | # @param $times int The number of times to repeat the character
92 | #}
93 | {% macro mdRepeat(char, times) %}
94 | {%- if times <= 0 -%}
95 | {{- '' -}}
96 | {%- else -%}
97 | {%- set result = '' -%}
98 | {%- for i in 1..times -%}
99 | {%- set result = result ~ char -%}
100 | {%- endfor -%}
101 | {{- result -}}
102 | {%- endif -%}
103 | {% endmacro %}
104 |
105 | {#
106 | # Pad a string to the right with spaces to a given length
107 | # @param str string The string to pad
108 | # @param length int The target length of the string
109 | #}
110 | {% macro mdPadRight(str, length) %}
111 | {%- set str = str | default('') -%}
112 | {%- set delta = length - str | length -%}
113 | {{- (str ~ _self.mdRepeat(' ', delta > 0 ? delta : 0)) | raw -}}
114 | {% endmacro %}
115 |
116 | {#
117 | # Render a generic Markdown table from raw values
118 | #
119 | # @param rows array List of associative arrays or objects (rows)
120 | # @param headers array List of headers as { label: string, key: string }
121 | #}
122 | {% macro mdTable(rows, headers) %}
123 | {%- set widths = [] -%}
124 | {# Initialize column widths based on header labels #}
125 | {%- for col in headers %}
126 | {%- set widths = widths | merge({ (loop.index0 ~ 'r'): col.label | length }) %}
127 | {%- endfor %}
128 | {# Compute max width for each column from content #}
129 | {%- for row in rows %}
130 | {%- for col in headers %}
131 | {%- set value = attribute(row, col.key) %}
132 | {%- set value = _self.mdEsc(value) %}
133 | {%- set length = value | length %}
134 | {%- if length > widths[loop.index0 ~ 'r'] %}
135 | {%- set widths = widths | merge({ (loop.index0 ~ 'r'): length }) %}
136 | {%- endif %}
137 | {%- endfor %}
138 | {%- endfor %}
139 | {# Header row #}
140 | {%- for col in headers %}
141 | {{- '| ' ~ _self.mdPadRight(col.label, widths[loop.index0 ~ 'r']) ~ ' ' }}
142 | {%- endfor -%}
143 | {{ '|' }}
144 | {# Divider row #}
145 | {%- for col in headers %}
146 | {{- '|-' ~ _self.mdRepeat('-', widths[loop.index0 ~ 'r']) ~ '-' }}
147 | {%- endfor -%}
148 | {{ '|' }}
149 | {# Data rows #}
150 | {%- for row in rows %}
151 | {%- for col in headers %}
152 | {%- set value = attribute(row, col.key) %}
153 | {%- set value = _self.mdEsc(value) %}
154 | {{- ('| ' ~ _self.mdPadRight(value, widths[loop.index0 ~ 'r']) ~ ' ') | raw }}
155 | {%- endfor -%}
156 | {{ '|' ~ "\n" }}
157 | {%- endfor %}
158 | {% endmacro %}
159 |
160 | {#
161 | # Calculate the nesting level of a given path
162 | # @param path string The path to analyze
163 | #}
164 | {% macro mdNestingLevel(path) %}
165 | {%- set normalized = path | replace({'\\': '/'}) | trim('/') -%}
166 | {{- normalized ? (normalized | split('/') | length - 1) : 0 -}}
167 | {% endmacro %}
168 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/Macro/MacroTest.php:
--------------------------------------------------------------------------------
1 | 'a\|b\|c',
12 | 'args' => ['a|b|c'],
13 | ],
14 | ];
15 | }
16 |
17 | /**
18 | * @dataProvider dataProviderTestMdEsc
19 | */
20 | public function testMdEsc(string $expected, array $args): void
21 | {
22 | $result = $this->renderTemplate('mdEsc', $args);
23 | self::assertEquals($expected, $result);
24 | }
25 |
26 | public static function dataProviderTestMdGetRelativePath(): array
27 | {
28 | return [
29 | [
30 | 'expected' => 'baz/qux',
31 | 'args' => ['foo/bar', 'foo/bar/baz/qux'],
32 | ],
33 | [
34 | 'expected' => '../..',
35 | 'args' => ['foo/bar/baz/qux', 'foo'],
36 | ],
37 | ];
38 | }
39 |
40 | /**
41 | * @dataProvider dataProviderTestMdGetRelativePath
42 | */
43 | public function testMdGetRelativePath(string $expected, array $args): void
44 | {
45 | $result = $this->renderTemplate('mdGetRelativePath', $args);
46 | self::assertEquals($expected, $result);
47 | }
48 |
49 | public static function dataProviderTestMdNodePath(): array
50 | {
51 | return [
52 | [
53 | 'expected' => 'Fully/Qualified/Structural/Element/Name',
54 | 'args' => ['\Fully\Qualified\Structural\Element\Name'],
55 | ],
56 | [
57 | 'expected' => 'Fully/Qualified/Structural/Element/Name',
58 | 'args' => [(object)['FullyQualifiedStructuralElementName' => '\Fully\Qualified\Structural\Element\Name']],
59 | ],
60 | ];
61 | }
62 |
63 | /**
64 | * @dataProvider dataProviderTestMdNodePath
65 | */
66 | public function testMdNodePath(string $expected, array $args): void
67 | {
68 | $result = $this->renderTemplate('mdNodePath', $args);
69 | self::assertEquals($expected, $result);
70 | }
71 |
72 | public static function dataProviderTestMdClassPath(): array
73 | {
74 | return [
75 | [
76 | 'expected' => 'classes/Fully/Qualified/Structural/Element/Name',
77 | 'args' => ['\Fully\Qualified\Structural\Element\Name'],
78 | ],
79 | [
80 | 'expected' => 'classes/Fully/Qualified/Structural/Element/Name',
81 | 'args' => [(object)['FullyQualifiedStructuralElementName' => '\Fully\Qualified\Structural\Element\Name']],
82 | ],
83 | ];
84 | }
85 |
86 | /**
87 | * @dataProvider dataProviderTestMdClassPath
88 | */
89 | public function testMdClassPath(string $expected, array $args): void
90 | {
91 | $result = $this->renderTemplate('mdClassPath', $args);
92 | self::assertEquals($expected, $result);
93 | }
94 |
95 | public static function dataProviderTestMdFunctionPath(): array
96 | {
97 | return [
98 | [
99 | 'expected' => 'functions/Fully/Qualified/Structural/Element/Name',
100 | 'args' => ['\Fully\Qualified\Structural\Element\Name'],
101 | ],
102 | [
103 | 'expected' => 'functions/Fully/Qualified/Structural/Element/Name',
104 | 'args' => [(object)['FullyQualifiedStructuralElementName' => '\Fully\Qualified\Structural\Element\Name']],
105 | ],
106 | ];
107 | }
108 |
109 | /**
110 | * @dataProvider dataProviderTestMdFunctionPath
111 | */
112 | public function testMdFunctionPath(string $expected, array $args): void
113 | {
114 | $result = $this->renderTemplate('mdFunctionPath', $args);
115 | self::assertEquals($expected, $result);
116 | }
117 |
118 | public static function dataProviderTestMdLink(): array
119 | {
120 | return [
121 | [
122 | 'expected' => '[`Unknown`](./classes/Fully/Qualified/Structural/Element/Name)',
123 | 'args' => ['\Fully\Qualified\Structural\Element\Name', null, null, 'class'],
124 | ],
125 | [
126 | 'expected' => '[`ClassName`](Structural/Element/Name)',
127 | 'args' => ['\Fully\Qualified\Structural\Element\Name', 'classes/Fully/Qualified', 'ClassName', 'class'],
128 | ],
129 | ];
130 | }
131 |
132 | /**
133 | * @dataProvider dataProviderTestMdLink
134 | */
135 | public function testMdLink(string $expected, array $args): void
136 | {
137 | $result = $this->renderTemplate('mdLink', $args);
138 | self::assertEquals($expected, $result);
139 | }
140 |
141 | public static function dataProviderTestMdRepeat(): array
142 | {
143 | return [
144 | ['expected' => '', 'args' => ['x', 0]],
145 | ['expected' => '', 'args' => ['x', -5]],
146 | ['expected' => 'x', 'args' => ['x', 1]],
147 | ['expected' => 'xxxxx', 'args' => ['x', 5]],
148 | ['expected' => '---', 'args' => ['-', 3]],
149 | ];
150 | }
151 |
152 | /**
153 | * @dataProvider dataProviderTestMdRepeat
154 | */
155 | public function testMdRepeat(string $expected, array $args): void
156 | {
157 | $result = $this->renderTemplate('mdRepeat', $args);
158 | self::assertEquals($expected, $result);
159 | }
160 |
161 | public static function dataProviderTestMdPadRight(): array
162 | {
163 | return [
164 | ['expected' => 'foo ', 'args' => ['foo', 8]],
165 | ['expected' => 'foobar', 'args' => ['foobar', 6]],
166 | ['expected' => 'foobar', 'args' => ['foobar', 4]],
167 | ['expected' => ' ', 'args' => ['', 8]],
168 | ['expected' => 'a', 'args' => ['a', 1]],
169 | ['expected' => 'a ', 'args' => ['a', 2]],
170 | ];
171 | }
172 |
173 | /**
174 | * @dataProvider dataProviderTestMdPadRight
175 | */
176 | public function testMdPadRight(string $expected, array $args): void
177 | {
178 | $result = $this->renderTemplate('mdPadRight', $args);
179 | self::assertEquals($expected, $result);
180 | }
181 |
182 | public static function dataProviderTestMdTable(): array
183 | {
184 | return [
185 | [
186 | 'expected' => << [
193 | // rows
194 | [
195 | ['name' => 'MyClass', 'summary' => 'This is a test class.'],
196 | ['name' => 'YourClass', 'summary' => 'Another description.'],
197 | ],
198 | // headers
199 | [
200 | ['label' => 'Class', 'key' => 'name'],
201 | ['label' => 'Description', 'key' => 'summary'],
202 | ],
203 | ],
204 | ],
205 | ];
206 | }
207 |
208 | /**
209 | * @dataProvider dataProviderTestMdTable
210 | */
211 | public function testMdTable(string $expected, array $args): void
212 | {
213 | $result = $this->renderTemplate('mdTable', $args);
214 | self::assertEquals(trim($expected), trim($result));
215 | }
216 |
217 | public static function dataProviderTestMdNestingLevel(): array
218 | {
219 | return [
220 | [
221 | 'expected' => 0,
222 | 'args' => [''],
223 | ],
224 | [
225 | 'expected' => 0,
226 | 'args' => ['/foo'],
227 | ],
228 | [
229 | 'expected' => 1,
230 | 'args' => ['foo/bar'],
231 | ],
232 | [
233 | 'expected' => 2,
234 | 'args' => ['foo/bar/baz'],
235 | ],
236 | [
237 | 'expected' => 3,
238 | 'args' => ['foo/bar/baz/qux'],
239 | ],
240 | [
241 | 'expected' => 3,
242 | 'args' => ['foo/bar/baz/qux/'],
243 | ],
244 | ];
245 | }
246 |
247 | /**
248 | * @dataProvider dataProviderTestMdNestingLevel
249 | */
250 | public function testMdNestingLevel(int $expected, array $args): void
251 | {
252 | $result = $this->renderTemplate('mdNestingLevel', $args);
253 | self::assertEquals($expected, $result);
254 | }
255 | }
256 |
--------------------------------------------------------------------------------