├── .gitignore ├── templates ├── searchlist.twig ├── menu │ ├── footer-documentation.html │ ├── business-solutions.html │ ├── help.html │ ├── documentation.html │ └── community.html ├── pages │ ├── overview.twig │ ├── parts │ │ ├── function-summary.twig │ │ ├── property-summary.twig │ │ ├── constant-detail.twig │ │ ├── property-detail.twig │ │ └── function-detail.twig │ ├── namespace.twig │ └── classlike.twig ├── footer.twig ├── page.twig └── header.twig ├── static └── assets │ ├── resources │ ├── github.png │ ├── linode.png │ ├── resize.png │ ├── search.png │ ├── sort.png │ ├── inherit.png │ ├── pingping.png │ ├── logo-cake.png │ ├── rackspace.png │ ├── tree-last.png │ ├── tree-cleaner.png │ ├── tree-hasnext.png │ ├── tree-vertical.png │ ├── favicons │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── browserconfig.xml │ │ ├── manifest.json │ │ ├── favicon.svg │ │ └── safari-pinned-tab.svg │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── cakedingbats-webfont.eot │ │ ├── cakedingbats-webfont.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── cakedingbats-webfont.woff │ │ ├── cakedingbats-webfont.woff2 │ │ └── fontawesome-webfont.woff2 │ ├── webfonts │ │ ├── fa-solid-900.ttf │ │ ├── fa-brands-400.ttf │ │ ├── fa-regular-400.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.woff2 │ │ ├── fa-v4compatibility.ttf │ │ └── fa-v4compatibility.woff2 │ ├── open-hub.svg │ └── css │ │ ├── prism.css │ │ └── responsive.css │ └── js │ ├── jquery.sprintf.js │ ├── jquery.sortElements.js │ ├── jquery.cookie.js │ └── main.js ├── .dockerignore ├── phpcs.xml ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── deploy_2x.yml │ └── ci.yml ├── bin └── apitool.php ├── .editorconfig ├── nginx.conf ├── config ├── queue.php ├── authorization.php ├── authentication.php ├── elastic.php ├── chronos.php ├── cakephp5.php ├── cakephp3.php └── cakephp4.php ├── README.md ├── src ├── Reflection │ ├── ReflectedTrait.php │ ├── ReflectedConstant.php │ ├── ReflectedInterface.php │ ├── ReflectedDefine.php │ ├── ReflectedClass.php │ ├── ClassElementTrait.php │ ├── Source.php │ ├── ReflectedProperty.php │ ├── ReflectedMethod.php │ ├── ReflectedClassLike.php │ ├── ReflectedNode.php │ ├── ReflectedParam.php │ ├── ReflectedFunction.php │ ├── Context.php │ └── DocBlock.php ├── Twig │ ├── TwigRuntimeLoader.php │ └── Extension │ │ └── ReflectionExtension.php ├── functions.php ├── Application.php ├── Util │ ├── PrintUtil.php │ ├── DocUtil.php │ └── MergeUtil.php ├── Loader.php ├── ProjectNamespace.php ├── Command │ └── GenerateCommand.php ├── Generator.php ├── FileVisitor.php ├── Project.php └── Factory.php ├── composer.json ├── LICENSE ├── Dockerfile └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | 3 | build/ 4 | release/ 5 | vendor/ 6 | website/ 7 | -------------------------------------------------------------------------------- /templates/searchlist.twig: -------------------------------------------------------------------------------- 1 | {% autoescape false %} 2 | var searchEntries = {{ entries|raw }}; 3 | {% endautoescape %} 4 | -------------------------------------------------------------------------------- /static/assets/resources/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/github.png -------------------------------------------------------------------------------- /static/assets/resources/linode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/linode.png -------------------------------------------------------------------------------- /static/assets/resources/resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/resize.png -------------------------------------------------------------------------------- /static/assets/resources/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/search.png -------------------------------------------------------------------------------- /static/assets/resources/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/sort.png -------------------------------------------------------------------------------- /static/assets/resources/inherit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/inherit.png -------------------------------------------------------------------------------- /static/assets/resources/pingping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/pingping.png -------------------------------------------------------------------------------- /static/assets/resources/logo-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/logo-cake.png -------------------------------------------------------------------------------- /static/assets/resources/rackspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/rackspace.png -------------------------------------------------------------------------------- /static/assets/resources/tree-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/tree-last.png -------------------------------------------------------------------------------- /static/assets/resources/tree-cleaner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/tree-cleaner.png -------------------------------------------------------------------------------- /static/assets/resources/tree-hasnext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/tree-hasnext.png -------------------------------------------------------------------------------- /static/assets/resources/tree-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/tree-vertical.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.groovy 3 | composer.phar 4 | Dockerfile 5 | phpcs.xml 6 | 7 | build/ 8 | release/ 9 | vendor/ 10 | website/ 11 | -------------------------------------------------------------------------------- /static/assets/resources/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/favicon.ico -------------------------------------------------------------------------------- /static/assets/resources/favicons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/favicon.png -------------------------------------------------------------------------------- /templates/menu/footer-documentation.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src/ 6 | 7 | -------------------------------------------------------------------------------- /static/assets/resources/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /static/assets/resources/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /static/assets/resources/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /static/assets/resources/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /static/assets/resources/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /static/assets/resources/fonts/cakedingbats-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/cakedingbats-webfont.eot -------------------------------------------------------------------------------- /static/assets/resources/fonts/cakedingbats-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/cakedingbats-webfont.ttf -------------------------------------------------------------------------------- /static/assets/resources/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/assets/resources/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/assets/resources/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /static/assets/resources/fonts/cakedingbats-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/cakedingbats-webfont.woff -------------------------------------------------------------------------------- /static/assets/resources/fonts/cakedingbats-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/cakedingbats-webfont.woff2 -------------------------------------------------------------------------------- /static/assets/resources/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /static/assets/resources/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/assets/resources/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/assets/resources/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakephp/cakephp-api-docs/HEAD/static/assets/resources/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Define the line ending behavior of the different file extensions 2 | # Set default behavior, in case users don't have core.autocrlf set. 3 | * text=auto eol=lf 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /templates/menu/business-solutions.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /static/assets/resources/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #D33C44 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /bin/apitool.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | run($argv)); 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at https://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.bat] 14 | end_of_line = crlf 15 | 16 | [*.{yml, yaml}] 17 | indent_size = 2 18 | 19 | [*.{twig, html}] 20 | indent_size = 2 21 | 22 | [Dockerfile, Dockerfile.*] 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /static/assets/resources/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CakePHP", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#D33C44", 17 | "background_color": "#D33C44", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | root /var/www/html; 5 | index index.html index.; 6 | server_name _; 7 | 8 | location ~ "^/([345]\.\d+)/.*\.html$" { 9 | try_files $uri @overview; 10 | } 11 | 12 | location ~ "^/4\.next/.*\.html$" { 13 | try_files $uri @overview; 14 | } 15 | 16 | location @overview { 17 | rewrite "^/([345]\.\d+)/.*$" /$1/ redirect; 18 | rewrite "^/4\.next/.*$" /$1/ redirect; 19 | return 404; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'namespaces' => 'Cake\Queue', 7 | 'sourceDirs' => ['src'], 8 | 'repo' => 'https://github.com/cakephp/queue', 9 | ], 10 | 11 | 'Twig' => [ 12 | 'templateDir' => 'templates', 13 | 'globals' => [ 14 | 'project' => 'Queue', 15 | 'release' => null, 16 | 'versions' => [ 17 | '2.x' => '../2.x', 18 | '1.x' => '../2.x', 19 | ], 20 | ], 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /config/authorization.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'namespaces' => 'Authorization', 7 | 'sourceDirs' => ['src'], 8 | 'excludePatterns' => [], 9 | 'repo' => 'https://github.com/cakephp/authorization', 10 | ], 11 | 12 | 'Twig' => [ 13 | 'templateDir' => 'templates', 14 | 'globals' => [ 15 | 'project' => 'Authorization', 16 | 'release' => null, 17 | 'versions' => [ 18 | '3.x' => '../3.x', 19 | '2.x' => '../2.x', 20 | ], 21 | ], 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /config/authentication.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'namespaces' => 'Authentication', 7 | 'sourceDirs' => ['src'], 8 | 'excludePatterns' => [], 9 | 'repo' => 'https://github.com/cakephp/authentication', 10 | ], 11 | 12 | 'Twig' => [ 13 | 'templateDir' => 'templates', 14 | 'globals' => [ 15 | 'project' => 'Authentication', 16 | 'release' => null, 17 | 'versions' => [ 18 | '3.x' => '../3.x', 19 | '2.x' => '../2.x', 20 | ], 21 | ], 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /config/elastic.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'namespaces' => 'Cake\ElasticSearch', 7 | 'sourceDirs' => ['src'], 8 | 'repo' => 'https://github.com/cakephp/elastic-search', 9 | ], 10 | 11 | 'Twig' => [ 12 | 'templateDir' => 'templates', 13 | 'globals' => [ 14 | 'project' => 'Elastic Search', 15 | 'release' => null, 16 | 'versions' => [ 17 | '4.x' => '../4.x', 18 | '3.x' => '../3.x', 19 | '2.x' => '../2.x', 20 | ], 21 | ], 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /.github/workflows/deploy_2x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Deploy API Docs - 2.x 3 | 4 | on: 5 | push: 6 | branches: 7 | - 2.x 8 | repository_dispatch: 9 | types: ['build'] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Cloning repo 17 | uses: actions/checkout@v5 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Push to dokku 22 | uses: dokku/github-action@master 23 | with: 24 | branch: 2.x 25 | git_remote_url: 'ssh://dokku@apps.cakephp.org:22/api-4' 26 | ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }} 27 | -------------------------------------------------------------------------------- /templates/menu/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  • Forum
  • 4 |
  • Stack Overflow
  • 5 |
  • IRC
  • 6 |
  • Slack
  • 7 |
  • Paid Support
  • 8 | -------------------------------------------------------------------------------- /templates/menu/documentation.html: -------------------------------------------------------------------------------- 1 |
  • Book
  • 2 |
  • API
  • 3 | 4 |
  • Videos
  • 5 |
  • Reporting Security Issues
  • 6 |
  • Privacy Policy
  • 7 |
  • Logos & Trademarks
  • 8 | -------------------------------------------------------------------------------- /config/chronos.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'namespaces' => 'Cake\Chronos', 7 | 'sourceDirs' => ['src'], 8 | 'exclude' => [ 9 | 'namespaces' => [ 10 | 'Cake\Chronos\Traits', 11 | ], 12 | ], 13 | 'repo' => 'https://github.com/cakephp/chronos', 14 | ], 15 | 16 | 'Twig' => [ 17 | 'templateDir' => 'templates', 18 | 'globals' => [ 19 | 'project' => 'Chronos', 20 | 'release' => null, 21 | 'versions' => [ 22 | '3.x' => '../3.x', 23 | '2.x' => '../2.x', 24 | '1.x' => '../1.x', 25 | ], 26 | ], 27 | ], 28 | ]; 29 | -------------------------------------------------------------------------------- /static/assets/resources/favicons/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CakePHP API docs # 2 | 3 | The CakePHP API docs allow you to build the API documentation as seen on 4 | https://api.cakephp.org 5 | 6 | 7 | 8 | ### Running documentation generator on a CakePHP codebase 9 | 10 | The tool generates documentation from CakePHP source code. According to the Makefile, you need: 11 | - A local clone of the CakePHP repository (default location: ../cakephp/) 12 | - Run the generator command 13 | 14 | For example, to test with CakePHP 5.2: 15 | 16 | #### Ensure you have a CakePHP clone 17 | ``` 18 | cd ../cakephp/ && git checkout origin/5.x && cd - 19 | ``` 20 | 21 | #### Run the generator 22 | ``` 23 | php bin/apitool.php generate --config cakephp5 --version 5.2 --tag origin/5.x --output-dir build/test/ ../cakephp 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /templates/pages/overview.twig: -------------------------------------------------------------------------------- 1 | {% extends 'page.twig' %} 2 | 3 | {% block title 'Overview' %} 4 | 5 | {% block content %} 6 |
    7 |

    {{ project }} {{ version }} API

    8 | 9 |

    Namespace Tree

    10 | 26 |
    27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedTrait.php: -------------------------------------------------------------------------------- 1 | =8.1", 13 | "cakephp/collection": "^5.0", 14 | "cakephp/console": "^5.0", 15 | "composer/composer": "^2.0", 16 | "erusev/parsedown": "^1.7", 17 | "nikic/php-parser": "^4.13", 18 | "phpstan/phpdoc-parser": "^1.29.1", 19 | "twig/markdown-extra": "^3.0", 20 | "twig/twig": "^3.0" 21 | }, 22 | "require-dev": { 23 | "cakephp/cakephp-codesniffer": "^4.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Cake\\ApiDocs\\": "src/" 28 | }, 29 | "files": [ 30 | "src/functions.php" 31 | ] 32 | }, 33 | "scripts": { 34 | "cs-check": "phpcs --colors --parallel=16 -p src/", 35 | "cs-fix": "phpcbf --colors --parallel=16 -p src/" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /static/assets/resources/open-hub.svg: -------------------------------------------------------------------------------- 1 | OpenHUB: CakePHPOpenHUBCakePHP -------------------------------------------------------------------------------- /src/Reflection/ReflectedProperty.php: -------------------------------------------------------------------------------- 1 | 2 |

    {{ title|default('Method') }} Summary

    3 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedMethod.php: -------------------------------------------------------------------------------- 1 | declared !== $this->owner && $this->declared instanceof ReflectedInterface) { 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Twig/TwigRuntimeLoader.php: -------------------------------------------------------------------------------- 1 | 2 |

    Property Summary

    3 | 34 | 35 | -------------------------------------------------------------------------------- /static/assets/js/jquery.sprintf.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * sprintf and vsprintf for jQuery 3 | * somewhat based on http://jan.moesen.nu/code/javascript/sprintf-and-printf-in-javascript/ 4 | * Copyright (c) 2008 Sabin Iacob (m0n5t3r) 5 | * @license http://www.gnu.org/licenses/gpl.html 6 | * @project jquery.sprintf 7 | */ 8 | (function(d){var a={b:function(e){return parseInt(e,10).toString(2)},c:function(e){return String.fromCharCode(parseInt(e,10))},d:function(e){return parseInt(e,10)},u:function(e){return Math.abs(e)},f:function(f,e){e=parseInt(e,10);f=parseFloat(f);if(isNaN(e&&f)){return NaN}return e&&f.toFixed(e)||f},o:function(e){return parseInt(e,10).toString(8)},s:function(e){return e},x:function(e){return(""+parseInt(e,10).toString(16)).toLowerCase()},X:function(e){return(""+parseInt(e,10).toString(16)).toUpperCase()}};var c=/%(?:(\d+)?(?:\.(\d+))?|\(([^)]+)\))([%bcdufosxX])/g;var b=function(f){if(f.length==1&&typeof f[0]=="object"){f=f[0];return function(i,h,k,j,g,m,l){return a[g](f[j])}}else{var e=0;return function(i,h,k,j,g,m,l){if(g=="%"){return"%"}return a[g](f[e++],k)}}};d.extend({sprintf:function(f){var e=Array.apply(null,arguments).slice(1);return f.replace(c,b(e))},vsprintf:function(f,e){return f.replace(c,b(e))}})})(jQuery); 9 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedClassLike.php: -------------------------------------------------------------------------------- 1 | context->namespace ? "{$this->context->namespace}\\{$this->name}" : $this->name; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/pages/parts/constant-detail.twig: -------------------------------------------------------------------------------- 1 |
    2 |

    Constants

    3 | 32 |
    33 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedNode.php: -------------------------------------------------------------------------------- 1 | doc = clone $this->doc; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/pages/parts/property-detail.twig: -------------------------------------------------------------------------------- 1 |
    2 |

    Property Detail

    3 | {% for property in properties %} 4 |
    5 | 6 |

    7 | ${{ property.name }} 8 | 9 | {% if property.source.inProject %} 10 | 11 | {% endif %} 12 | 13 | {{ property.visibility }} 14 | 15 | {% if property.static %} 16 | static 17 | {% endif %} 18 | {% if property.annotation %} 19 | {{ property.annotation }} 20 | {% endif %} 21 | {% if property.doc.tags.deprecated is defined %} 22 | deprecated 23 | {% endif %} 24 |

    25 | 26 |
    27 | {{ property.doc.summary|markdown_to_html }} 28 | {{ property.doc.description|markdown_to_html }} 29 | 30 |
    Type
    31 |
    32 | {{ property.type|type }}
    33 |
    34 |
    35 |
    36 | {% endfor %} 37 |
    38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 2.x 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | Docker: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v5 17 | 18 | - name: Build Dockerfile 19 | run: docker build . 20 | 21 | cs-stan: 22 | name: Coding Standard 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - uses: actions/checkout@v5 27 | 28 | - name: Setup PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: '8.2' 32 | extensions: mbstring, intl 33 | tools: cs2pr 34 | coverage: none 35 | 36 | - name: Get composer cache directory 37 | id: composer-cache 38 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 39 | 40 | - name: Get date part for cache key 41 | id: key-date 42 | run: echo "::set-output name=date::$(date +'%Y-%m')" 43 | 44 | - name: Cache composer dependencies 45 | uses: actions/cache@v5 46 | with: 47 | path: ${{ steps.composer-cache.outputs.dir }} 48 | key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }} 49 | 50 | - name: Composer install 51 | run: composer install 52 | 53 | - name: Run PHP CodeSniffer 54 | run: vendor/bin/phpcs -q --report=checkstyle src/ | cs2pr 55 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | add('generate', GenerateCommand::class); 44 | 45 | return $commands; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedParam.php: -------------------------------------------------------------------------------- 1 | name = $name; 44 | } 45 | 46 | /** 47 | * @return void 48 | */ 49 | public function __clone(): void 50 | { 51 | if ($this->type) { 52 | $this->type = clone $this->type; 53 | } 54 | if ($this->nativeType) { 55 | $this->nativeType = clone $this->nativeType; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Util/PrintUtil.php: -------------------------------------------------------------------------------- 1 | prettyPrintExpr($expr); 33 | } 34 | 35 | /** 36 | * @param \PhpParser\Node $node Node 37 | * @return string 38 | */ 39 | public static function node(Node $node): string 40 | { 41 | return static::printer()->prettyPrint([$node]); 42 | } 43 | 44 | /** 45 | * @return \PhpParser\PrettyPrinter\Standard 46 | */ 47 | protected static function printer(): Standard 48 | { 49 | static $printer; 50 | if (!isset($printer)) { 51 | $printer = new Standard(); 52 | } 53 | 54 | return $printer; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /templates/menu/community.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  • Get Involved
  • 4 |
  • Issues (Github)
  • 5 |
  • Bakery
  • 6 |
  • Featured Resources
  • 7 |
  • Training
  • 8 |
  • Meetups
  • 9 |
  • My CakePHP
  • 10 |
  • CakeFest
  • 11 |
  • Newsletter
  • 12 |
  • Linkedin
  • 13 |
  • YouTube
  • 14 |
  • Facebook
  • 15 |
  • Twitter
  • 16 |
  • Mastodon
  • 17 | -------------------------------------------------------------------------------- /src/Reflection/ReflectedFunction.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public array $params = []; 28 | 29 | public ?TypeNode $returnType = null; 30 | 31 | public ?TypeNode $nativeReturnType = null; 32 | 33 | public string $returnDescription = ''; 34 | 35 | public bool $abstract = false; 36 | 37 | public bool $static = false; 38 | 39 | /** 40 | * @return void 41 | */ 42 | public function __clone(): void 43 | { 44 | parent::__clone(); 45 | foreach ($this->params as &$param) { 46 | $param = clone $param; 47 | } 48 | if ($this->returnType) { 49 | $this->returnType = clone $this->returnType; 50 | } 51 | if ($this->nativeReturnType) { 52 | $this->nativeReturnType = clone $this->nativeReturnType; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /templates/pages/namespace.twig: -------------------------------------------------------------------------------- 1 | {% extends 'page.twig' %} 2 | 3 | {% block title 'Namespace ' ~ namespace.qualifiedName|default(namespace.name) %} 4 | 5 | {% block content %} 6 |
    7 |

    Namespace {{ namespace.name }}

    8 | 9 | {% if namespace.children is not empty %} 10 |
    11 |

    Namespaces

    12 | 19 |
    20 | {% endif %} 21 | 22 | {% if namespace.functions is not empty %} 23 | {{ include('pages/parts/function-detail.twig', {title: 'Function', functions: namespace.functions}) }} 24 | {% endif %} 25 | 26 | {% if namespace.defines is not empty %} 27 | {{ include('pages/parts/constant-detail.twig', {constants: namespace.defines}) }} 28 | {% endif %} 29 | 30 | {% macro showClassLikes(type, title, classLikes, type) %} 31 | {% if classLikes is not empty %} 32 |
    33 |

    {{ title }}

    34 |
      35 | {% for classLike in classLikes %} 36 |
    • 37 |
      {{ classLike.name }}
      38 | {{ classLike.doc.summary|markdown_to_html }} 39 |
    • 40 | {% endfor %} 41 |
    42 |
    43 | {% endif %} 44 | {% endmacro %} 45 | 46 | {{ _self.showClassLikes('interface', 'Interfaces', namespace.interfaces, 'interface') }} 47 | {{ _self.showClassLikes('class', 'Classes', namespace.classes, 'class') }} 48 | {{ _self.showClassLikes('trait', 'Traits', namespace.traits, 'trait') }} 49 |
    50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /config/cakephp5.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'namespaces' => 'Cake', 6 | 'sourceDirs' => ['src', 'config'], 7 | 'exclude' => [ 8 | 'classes' => [ 9 | 'Cake\Collection\CollectionTrait', 10 | 'Cake\Collection\ExtractTrait', 11 | 'Cake\Datasource\EntityTrait', 12 | 'Cake\Datasource\QueryTrait', 13 | 'Cake\I18n\DateFormatTrait', 14 | ], 15 | ], 16 | 'repo' => 'https://github.com/cakephp/cakephp', 17 | ], 18 | 19 | 'Twig' => [ 20 | 'templateDir' => 'templates', 21 | 'globals' => [ 22 | 'project' => 'CakePHP', 23 | 'release' => 'Chiffon', 24 | 'versions' => [ 25 | '5.2' => '../5.2/', 26 | '5.1' => '../5.1/', 27 | '5.0' => '../5.0/', 28 | '4.6' => '../4.6/', 29 | '4.5' => '../4.5/', 30 | '4.4' => '../4.4/', 31 | '4.3' => '../4.3/', 32 | '4.2' => '../4.2/', 33 | '4.1' => '../4.1/', 34 | '4.0' => '../4.0/', 35 | '3.10' => '../3.10', 36 | '3.9' => '../3.9/', 37 | '3.8' => '../3.8/', 38 | '3.7' => '../3.7/', 39 | '3.6' => '../3.6/', 40 | '3.5' => '../3.5/', 41 | '3.4' => '../3.4/', 42 | '3.3' => '../3.3/', 43 | '3.2' => '../3.2/', 44 | '3.1' => '../3.1/', 45 | '3.0' => '../3.0/', 46 | '2.10' => '../2.10/', 47 | '2.9' => '../2.9/', 48 | '2.8' => '../2.8/', 49 | '2.7' => '../2.7/', 50 | '2.6' => '../2.6/', 51 | '2.5' => '../2.5/', 52 | '2.4' => '../2.4/', 53 | '2.3' => '../2.3/', 54 | '2.2' => '../2.2/', 55 | '2.1' => '../2.1/', 56 | '2.0' => '../2.0/', 57 | '1.3' => '../1.3/', 58 | '1.2' => '../1.2/', 59 | ], 60 | ], 61 | ], 62 | ]; 63 | -------------------------------------------------------------------------------- /config/cakephp3.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'namespaces' => 'Cake', 6 | 'sourceDirs' => ['src', 'config'], 7 | 'exclude' => [ 8 | 'classes' => [ 9 | 'Cake\Collection\CollectionTrait', 10 | 'Cake\Collection\ExtractTrait', 11 | 'Cake\Datasource\EntityTrait', 12 | 'Cake\Datasource\QueryTrait', 13 | 'Cake\I18n\DateFormatTrait', 14 | ], 15 | ], 16 | 'repo' => 'https://github.com/cakephp/cakephp', 17 | ], 18 | 19 | 'Twig' => [ 20 | 'templateDir' => 'templates', 21 | 'globals' => [ 22 | 'project' => 'CakePHP', 23 | 'release' => 'Red Velvet', 24 | 'versions' => [ 25 | '5.2' => '../5.2/', 26 | '5.1' => '../5.1/', 27 | '5.0' => '../5.0/', 28 | '4.6' => '../4.6/', 29 | '4.5' => '../4.5/', 30 | '4.4' => '../4.4/', 31 | '4.3' => '../4.3/', 32 | '4.2' => '../4.2/', 33 | '4.1' => '../4.1/', 34 | '4.0' => '../4.0/', 35 | '3.10' => '../3.10', 36 | '3.9' => '../3.9/', 37 | '3.8' => '../3.8/', 38 | '3.7' => '../3.7/', 39 | '3.6' => '../3.6/', 40 | '3.5' => '../3.5/', 41 | '3.4' => '../3.4/', 42 | '3.3' => '../3.3/', 43 | '3.2' => '../3.2/', 44 | '3.1' => '../3.1/', 45 | '3.0' => '../3.0/', 46 | '2.10' => '../2.10/', 47 | '2.9' => '../2.9/', 48 | '2.8' => '../2.8/', 49 | '2.7' => '../2.7/', 50 | '2.6' => '../2.6/', 51 | '2.5' => '../2.5/', 52 | '2.4' => '../2.4/', 53 | '2.3' => '../2.3/', 54 | '2.2' => '../2.2/', 55 | '2.1' => '../2.1/', 56 | '2.0' => '../2.0/', 57 | '1.3' => '../1.3/', 58 | '1.2' => '../1.2/', 59 | ], 60 | ], 61 | ], 62 | ]; 63 | -------------------------------------------------------------------------------- /config/cakephp4.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'namespaces' => 'Cake', 6 | 'sourceDirs' => ['src', 'config'], 7 | 'exclude' => [ 8 | 'classes' => [ 9 | 'Cake\Collection\CollectionTrait', 10 | 'Cake\Collection\ExtractTrait', 11 | 'Cake\Datasource\EntityTrait', 12 | 'Cake\Datasource\QueryTrait', 13 | 'Cake\I18n\DateFormatTrait', 14 | ], 15 | ], 16 | 'repo' => 'https://github.com/cakephp/cakephp', 17 | ], 18 | 19 | 'Twig' => [ 20 | 'templateDir' => 'templates', 21 | 'globals' => [ 22 | 'project' => 'CakePHP', 23 | 'release' => 'Strawberry', 24 | 'versions' => [ 25 | '5.2' => '../5.2/', 26 | '5.1' => '../5.1/', 27 | '5.0' => '../5.0/', 28 | '4.6' => '../4.6/', 29 | '4.5' => '../4.5/', 30 | '4.4' => '../4.4/', 31 | '4.3' => '../4.3/', 32 | '4.2' => '../4.2/', 33 | '4.1' => '../4.1/', 34 | '4.0' => '../4.0/', 35 | '3.10' => '../3.10', 36 | '3.9' => '../3.9/', 37 | '3.8' => '../3.8/', 38 | '3.7' => '../3.7/', 39 | '3.6' => '../3.6/', 40 | '3.5' => '../3.5/', 41 | '3.4' => '../3.4/', 42 | '3.3' => '../3.3/', 43 | '3.2' => '../3.2/', 44 | '3.1' => '../3.1/', 45 | '3.0' => '../3.0/', 46 | '2.10' => '../2.10/', 47 | '2.9' => '../2.9/', 48 | '2.8' => '../2.8/', 49 | '2.7' => '../2.7/', 50 | '2.6' => '../2.6/', 51 | '2.5' => '../2.5/', 52 | '2.4' => '../2.4/', 53 | '2.3' => '../2.3/', 54 | '2.2' => '../2.2/', 55 | '2.1' => '../2.1/', 56 | '2.0' => '../2.0/', 57 | '1.3' => '../1.3/', 58 | '1.2' => '../1.2/', 59 | ], 60 | ], 61 | ], 62 | ]; 63 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build api docs with php 8.1 requirements 2 | FROM alpine:3.19 as builder 3 | 4 | RUN apk add --no-cache \ 5 | bash \ 6 | curl \ 7 | git \ 8 | make \ 9 | openssh-client \ 10 | php81 \ 11 | php82-bz2 \ 12 | php82-curl \ 13 | php82-dom \ 14 | php82-intl \ 15 | php82-json \ 16 | php82-mbstring \ 17 | php82-openssl \ 18 | php82-phar \ 19 | php82-simplexml \ 20 | php82-tokenizer \ 21 | php82-xml \ 22 | php82-xmlwriter \ 23 | php82-zip 24 | 25 | RUN ln -sf /usr/bin/php82 /usr/bin/php 26 | 27 | RUN mkdir /root/.ssh \ 28 | && ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 29 | 30 | WORKDIR /data 31 | COPY . /data 32 | 33 | RUN git clone https://github.com/cakephp/cakephp.git /cakephp \ 34 | && git clone https://github.com/cakephp/authentication.git /authentication \ 35 | && git clone https://github.com/cakephp/authorization.git /authorization \ 36 | && git clone https://github.com/cakephp/chronos.git /chronos \ 37 | && git clone https://github.com/cakephp/elastic-search.git /elastic \ 38 | && git clone https://github.com/cakephp/queue.git /queue 39 | 40 | RUN ls -lah \ 41 | && make build-cakephp3-all CAKEPHP_SOURCE_DIR=/cakephp \ 42 | && make build-cakephp4-all CAKEPHP_SOURCE_DIR=/cakephp \ 43 | && make build-cakephp5-all CAKEPHP_SOURCE_DIR=/cakephp \ 44 | && make build-authentication-all AUTHENTICATION_SOURCE_DIR=/authentication \ 45 | && make build-authorization-all AUTHORIZATION_SOURCE_DIR=/authorization \ 46 | && make build-chronos-all CHRONOS_SOURCE_DIR=/chronos \ 47 | && make build-elastic-all ELASTIC_SOURCE_DIR=/elastic \ 48 | && make build-queue-all QUEUE_SOURCE_DIR=/queue 49 | 50 | # nginx server 51 | FROM alpine:3.19 52 | 53 | LABEL Description="CakePHP API Docs" 54 | 55 | RUN apk add --no-cache \ 56 | nginx \ 57 | openssh-client 58 | 59 | # forward request and error logs to docker log collector 60 | RUN ln -sf /dev/stdout /var/log/nginx/access.log \ 61 | && ln -sf /dev/stderr /var/log/nginx/error.log 62 | 63 | COPY nginx.conf /etc/nginx/http.d/default.conf 64 | 65 | WORKDIR /var/www/html 66 | COPY --from=builder /data/build/api ./ 67 | 68 | EXPOSE 80 69 | 70 | CMD ["nginx", "-g", "daemon off;"] 71 | -------------------------------------------------------------------------------- /static/assets/js/jquery.sortElements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery.fn.sortElements 3 | * -------------- 4 | * @author James Padolsey (http://james.padolsey.com) 5 | * @version 0.11 6 | * @updated 18-MAR-2010 7 | * -------------- 8 | * @param Function comparator: 9 | * Exactly the same behaviour as [1,2,3].sort(comparator) 10 | * 11 | * @param Function getSortable 12 | * A function that should return the element that is 13 | * to be sorted. The comparator will run on the 14 | * current collection, but you may want the actual 15 | * resulting sort to occur on a parent or another 16 | * associated element. 17 | * 18 | * E.g. $('td').sortElements(comparator, function(){ 19 | * return this.parentNode; 20 | * }) 21 | * 22 | * The 's parent () will be sorted instead 23 | * of the itself. 24 | */ 25 | jQuery.fn.sortElements = (function(){ 26 | 27 | var sort = [].sort; 28 | 29 | return function(comparator, getSortable) { 30 | 31 | getSortable = getSortable || function(){return this;}; 32 | 33 | var placements = this.map(function(){ 34 | 35 | var sortElement = getSortable.call(this), 36 | parentNode = sortElement.parentNode, 37 | 38 | // Since the element itself will change position, we have 39 | // to have some way of storing it's original position in 40 | // the DOM. The easiest way is to have a 'flag' node: 41 | nextSibling = parentNode.insertBefore( 42 | document.createTextNode(''), 43 | sortElement.nextSibling 44 | ); 45 | 46 | return function() { 47 | 48 | if (parentNode === this) { 49 | throw new Error( 50 | "You can't sort elements if any one is a descendant of another." 51 | ); 52 | } 53 | 54 | // Insert before flag: 55 | parentNode.insertBefore(this, nextSibling); 56 | // Remove flag: 57 | parentNode.removeChild(nextSibling); 58 | 59 | }; 60 | 61 | }); 62 | 63 | return sort.call(this, comparator).each(function(i){ 64 | placements[i].call(getSortable.call(this)); 65 | }); 66 | 67 | }; 68 | 69 | })(); -------------------------------------------------------------------------------- /templates/pages/classlike.twig: -------------------------------------------------------------------------------- 1 | {% extends 'page.twig' %} 2 | 3 | {% block title type|capitalize ~ ' ' ~ ref.name %} 4 | 5 | {% block content %} 6 |
    7 |

    8 | {{ type|capitalize }} 9 | {% if ref.doc.tags.deprecated is defined %} 10 |
    {{ ref.name }}
    11 | {% else %} 12 | {{ ref.name }} 13 | {% endif %} 14 | {% if ref.source.inProject %} 15 | 16 | {% endif %} 17 |

    18 | 19 |
    20 | {{ ref.doc.summary|markdown_to_html }} 21 | {{ ref.doc.description|markdown_to_html }} 22 |
    23 | 24 | {#{{ macros.showInheritanceTree(ref.inheritanceTree) }}#} 25 | 26 |
    27 | {% if ref.final ?? false %} 28 | Final
    29 | {% endif %} 30 | {% if ref.abstract ?? false %} 31 | Abstract
    32 | {% endif %} 33 | 34 | Namespace: {{ ref.context.namespace }}
    35 | 36 | {% if ref.doc.tags.deprecated is defined %} 37 | Deprecated: {{ ref.doc.tags.deprecated }}
    38 | {% endif %} 39 | 40 | {% if ref.doc.tags.experimental is defined %} 41 | Experimental: Expect API changes in minor releases.
    42 | {% endif %} 43 | 44 | {% for tag in ref.doc.tags.see|default([]) %} 45 | See: {{ tag.value }}
    46 | {% endfor %} 47 | 48 | {% for tag in ref.doc.tags.link|default([]) %} 49 | Link: {{ tag.value }}
    50 | {% endfor %} 51 |
    52 | 53 | {% if ref.constants %} 54 | {{ include('pages/parts/constant-detail.twig', {constants: ref.constants}) }} 55 | {% endif %} 56 | 57 | {% if ref.properties %} 58 | {{ include('pages/parts/property-summary.twig', {properties: ref.properties}) }} 59 | {% endif %} 60 | 61 | {% if ref.methods %} 62 | {{ include('pages/parts/function-summary.twig', {functions: ref.methods}) }} 63 | 64 | {{ include('pages/parts/function-detail.twig', {functions: ref.methods}) }} 65 | {% endif %} 66 | 67 | {% if ref.properties %} 68 | {{ include('pages/parts/property-detail.twig', {properties: ref.properties}) }} 69 | {% endif %} 70 |
    71 | {% endblock %} 72 | -------------------------------------------------------------------------------- /src/Util/DocUtil.php: -------------------------------------------------------------------------------- 1 | parse(new TokenIterator(static::$docLexer->tokenize($comment))); 48 | } 49 | 50 | return new PhpDocNode([]); 51 | } 52 | 53 | /** 54 | * @param string $type Type string 55 | * @return \PHPStan\PhpDocParser\Ast\Type\TypeNode 56 | */ 57 | public static function parseType(string $type): TypeNode 58 | { 59 | static::init(); 60 | 61 | return static::$typeParser->parse(new TokenIterator(static::$docLexer->tokenize($type))); 62 | } 63 | 64 | /** 65 | * Initialize parser. 66 | * 67 | * @return void 68 | */ 69 | protected static function init(): void 70 | { 71 | if (!static::$initialized) { 72 | $exprParser = new ConstExprParser(); 73 | static::$typeParser = new TypeParser($exprParser); 74 | static::$docParser = new PhpDocParser(static::$typeParser, $exprParser); 75 | static::$docLexer = new Lexer(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Reflection/Context.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 36 | } 37 | 38 | /** 39 | * @param string $name ClassLike name 40 | * @return string 41 | */ 42 | public function resolveConstant(string $name): string 43 | { 44 | return $this->resolve($this->constants, $name); 45 | } 46 | 47 | /** 48 | * @param string $name Function name 49 | * @return string 50 | */ 51 | public function resolveFunction(string $name): string 52 | { 53 | return $this->resolve($this->functions, $name); 54 | } 55 | 56 | /** 57 | * @param string $name ClassLike name 58 | * @return string 59 | */ 60 | public function resolveClassLike(string $name): string 61 | { 62 | return $this->resolve($this->classLikes, $name); 63 | } 64 | 65 | /** 66 | * @param array $imports Imports 67 | * @param string $name Element name 68 | * @return string 69 | */ 70 | protected function resolve(array $imports, string $name): string 71 | { 72 | if (!$name) { 73 | return $name; 74 | } 75 | 76 | if ($name[0] === '\\') { 77 | return substr($name, 1); 78 | } 79 | 80 | $first = strchr($name, '\\', true); 81 | if ($first === false) { 82 | $first = $name; 83 | $append = ''; 84 | } else { 85 | $append = substr($name, strlen($first)); 86 | } 87 | 88 | if (isset($imports[$first])) { 89 | return $imports[$first] . $append; 90 | } 91 | 92 | return $this->namespace ? ($this->namespace . '\\' . $name) : $name; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Loader.php: -------------------------------------------------------------------------------- 1 | projectPath = $projectPath; 44 | $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); 45 | } 46 | 47 | /** 48 | * @param string $path Directory path 49 | * @param bool $inProject Whether directory is within project 50 | * @return array 51 | */ 52 | public function loadDirectory(string $path, bool $inProject): array 53 | { 54 | $nodes = []; 55 | $directoryIterator = new RecursiveDirectoryIterator( 56 | $path, 57 | FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_PATHNAME 58 | ); 59 | foreach (new RecursiveIteratorIterator($directoryIterator) as $filePath) { 60 | if (preg_match('/\.php$/', $filePath)) { 61 | $nodes = array_merge($nodes, $this->loadFile($filePath, $inProject)); 62 | } 63 | } 64 | 65 | return $nodes; 66 | } 67 | 68 | /** 69 | * @param string $path File path 70 | * @param bool $inProject Whether file is within project 71 | * @return array 72 | */ 73 | public function loadFile(string $path, bool $inProject): array 74 | { 75 | $stmts = $this->parser->parse(file_get_contents($path)); 76 | 77 | $traverser = new NodeTraverser(); 78 | $visitor = new FileVisitor(new Factory(), substr($path, strlen($this->projectPath) + 1), $inProject); 79 | $traverser->addVisitor($visitor); 80 | $traverser->traverse($stmts); 81 | 82 | $nodes = $visitor->getNodes(); 83 | foreach ($nodes as $node) { 84 | if ($node instanceof ReflectedClassLike) { 85 | $this->cache[$node->qualifiedName()] = $node; 86 | } 87 | } 88 | 89 | return $nodes; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /static/assets/resources/css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.22.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+javadoclike+markup-templating+php+phpdoc+php-extras */ 3 | /** 4 | * prism.js default theme for JavaScript, CSS and HTML 5 | * Based on dabblet (http://dabblet.com) 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | text-shadow: 0 1px white; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | text-align: left; 16 | white-space: pre; 17 | word-spacing: normal; 18 | word-break: normal; 19 | word-wrap: normal; 20 | line-height: 1.5; 21 | 22 | -moz-tab-size: 4; 23 | -o-tab-size: 4; 24 | tab-size: 4; 25 | 26 | -webkit-hyphens: none; 27 | -moz-hyphens: none; 28 | -ms-hyphens: none; 29 | hyphens: none; 30 | } 31 | 32 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 33 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 34 | text-shadow: none; 35 | background: #b3d4fc; 36 | } 37 | 38 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 39 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 40 | text-shadow: none; 41 | background: #b3d4fc; 42 | } 43 | 44 | @media print { 45 | code[class*="language-"], 46 | pre[class*="language-"] { 47 | text-shadow: none; 48 | } 49 | } 50 | 51 | /* Code blocks */ 52 | pre[class*="language-"] { 53 | padding: 1em; 54 | margin: .5em 0; 55 | overflow: auto; 56 | } 57 | 58 | :not(pre) > code[class*="language-"], 59 | pre[class*="language-"] { 60 | background: #f5f2f0; 61 | } 62 | 63 | /* Inline code */ 64 | :not(pre) > code[class*="language-"] { 65 | padding: .1em; 66 | border-radius: .3em; 67 | white-space: normal; 68 | } 69 | 70 | .token.comment, 71 | .token.prolog, 72 | .token.doctype, 73 | .token.cdata { 74 | color: slategray; 75 | } 76 | 77 | .token.punctuation { 78 | color: #999; 79 | } 80 | 81 | .token.namespace { 82 | opacity: .7; 83 | } 84 | 85 | .token.property, 86 | .token.tag, 87 | .token.boolean, 88 | .token.number, 89 | .token.constant, 90 | .token.symbol, 91 | .token.deleted { 92 | color: #905; 93 | } 94 | 95 | .token.selector, 96 | .token.attr-name, 97 | .token.string, 98 | .token.char, 99 | .token.builtin, 100 | .token.inserted { 101 | color: #690; 102 | } 103 | 104 | .token.operator, 105 | .token.entity, 106 | .token.url, 107 | .language-css .token.string, 108 | .style .token.string { 109 | color: #9a6e3a; 110 | /* This background color was intended by the author of this theme. */ 111 | background: hsla(0, 0%, 100%, .5); 112 | } 113 | 114 | .token.atrule, 115 | .token.attr-value, 116 | .token.keyword { 117 | color: #07a; 118 | } 119 | 120 | .token.function, 121 | .token.class-name { 122 | color: #DD4A68; 123 | } 124 | 125 | .token.regex, 126 | .token.important, 127 | .token.variable { 128 | color: #e90; 129 | } 130 | 131 | .token.important, 132 | .token.bold { 133 | font-weight: bold; 134 | } 135 | .token.italic { 136 | font-style: italic; 137 | } 138 | 139 | .token.entity { 140 | cursor: help; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /templates/footer.twig: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 14 |
    15 |
    16 |
    17 | 18 |
    19 |
    20 |
    21 |
    22 | 39 |
    40 | 41 |
    42 |
    43 | 46 |
    47 | 48 |
    49 | 53 |
    54 | 55 |
    56 | 59 |
    60 | 61 |
    62 | 65 |
    66 |
    67 |
    68 | 69 |
    70 |
    71 | 74 |
    75 |
    76 |
    77 |
    78 | -------------------------------------------------------------------------------- /src/ProjectNamespace.php: -------------------------------------------------------------------------------- 1 | 36 | */ 37 | public array $children = []; 38 | 39 | /** 40 | * @var array 41 | */ 42 | public array $defines = []; 43 | 44 | /** 45 | * @var array 46 | */ 47 | public array $constants = []; 48 | 49 | /** 50 | * @var array 51 | */ 52 | public array $functions = []; 53 | 54 | /** 55 | * @var array 56 | */ 57 | public array $interfaces = []; 58 | 59 | /** 60 | * @var array 61 | */ 62 | public array $classes = []; 63 | 64 | /** 65 | * @var array 66 | */ 67 | public array $traits = []; 68 | 69 | /** 70 | * @param string $name Display name 71 | * @param string|null $qualifiedName Qualified name 72 | */ 73 | public function __construct(string $name, ?string $qualifiedName) 74 | { 75 | $this->name = $name; 76 | $this->qualifiedName = $qualifiedName; 77 | } 78 | 79 | /** 80 | * @return bool 81 | */ 82 | public function isEmpty(): bool 83 | { 84 | return empty($this->children) && 85 | empty($this->constants) && 86 | empty($this->functions) && 87 | empty($this->interfaces) && 88 | empty($this->classes) && 89 | empty($this->traits); 90 | } 91 | 92 | /** 93 | * @param \Cake\ApiDocs\Reflection\ReflectedNode $ref Ref to add 94 | * @return void 95 | */ 96 | public function addNode(ReflectedNode $ref): void 97 | { 98 | if ($ref instanceof ReflectedDefine) { 99 | $this->constants[$ref->name] = $ref; 100 | ksort($this->constants); 101 | } elseif ($ref instanceof ReflectedFunction) { 102 | $this->functions[$ref->name] = $ref; 103 | ksort($this->functions); 104 | } elseif ($ref instanceof ReflectedInterface) { 105 | $this->interfaces[$ref->name] = $ref; 106 | ksort($this->interfaces); 107 | } elseif ($ref instanceof ReflectedClass) { 108 | $this->classes[$ref->name] = $ref; 109 | ksort($this->classes); 110 | } elseif ($ref instanceof ReflectedTrait) { 111 | $this->traits[$ref->name] = $ref; 112 | ksort($this->traits); 113 | } else { 114 | throw new InvalidArgumentException(sprintf('Cannot add node of type %s.', get_class($ref))); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Twig/Extension/ReflectionExtension.php: -------------------------------------------------------------------------------- 1 | project = $project; 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function getFilters() 46 | { 47 | return [ 48 | new TwigFilter('classlike_to_url', function (string $name, string $type) { 49 | return sprintf('%s-%s.html', $type, preg_replace('[\\\\]', '.', $name)); 50 | }), 51 | new TwigFilter('namespace_to_url', function (?string $name) { 52 | return sprintf('namespace-%s.html', preg_replace('[\\\\]', '.', $name ?: 'Global')); 53 | }), 54 | new TwigFilter('type', function (?TypeNode $type) { 55 | // Remove parenthesis added by phpstan around union and intersection types 56 | // Remove space around union and intersection delimiter 57 | // Remove leading \ in front of types 58 | return preg_replace( 59 | [ 60 | '/ ([|&]) /', 61 | '/<\(/', 62 | '/\)>/', 63 | '/\), /', 64 | '/, \(/', 65 | '/^\((.*)\)$/', 66 | '/(?<=[(<|&])\\\\/', 67 | '/^\\\\/', 68 | ], 69 | ['${1}', '<', '>', ', ', ', ', '${1}', '', ''], 70 | (string)$type 71 | ); 72 | }), 73 | new TwigFilter('node_to_repo_url', function (ReflectedNode $node) { 74 | preg_match('/^(?:origin\/)?(.*)$/', $this->project->getConfig('tag'), $matches); 75 | 76 | return sprintf( 77 | '%s/blob/%s/%s#L%d', 78 | $this->project->getConfig('repo'), 79 | $matches[1], 80 | $node->source->path, 81 | $node->source->startLine 82 | ); 83 | }), 84 | ]; 85 | } 86 | 87 | /** 88 | * @inheritDoc 89 | */ 90 | public function getTests() 91 | { 92 | return [ 93 | ]; 94 | } 95 | 96 | /** 97 | * @inheritDoc 98 | */ 99 | public function getFunctions() 100 | { 101 | return [ 102 | new TwigFunction('inNamespace', function (?string $name, ?string $namespace) { 103 | if ($name === null && $namespace !== null) { 104 | return false; 105 | } 106 | 107 | if ($name === null && $namespace == null) { 108 | return true; 109 | } 110 | 111 | return str_starts_with($namespace ?? '', $name); 112 | }), 113 | ]; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Command/GenerateCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('project-path', [ 42 | 'required' => true, 43 | 'help' => 'The project root path. Source directories are relative to this.', 44 | ]); 45 | 46 | $parser->addOption('output-dir', [ 47 | 'required' => true, 48 | 'help' => 'The render output directory.', 49 | ]); 50 | 51 | $parser->addOption('config', [ 52 | 'required' => true, 53 | 'help' => 'The config name to use, for example: cakephp4.', 54 | ]); 55 | 56 | $parser->addOption('version', [ 57 | 'required' => true, 58 | 'help' => 'The project version (example: 4.0)', 59 | ]); 60 | 61 | $parser->addOption('tag', [ 62 | 'required' => true, 63 | 'help' => 'The github tag or branch name', 64 | ]); 65 | 66 | return $parser; 67 | } 68 | 69 | /** 70 | * @inheritDoc 71 | */ 72 | public function execute(Arguments $args, ConsoleIo $io): ?int 73 | { 74 | Configure::config('default', new PhpConfig()); 75 | Configure::load($args->getOption('config'), 'default', false); 76 | 77 | Configure::write('Project.version', $args->getOption('version')); 78 | Configure::write('Project.tag', $args->getOption('tag')); 79 | 80 | $project = new Project($args->getArgumentAt(0), Configure::read('Project')); 81 | 82 | $twig = $this->createTwig(Configure::read('Twig.templateDir'), $project); 83 | $twig->addGlobal('version', Configure::read('Project.version')); 84 | foreach (Configure::read('Twig.globals') as $name => $value) { 85 | $twig->addGlobal($name, $value); 86 | } 87 | 88 | $generator = new Generator($twig, $args->getOption('output-dir')); 89 | $generator->generate($project); 90 | 91 | return static::CODE_SUCCESS; 92 | } 93 | 94 | /** 95 | * @param string $templateDir Twig template dirctory 96 | * @param \Cake\ApiDocs\Project $project Api Project 97 | * @return \Twig\Environment 98 | */ 99 | protected function createTwig(string $templateDir, Project $project): Environment 100 | { 101 | $twig = new Environment( 102 | new FilesystemLoader($templateDir), 103 | ['strict_variables' => true] 104 | ); 105 | 106 | $twig->addRuntimeLoader(new TwigRuntimeLoader()); 107 | $twig->addExtension(new MarkdownExtension()); 108 | $twig->addExtension(new ReflectionExtension($project)); 109 | 110 | return $twig; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Reflection/DocBlock.php: -------------------------------------------------------------------------------- 1 | children as $childNode) { 40 | if (!$childNode instanceof PhpDocTextNode) { 41 | break; 42 | } 43 | 44 | if (!$this->summary) { 45 | $this->summary = $childNode->text; 46 | } else { 47 | if ($this->description) { 48 | $this->description .= "\n" . $childNode->text; 49 | } else { 50 | $this->description = $childNode->text; 51 | } 52 | } 53 | } 54 | 55 | $tags = $node->getTagsByName('@template'); 56 | array_walk($tags, fn($tag) => $this->tags['template'][] = $tag->value); 57 | 58 | $tags = $node->getTagsByName('@var'); 59 | if ($tags) { 60 | $this->tags['var'] = current($tags)->value; 61 | } 62 | 63 | $tags = $node->getTagsByName('@param'); 64 | foreach ($tags as $tag) { 65 | if ($tag->value instanceof InvalidTagValueNode) { 66 | continue; 67 | } 68 | $this->tags['param'][$tag->value->parameterName] = $tag->value; 69 | } 70 | 71 | $tags = $node->getTagsByName('@return'); 72 | if ($tags) { 73 | $this->tags['return'] = current($tags)->value; 74 | } 75 | 76 | foreach (['@property', '@property-read', '@property-write'] as $tagName) { 77 | $tags = $node->getTagsByName($tagName); 78 | foreach ($tags as $tag) { 79 | if ($tag->value instanceof InvalidTagValueNode) { 80 | continue; 81 | } 82 | $this->tags[$tagName][$tag->value->propertyName] = $tag->value; 83 | } 84 | } 85 | 86 | $tags = $node->getTagsByName('@method'); 87 | foreach ($tags as $tag) { 88 | if ($tag->value instanceof InvalidTagValueNode) { 89 | continue; 90 | } 91 | $this->tags['@method'][$tag->value->methodName] = $tag->value; 92 | } 93 | 94 | $tags = $node->getTagsByName('@throws'); 95 | array_walk($tags, fn($tag) => $this->tags['throws'][] = $tag->value); 96 | 97 | $tags = $node->getTagsByName('@see'); 98 | array_walk($tags, fn($tag) => $this->tags['see'][] = $tag->value); 99 | 100 | $tags = $node->getTagsByName('@link'); 101 | array_walk($tags, fn($tag) => $this->tags['link'][] = $tag->value); 102 | 103 | $tags = $node->getTagsByName('@deprecated'); 104 | if ($tags) { 105 | $this->tags['deprecated'] = current($tags)->value; 106 | } 107 | 108 | $tags = $node->getTagsByName('@experimental'); 109 | if ($tags) { 110 | $this->tags['experimental'] = current($tags)->value; 111 | } 112 | 113 | $tags = $node->getTagsByName('@internal'); 114 | if ($tags) { 115 | $this->tags['internal'] = current($tags)->value; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /templates/page.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | {% block title 'API' %} | {{ project ~ ' ' ~ version }} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | 57 |
    58 | {{ include('header.twig') }} 59 | 60 |
    61 |
    62 | 88 | 89 | 92 |
    93 |
    94 |
    95 | 96 | {{ include('footer.twig') }} 97 | 98 | 99 | -------------------------------------------------------------------------------- /static/assets/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cookie plugin 3 | * 4 | * Copyright (c) 2006 Klaus Hartl (stilbuero.de) 5 | * Dual licensed under the MIT and GPL licenses: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * http://www.gnu.org/licenses/gpl.html 8 | * 9 | */ 10 | 11 | /** 12 | * Create a cookie with the given name and value and other optional parameters. 13 | * 14 | * @example $.cookie('the_cookie', 'the_value'); 15 | * @desc Set the value of a cookie. 16 | * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); 17 | * @desc Create a cookie with all available options. 18 | * @example $.cookie('the_cookie', 'the_value'); 19 | * @desc Create a session cookie. 20 | * @example $.cookie('the_cookie', null); 21 | * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain 22 | * used when the cookie was set. 23 | * 24 | * @param String name The name of the cookie. 25 | * @param String value The value of the cookie. 26 | * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. 27 | * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. 28 | * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. 29 | * If set to null or omitted, the cookie will be a session cookie and will not be retained 30 | * when the the browser exits. 31 | * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). 32 | * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). 33 | * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will 34 | * require a secure protocol (like HTTPS). 35 | * @type undefined 36 | * 37 | * @name $.cookie 38 | * @cat Plugins/Cookie 39 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 40 | */ 41 | 42 | /** 43 | * Get the value of a cookie with the given name. 44 | * 45 | * @example $.cookie('the_cookie'); 46 | * @desc Get the value of a cookie. 47 | * 48 | * @param String name The name of the cookie. 49 | * @return The value of the cookie. 50 | * @type String 51 | * 52 | * @name $.cookie 53 | * @cat Plugins/Cookie 54 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 55 | */ 56 | jQuery.cookie = function(name, value, options) { 57 | if (typeof value != 'undefined') { // name and value given, set cookie 58 | options = options || {}; 59 | if (value === null) { 60 | value = ''; 61 | options.expires = -1; 62 | } 63 | var expires = ''; 64 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { 65 | var date; 66 | if (typeof options.expires == 'number') { 67 | date = new Date(); 68 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); 69 | } else { 70 | date = options.expires; 71 | } 72 | expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE 73 | } 74 | // CAUTION: Needed to parenthesize options.path and options.domain 75 | // in the following expressions, otherwise they evaluate to undefined 76 | // in the packed version for some reason... 77 | var path = options.path ? '; path=' + (options.path) : ''; 78 | var domain = options.domain ? '; domain=' + (options.domain) : ''; 79 | var secure = options.secure ? '; secure' : ''; 80 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); 81 | } else { // only name given, get cookie 82 | var cookieValue = null; 83 | if (document.cookie && document.cookie != '') { 84 | var cookies = document.cookie.split(';'); 85 | for (var i = 0; i < cookies.length; i++) { 86 | var cookie = jQuery.trim(cookies[i]); 87 | // Does this cookie string begin with the name we want? 88 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 89 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 90 | break; 91 | } 92 | } 93 | } 94 | return cookieValue; 95 | } 96 | }; -------------------------------------------------------------------------------- /templates/pages/parts/function-detail.twig: -------------------------------------------------------------------------------- 1 |
    2 |

    {{ title|default('Method') }} Detail

    3 | {% for function in functions %} 4 |
    5 | 6 |

    7 | {{ function.name }}() 8 | 9 | {% if function.source.inProject %} 10 | 11 | {% endif %} 12 | 13 | {% if function.abstract|default(false) %} 14 | abstract 15 | {% endif %} 16 | 17 | {% if function.visibility is defined %} 18 | {{ function.visibility }} 19 | {% endif %} 20 | {% if function.static %} 21 | static 22 | {% endif %} 23 | {% if function.annotation|default(null) %} 24 | {{ function.annotation }} 25 | {% endif %} 26 | {% if function.doc.tags.depecated is defined %} 27 | deprecated 28 | {% endif %} 29 |

    30 | 31 |
    
     32 |         {{- function.name }}(
     33 |           {%- for param in function.params %}
     34 |             {{- param.type|type|default('mixed') }} {% if param.variadic %}...{% endif %}${{ param.name }}{{ param.default is not null ? ' = ' ~ param.default : '' }}{{ loop.last?'':', ' }}
     35 |           {%- endfor -%}
     36 |         ){{ function.returnType ? ': ' ~ function.returnType|type : '' -}}
     37 |       
    38 | 39 |
    40 | {{ function.doc.summary|markdown_to_html }} 41 | {{ function.doc.description|markdown_to_html }} 42 | 43 | {% if function.doc.tags.template is defined %} 44 |
    Templates
    45 |
    46 | {% for tag in function.doc.tags.template %} 47 | 48 | {{ tag.name }} 49 | {% if tag.bound is not null %} of {{tag.bound}}{% endif %} 50 | {% if tag.default is not null %} = {{tag.default}}{% endif %} 51 |
    52 | {{ tag.description }}{{ loop.last ? '' : '
    ' }} 53 | {% endfor %} 54 |
    55 | {% endif %} 56 | 57 | {% if function.params %} 58 |
    Parameters
    59 |
    60 |
    61 | {% for param in function.params %} 62 |
    63 | {{ param.type|type }} 64 | {% if param.variadic %}...{% endif %}${{ param.name }} 65 | {% if param.default is not null %} 66 | optional 67 | {% endif %} 68 |
    69 |
    {{ (param.description ?? '')|markdown_to_html }}
    70 | {% endfor %} 71 |
    72 |
    73 | {% endif %} 74 | 75 | {% if function.returnType %} 76 |
    Returns
    77 |
    78 | {{ function.returnType|type }}
    79 | {% if function.returnDescription %} 80 | {{ function.returnDescription|markdown_to_html }} 81 | {% endif %} 82 |
    83 | {% endif %} 84 | 85 | {% if function.doc.tags.throws is defined %} 86 |
    Throws
    87 |
    88 | {% for tag in function.doc.tags.throws %} 89 | {{ tag.type|type }}
    90 | {{ tag.description }}{{ loop.last ? '' : '
    ' }} 91 | {% endfor %} 92 |
    93 | {% endif %} 94 | 95 | {% if function.doc.tags.see is defined %} 96 |
    See Also
    97 |
    98 | {% for tag in function.doc.tags.see %} 99 | {{ tag.value }}{{ loop.last ? '': '
    ' }} 100 | {% endfor %} 101 |
    102 | {% endif %} 103 | 104 | {% if function.doc.tags.link is defined %} 105 |
    Links
    106 |
    107 | {% for tag in function.doc.tags.link %} 108 | {{ tag.value }}
    {{ loop.last ? '' : '
    ' }} 109 | {% endfor %} 110 |
    111 | {% endif %} 112 |
    113 |
    114 | {% endfor %} 115 |
    116 | -------------------------------------------------------------------------------- /src/Util/MergeUtil.php: -------------------------------------------------------------------------------- 1 | constants as $name => $sourceConstant) { 45 | $targetConstant = $target->constants[$name] ?? null; 46 | if (!$targetConstant) { 47 | $target->constants[$name] = clone $sourceConstant; 48 | continue; 49 | } 50 | 51 | static::mergeDoc($targetConstant->doc, $sourceConstant->doc); 52 | if ($targetConstant->type === null) { 53 | $targetConstant->type = $sourceConstant->type; 54 | } 55 | } 56 | ksort($target->constants); 57 | } 58 | 59 | /** 60 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $target Target class-like 61 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $source Source class-like 62 | * @return void 63 | */ 64 | protected static function mergeProperties(ReflectedClassLike $target, ReflectedClassLike $source): void 65 | { 66 | foreach ($source->properties as $name => $sourceProperty) { 67 | $targetProperty = $target->properties[$name] ?? null; 68 | if (!$targetProperty) { 69 | $target->properties[$name] = clone $sourceProperty; 70 | continue; 71 | } 72 | 73 | static::mergeDoc($targetProperty->doc, $sourceProperty->doc); 74 | if ( 75 | $targetProperty->type === null || 76 | ( 77 | !isset($targetProperty->doc->tags['var']) && 78 | $targetProperty->nativeType == $sourceProperty->nativeType 79 | ) 80 | ) { 81 | $targetProperty->type = $sourceProperty->type; 82 | } 83 | } 84 | ksort($target->properties); 85 | } 86 | 87 | /** 88 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $target Target class-like 89 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $source Source class-like 90 | * @return void 91 | */ 92 | protected static function mergeMethods(ReflectedClassLike $target, ReflectedClassLike $source): void 93 | { 94 | foreach ($source->methods as $name => $sourceMethod) { 95 | $targetMethod = $target->methods[$name] ?? null; 96 | if (!$targetMethod) { 97 | $target->methods[$name] = clone $sourceMethod; 98 | continue; 99 | } 100 | 101 | if (count($targetMethod->params) !== count($sourceMethod->params)) { 102 | // Ignore methods that have different number of parameters such as overriden constructors 103 | continue; 104 | } 105 | 106 | static::mergeDoc($targetMethod->doc, $sourceMethod->doc); 107 | if ( 108 | $targetMethod->returnType === null || 109 | ( 110 | !isset($targetMethod->doc->tags['return']) && 111 | $targetMethod->nativeReturnType == $sourceMethod->nativeReturnType 112 | ) 113 | ) { 114 | $targetMethod->returnType = $sourceMethod->returnType; 115 | } 116 | 117 | foreach ($targetMethod->params as $param) { 118 | if ( 119 | isset($sourceMethod->params[$param->name]) && 120 | ( 121 | $param->type === null || 122 | ( 123 | !isset($targetMethod->doc->tags['param'][$param->name]) && 124 | $param->nativeType == $sourceMethod->params[$param->name]->nativeType 125 | ) 126 | ) 127 | ) { 128 | $param->type = $sourceMethod->params[$param->name]->type; 129 | } 130 | } 131 | } 132 | ksort($target->methods); 133 | } 134 | 135 | /** 136 | * @param \Cake\ApiDocs\Reflection\DocBlock $target Target doc 137 | * @param \Cake\ApiDocs\Reflection\DocBlock $source Source doc 138 | * @return bool 139 | */ 140 | protected static function mergeDoc(DocBlock $target, DocBlock $source): bool 141 | { 142 | $inherited = false; 143 | if (preg_match('/(^$)|(^@inheritDoc$)|(^{@inheritDoc}$)/i', $target->summary)) { 144 | $target->summary = $source->summary; 145 | $inherited = true; 146 | } 147 | 148 | if (preg_match('/(^$)|(^@inheritDoc$)|(^{@inheritDoc}$)/i', $target->description)) { 149 | $target->description = $source->description; 150 | $inherited = true; 151 | } elseif (preg_match('/^(@inheritDoc|{@inheritDoc})\n\n([\s\S]+)/i', $target->description, $matches)) { 152 | $target->description = $source->description . "\n\n---\n\n" . $matches[2]; 153 | $inherited = true; 154 | } 155 | 156 | return $inherited; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | twig = $twig; 44 | $this->outputDir = $outputDir; 45 | } 46 | 47 | /** 48 | * Render project. 49 | * 50 | * @param \Cake\ApiDocs\Project $project Project 51 | * @return void 52 | */ 53 | public function generate(Project $project): void 54 | { 55 | $this->log(sprintf('Generating project into `%s`', $this->outputDir), 'info'); 56 | 57 | $this->twig->addGlobal('namespaces', $project->namespaces); 58 | 59 | $this->renderOverview(); 60 | $this->renderSearch($project->namespaces); 61 | 62 | array_walk($project->namespaces, fn ($namespace) => $this->renderNamespace($namespace)); 63 | } 64 | 65 | /** 66 | * Render overview page. 67 | * 68 | * @return void 69 | */ 70 | public function renderOverview(): void 71 | { 72 | $this->renderTemplate('pages/overview.twig', 'index.html'); 73 | } 74 | 75 | /** 76 | * Render all child namespaces, classes, interfaces and traits in namespace. 77 | * 78 | * @param \Cake\ApiDocs\ProjectNamespace $ns Project namespace 79 | * @return void 80 | */ 81 | public function renderNamespace(ProjectNamespace $ns): void 82 | { 83 | $renderer = function (ProjectNamespace $ns) use (&$renderer): void { 84 | $this->renderTemplate( 85 | 'pages/namespace.twig', 86 | sprintf('namespace-%s.html', str_replace('\\', '.', $ns->qualifiedName ?? $ns->name)), 87 | ['namespace' => $ns, 'contextName' => $ns->name] 88 | ); 89 | 90 | array_map(fn($interface) => $this->renderClassLike($interface, 'interface'), $ns->interfaces); 91 | array_map(fn($class) => $this->renderClassLike($class, 'class'), $ns->classes); 92 | array_map(fn($trait) => $this->renderClassLike($trait, 'trait'), $ns->traits); 93 | 94 | foreach ($ns->children as $child) { 95 | $renderer($child); 96 | } 97 | }; 98 | 99 | $renderer($ns); 100 | } 101 | 102 | /** 103 | * Renders class, interface or trait. 104 | * 105 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $ref Reflected classlike 106 | * @param string $type Class-like type 107 | * @return void 108 | */ 109 | protected function renderClassLike(ReflectedClassLike $ref, string $type): void 110 | { 111 | $filename = sprintf('%s-%s.html', $type, str_replace('\\', '.', $ref->qualifiedName())); 112 | $this->renderTemplate( 113 | 'pages/classlike.twig', 114 | $filename, 115 | ['ref' => $ref, 'type' => $type, 'contextName' => $ref->context->namespace] 116 | ); 117 | } 118 | 119 | /** 120 | * Renders search data. 121 | * 122 | * @param array $namespaces Project namespaces 123 | * @return void 124 | */ 125 | protected function renderSearch(array $namespaces): void 126 | { 127 | $entries = []; 128 | 129 | $addClassLike = function (ReflectedClassLike $classLike) use (&$entries) { 130 | $type = match (true) { 131 | $classLike instanceof ReflectedInterface => 'i', 132 | $classLike instanceof ReflectedTrait => 't', 133 | $classLike instanceof ReflectedClassLike => 'c' 134 | }; 135 | 136 | $entries[] = [$type, $classLike->qualifiedName()]; 137 | foreach ($classLike->constants as $constant) { 138 | if ( 139 | $constant->visibility === 'public' && 140 | ( 141 | !$constant->source->inProject || $constant->owner === $classLike 142 | ) 143 | ) { 144 | $entries[] = [$type, sprintf('%s::%s', $classLike->qualifiedName(), $constant->name)]; 145 | } 146 | } 147 | foreach ($classLike->properties as $property) { 148 | if ( 149 | $property->visibility === 'public' && 150 | ( 151 | !$property->source->inProject || $property->owner === $classLike 152 | ) 153 | ) { 154 | $entries[] = [$type, sprintf('%s::$%s', $classLike->qualifiedName(), $property->name)]; 155 | } 156 | } 157 | foreach ($classLike->methods as $method) { 158 | if ( 159 | $method->visibility === 'public' && 160 | ( 161 | !$method->source->inProject || $method->owner === $classLike 162 | ) 163 | ) { 164 | $entries[] = [$type, sprintf('%s::%s()', $classLike->qualifiedName(), $method->name)]; 165 | } 166 | } 167 | }; 168 | 169 | $addNamespace = function (ProjectNamespace $ns) use (&$addNamespace, $addClassLike) { 170 | array_walk($ns->children, fn ($ns) => $addNamespace($ns)); 171 | array_walk($ns->interfaces, fn ($classLike) => $addClassLike($classLike)); 172 | array_walk($ns->traits, fn ($classLike) => $addClassLike($classLike)); 173 | array_walk($ns->classes, fn ($classLike) => $addClassLike($classLike)); 174 | }; 175 | 176 | array_walk($namespaces, $addNamespace); 177 | 178 | $this->renderTemplate('searchlist.twig', 'searchlist.js', ['entries' => json_encode(array_values($entries))]); 179 | } 180 | 181 | /** 182 | * @param string $template Twig template name 183 | * @param string $filename Output filename 184 | * @param array $context Twig render context 185 | * @return void 186 | */ 187 | protected function renderTemplate(string $template, string $filename, array $context = []): void 188 | { 189 | $path = getcwd() . DS . $this->outputDir . DS . $filename; 190 | file_put_contents($path, $this->twig->render($template, $context)); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /static/assets/resources/css/responsive.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Phone/phablet layout. 3 | */ 4 | @media (max-width: 767px) { 5 | .description { 6 | padding: 0; 7 | } 8 | 9 | .logo-cake img { 10 | margin-top: 20px; 11 | margin-bottom: 14px; 12 | } 13 | 14 | .navbar-right { 15 | float: right!important; 16 | margin-right: -15px; 17 | } 18 | 19 | .title-red { 20 | font-size: 40px; 21 | } 22 | 23 | #quote h4 { 24 | font-size: 35px; 25 | } 26 | 27 | #quote h5 { 28 | text-align: left; 29 | line-height: 1; 30 | margin-top: 30px; 31 | } 32 | 33 | #quote h6 { 34 | text-align: left; 35 | } 36 | 37 | .subtitle-black { 38 | font-size: 30px; 39 | line-height: 34px; 40 | } 41 | 42 | 43 | .search-form { 44 | top: -21px; 45 | } 46 | 47 | .icon-irc, .icon-social-q { 48 | margin-bottom: 30px; 49 | } 50 | 51 | .submenu { 52 | background-color: #D33C44; 53 | } 54 | 55 | #wrap .ac_input:focus { 56 | width: 270px; 57 | } 58 | 59 | #wrap input[type="submit"] { 60 | height: 58px; 61 | } 62 | 63 | .nav-up { 64 | top: -600px; 65 | } 66 | 67 | .menu-title-m { 68 | margin-top: 180px; 69 | } 70 | 71 | .logo { 72 | padding: 14px 0; 73 | } 74 | 75 | .toggle-menu .fa { 76 | margin: 21px 0; 77 | } 78 | 79 | #sub { 80 | margin-top: 60px; 81 | } 82 | 83 | #cakefest p { 84 | text-align: center; 85 | } 86 | 87 | .sub-expertise { 88 | text-align: center; 89 | } 90 | 91 | #expertise p { 92 | text-align: center; 93 | } 94 | 95 | .icon-expertise-2 { 96 | text-align: center; 97 | } 98 | 99 | .title-expertise{ 100 | text-align: center; 101 | } 102 | 103 | .box-services-d:hover .icon-expertise-2 { 104 | text-align: center; 105 | } 106 | 107 | .git-frame iframe{ 108 | padding-left: 34%; 109 | } 110 | 111 | .social-footer a { 112 | padding: 10px 6px; 113 | } 114 | 115 | .social { 116 | margin-bottom: 15px; 117 | } 118 | 119 | .social iframe { 120 | margin: 5px; 121 | } 122 | 123 | /* ---------------------- COOK BOOK ------------------------- */ 124 | 125 | 126 | #cookbook { 127 | padding-top: 50px; 128 | } 129 | 130 | .page-container { 131 | padding-top: 20px; 132 | } 133 | 134 | .nav-btn { 135 | padding-top: 73px; 136 | padding-bottom: 17px; 137 | } 138 | 139 | .read-the-book a { 140 | margin: 15px 5px 22px; 141 | } 142 | 143 | .page-container .row { 144 | margin-left: -10px; 145 | margin-right: -10px; 146 | } 147 | 148 | .btn-nav { 149 | font-size: 11px; 150 | } 151 | 152 | #improve-slideout { 153 | bottom: 0; 154 | } 155 | 156 | #improve-slideout-inner { 157 | bottom: 0; 158 | } 159 | 160 | .page-contents { 161 | float: none; 162 | margin-left: 0; 163 | } 164 | 165 | .list, 166 | .property-type { 167 | overflow-x: auto; 168 | } 169 | 170 | h1 { 171 | font-size: 34px; 172 | } 173 | } 174 | 175 | @media (min-width:768px) { 176 | .row.col-p30 { margin-left:-15px; margin-right:-15px; } 177 | } 178 | 179 | @media (min-width:1200px) { 180 | .row.col-p30 { margin-left:-30px; margin-right:-30px; } 181 | 182 | .row.col-p30 [class*="col-"].business-solution { 183 | padding: 0 15px; 184 | } 185 | } 186 | 187 | 188 | 189 | 190 | /** 191 | * Tablet Portrait layout. 192 | */ 193 | @media (min-width: 768px) and (max-width: 991px) { 194 | .header-transparent .main-header { 195 | background-color: #d33c44; 196 | } 197 | 198 | .search-form { 199 | top: -21px; 200 | } 201 | 202 | .menu > li > a:hover, .menu > li > a:focus, .menu > li:hover > a { 203 | color: #ffffff; 204 | } 205 | 206 | .menu > li > a { 207 | padding: 25px 25px; 208 | } 209 | 210 | .logo-cake img { 211 | margin-top: 15px; 212 | margin-bottom: 14px; 213 | } 214 | 215 | #sub { 216 | margin-top: 60px; 217 | } 218 | 219 | /* ---------------------- COOK BOOK ------------------------- */ 220 | 221 | .page-container { 222 | padding-top: 20px; 223 | } 224 | 225 | #wrap .ac_input:focus { 226 | width: 450px; 227 | } 228 | 229 | #wrap input[type="submit"] { 230 | height: 58px; 231 | } 232 | 233 | #improve-slideout { 234 | bottom: 0; 235 | } 236 | 237 | #improve-slideout-inner { 238 | bottom: 0; 239 | } 240 | } 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | @media (min-width: 992px) and (max-width: 1199px) { 252 | /*CSS LIDO EM DISPOSITIVOS ATÉ 1199 PX DE LARGURA*/ 253 | .menu > li > a { 254 | padding: 25px 20px; 255 | } 256 | 257 | .menu-fixed .menu > li > a { 258 | padding: 25px 25px; 259 | } 260 | .fa-menu-title { 261 | font-size: 17px; 262 | display: block; 263 | padding-bottom: 8px; 264 | } 265 | /* ---------------------- COOK BOOK ------------------------- */ 266 | 267 | .t-language h6 { 268 | font-size: 9px; 269 | } 270 | 271 | .dropdown > a { 272 | font-size: 12px; 273 | padding: 5px 5px !important; 274 | } 275 | } 276 | 277 | /* Site menu in modal window */ 278 | #modal .menu { 279 | float: none; 280 | } 281 | #modal .menu > li { 282 | float: none; 283 | display: block; 284 | width: 100%; 285 | margin-bottom: 1em; 286 | } 287 | #modal .menu .fa-chevron-down { 288 | display: none; 289 | } 290 | #modal .menu > li { 291 | background: #aaabac; 292 | border-bottom: 2px solid #aaabac; 293 | } 294 | #modal .menu > li > a { 295 | color: #363638; 296 | font-weight: bold; 297 | display: block; 298 | } 299 | #modal .menu a { 300 | color: #667; 301 | padding: 10px; 302 | } 303 | #modal .menu-title { 304 | padding: 2px 0 0 10px; 305 | color: #363637; 306 | } 307 | #modal .megamenu, 308 | #modal .submenu .submenu, 309 | #modal .submenu { 310 | margin: 2px 2px 0 2px; 311 | padding: 0; 312 | background: #F1F1F1; 313 | min-width: auto; 314 | width: auto; 315 | top: 0; 316 | left: 0; 317 | position: relative; 318 | z-index: auto; 319 | } 320 | 321 | #modal .megamenu { 322 | padding-top: 20px; 323 | } 324 | 325 | #modal .megamenu-list { 326 | padding-top: 5px; 327 | } 328 | #modal .megamenu-list li { 329 | padding: 0; 330 | } 331 | #modal .col-3 { 332 | padding: 0; 333 | width: 100%; 334 | border-bottom: 2px solid #aaabac; 335 | margin-bottom: 0; 336 | } 337 | #modal .col-3:last-child { 338 | border-bottom: 0; 339 | } 340 | 341 | /* Book nav in modal menu */ 342 | #modal .back-book:hover, 343 | #modal .back-book { 344 | background: transparent; 345 | } 346 | #modal .back-book h6, 347 | #modal .back-book h2 { 348 | color: #363638; 349 | } 350 | #modal .t-language h6 { 351 | text-align: left; 352 | color: #363638; 353 | } 354 | #modal .search, 355 | #modal .dropdown .fa-chevron-down { 356 | display: none; 357 | } 358 | #modal .navbar-nav { 359 | float: none; 360 | } 361 | #modal .dropdown-toggle { 362 | display: inline-block; 363 | position: static; 364 | } 365 | #modal .dropdown-menu { 366 | float: none; 367 | display: inline; 368 | position: static; 369 | background: transparent; 370 | box-shadow: none; 371 | } 372 | #modal .dropdown-menu li { 373 | float: none; 374 | display: inline-block 375 | } 376 | 377 | @media (min-width: 768px) and (max-width: 1199px) { 378 | .social { 379 | margin-bottom: 20px; 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/FileVisitor.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 58 | $this->filePath = $filePath; 59 | $this->inProject = $inProject; 60 | $this->context = new Context(null); 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getNodes(): array 67 | { 68 | return $this->nodes; 69 | } 70 | 71 | /** 72 | * @inheritDoc 73 | */ 74 | public function enterNode(Node $node) 75 | { 76 | if ($node instanceof Namespace_) { 77 | $this->context = new Context((string)$node->name ?: null); 78 | 79 | return null; 80 | } 81 | 82 | if ($node instanceof Use_) { 83 | foreach ($node->uses as $use) { 84 | switch ($node->type) { 85 | case Use_::TYPE_NORMAL: 86 | $this->context->classLikes += $this->normalizeUse($use); 87 | break; 88 | case Use_::TYPE_FUNCTION: 89 | $this->context->functions += $this->normalizeUse($use); 90 | break; 91 | case Use_::TYPE_CONSTANT: 92 | $this->context->constants += $this->normalizeUse($use); 93 | break; 94 | } 95 | } 96 | 97 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 98 | } 99 | 100 | if ($node instanceof GroupUse) { 101 | $prefix = (string)$node->prefix; 102 | foreach ($node->uses as $use) { 103 | switch ($use->type) { 104 | case Use_::TYPE_NORMAL: 105 | $this->context->classLikes += $this->normalizeUse($use, $prefix); 106 | break; 107 | case Use_::TYPE_FUNCTION: 108 | $this->context->functions += $this->normalizeUse($use, $prefix); 109 | break; 110 | case Use_::TYPE_CONSTANT: 111 | $this->context->constants += $this->normalizeUse($use, $prefix); 112 | break; 113 | } 114 | } 115 | 116 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 117 | } 118 | 119 | if ($node instanceof FuncCall && (string)$node->name === 'define') { 120 | if (count($node->args) === 2 && $node->args[0]->value instanceof String_) { 121 | $const = new NodeConst_( 122 | (string)$node->args[0]->value->value, 123 | $node->args[1]->value, 124 | $node->getAttributes() 125 | ); 126 | $source = new Source($this->filePath, $this->inProject, $const->getStartLine(), $const->getEndLine()); 127 | $this->nodes[] = $this->factory->createDefine($const, $this->context, $source); 128 | } 129 | 130 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 131 | } 132 | 133 | if ($node instanceof Const_) { 134 | foreach ($node->consts as $const) { 135 | $source = new Source($this->filePath, $this->inProject, $const->getStartLine(), $const->getEndLine()); 136 | $this->nodes[] = $this->factory->createDefine($const, $this->context, $source); 137 | } 138 | 139 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 140 | } 141 | 142 | if ($node instanceof Function_) { 143 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine()); 144 | $this->nodes[] = $this->factory->createFunction($node, $this->context, $source); 145 | 146 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 147 | } 148 | 149 | if ($node instanceof Interface_) { 150 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine()); 151 | $this->nodes[] = $this->factory->createInterface($node, $this->context, $source); 152 | 153 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 154 | } 155 | 156 | if ($node instanceof Class_) { 157 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine()); 158 | $this->nodes[] = $this->factory->createClass($node, $this->context, $source); 159 | 160 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 161 | } 162 | 163 | if ($node instanceof Trait_) { 164 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine()); 165 | $this->nodes[] = $this->factory->createTrait($node, $this->context, $source); 166 | 167 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 168 | } 169 | 170 | return null; 171 | } 172 | 173 | /** 174 | * @inheritDoc 175 | */ 176 | public function leaveNode(Node $node) 177 | { 178 | if ($node instanceof Namespace_) { 179 | $this->context = new Context(null); 180 | } 181 | 182 | return null; 183 | } 184 | 185 | /** 186 | * @param \PhpParser\Node\Stmt\UseUse $use Use node 187 | * @param string|null $prefix Group use prefix 188 | * @return array 189 | */ 190 | protected function normalizeUse(UseUse $use, ?string $prefix = null): array 191 | { 192 | $name = (string)$use->name; 193 | if ($prefix) { 194 | $name = $prefix . '\\' . $name; 195 | } 196 | 197 | $alias = $use->alias; 198 | if (!$alias) { 199 | $last = strrpos($name, '\\', -1); 200 | if ($last !== false) { 201 | $alias = substr($name, strrpos($name, '\\', -1) + 1); 202 | } else { 203 | $alias = $name; 204 | } 205 | } 206 | 207 | return [(string)$alias => $name]; 208 | } 209 | 210 | /** 211 | * @param string $name Node name 212 | * @return string 213 | */ 214 | protected function addNamespace(string $name): string 215 | { 216 | if ($this->context->namespace) { 217 | return $this->context->namespace . '\\' . $name; 218 | } 219 | 220 | return $name; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /templates/header.twig: -------------------------------------------------------------------------------- 1 | {# Red Mega Menu #} 2 | 167 | 168 | {# Responsive grey bar navigation. This is outside of header so it scrolls with the page. #} 169 | 182 | 183 | {# modal used in mobile responsive views #} 184 | 197 | -------------------------------------------------------------------------------- /static/assets/resources/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Project.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | public array $namespaces; 39 | 40 | protected Loader $loader; 41 | 42 | protected ?ClassLoader $classLoader; 43 | 44 | protected array $cache = []; 45 | 46 | protected array $_defaultConfig = []; 47 | 48 | /** 49 | * Loads projects. 50 | * 51 | * @param string $projectPath Project path 52 | * @param array $config Project config 53 | */ 54 | public function __construct(string $projectPath, array $config) 55 | { 56 | $this->setConfig($config); 57 | 58 | foreach ((array)$this->_config['namespaces'] as $namespace) { 59 | $this->namespaces[$namespace] = new ProjectNamespace($namespace, $namespace); 60 | } 61 | 62 | $this->loader = new Loader($projectPath); 63 | $this->classLoader = $this->createClassLoader($projectPath); 64 | 65 | foreach ($this->_config['sourceDirs'] as $dir) { 66 | $this->log(sprintf('Loading sources from `%s`', $dir), 'info'); 67 | $nodes = $this->loader->loadDirectory($projectPath . DIRECTORY_SEPARATOR . $dir, true); 68 | 69 | foreach ($nodes as $node) { 70 | if ($node instanceof ReflectedClassLike) { 71 | $this->cache[$node->qualifiedName()] = $node; 72 | 73 | if (in_array($node->qualifiedName(), $config['exclude']['classes'] ?? [], true)) { 74 | continue; 75 | } 76 | } 77 | 78 | if (in_array($node->context->namespace, $config['exclude']['namespaces'] ?? [], true)) { 79 | continue; 80 | } 81 | 82 | $namespace = $this->getNamespace($node->context->namespace); 83 | $namespace->addNode($node); 84 | } 85 | } 86 | ksort($this->namespaces); 87 | 88 | $this->mergeInherited(); 89 | } 90 | 91 | /** 92 | * @return void 93 | */ 94 | protected function mergeInherited(): void 95 | { 96 | $classLikeMerger = function (ReflectedClassLike $ref) use (&$classLikeMerger) { 97 | foreach ($ref->uses as $use) { 98 | $trait = $this->findClassLike($ref->context->resolveClassLike($use)); 99 | if (!$trait) { 100 | continue; 101 | } 102 | $classLikeMerger($trait); 103 | MergeUtil::mergeClassLike($ref, $trait); 104 | } 105 | 106 | if ($ref instanceof ReflectedClass) { 107 | foreach ($ref->implements as $implement) { 108 | $interface = $this->findClassLike($ref->context->resolveClassLike($implement)); 109 | if (!$interface) { 110 | continue; 111 | } 112 | $classLikeMerger($interface); 113 | MergeUtil::mergeClassLike($ref, $interface); 114 | } 115 | } 116 | 117 | if ($ref instanceof ReflectedInterface || $ref instanceof ReflectedClass) { 118 | foreach ((array)$ref->extends ?? [] as $extend) { 119 | $interface = $this->findClassLike($ref->context->resolveClassLike($extend)); 120 | if (!$interface) { 121 | continue; 122 | } 123 | $classLikeMerger($interface); 124 | MergeUtil::mergeClassLike($ref, $interface); 125 | } 126 | } 127 | }; 128 | 129 | $namespaceMerger = function (ProjectNamespace $ns) use (&$namespaceMerger, $classLikeMerger) { 130 | foreach ($ns->children as $child) { 131 | $namespaceMerger($child); 132 | } 133 | 134 | foreach ($ns->interfaces as $interface) { 135 | $classLikeMerger($interface); 136 | } 137 | foreach ($ns->traits as $trait) { 138 | $classLikeMerger($trait); 139 | } 140 | foreach ($ns->classes as $class) { 141 | $classLikeMerger($class); 142 | } 143 | }; 144 | 145 | array_walk($this->namespaces, fn ($namespace) => $namespaceMerger($namespace)); 146 | } 147 | 148 | /** 149 | * @param string $qualifiedName Qualified name 150 | * @return \Cake\ApiDocs\Reflection\ReflectedClassLike|null 151 | */ 152 | protected function findClassLike(string $qualifiedName): ?ReflectedClassLike 153 | { 154 | if (array_key_exists($qualifiedName, $this->cache)) { 155 | return $this->cache[$qualifiedName]; 156 | } 157 | 158 | if (!$this->classLoader) { 159 | return null; 160 | } 161 | 162 | $path = $this->classLoader->findFile($qualifiedName); 163 | if ($path === false) { 164 | return null; 165 | } 166 | 167 | $nodes = $this->loader->loadFile($path, false); 168 | foreach ($nodes as $node) { 169 | if ($node instanceof ReflectedClassLike && $node->qualifiedName() === $qualifiedName) { 170 | return $node; 171 | } 172 | } 173 | 174 | return $this->cache[$qualifiedName] = null; 175 | } 176 | 177 | /** 178 | * @param string $projectPath Project path 179 | * @return \Composer\Autoload\ClassLoader|null 180 | */ 181 | protected function createClassLoader(string $projectPath): ?ClassLoader 182 | { 183 | // try to find vendor/ relative to sourceDir 184 | $vendorDir = $projectPath . DIRECTORY_SEPARATOR . 'vendor'; 185 | 186 | $autoloadPath = $vendorDir . DIRECTORY_SEPARATOR . 'autoload.php'; 187 | if (!file_exists($autoloadPath)) { 188 | $this->log("Unable to find class loader at `$autoloadPath`", 'warning'); 189 | 190 | return null; 191 | } 192 | 193 | $this->log("Found class loader at `$autoloadPath`", 'info'); 194 | $loader = new ClassLoader($vendorDir); 195 | 196 | // Get geneated class name from autoload_static.php 197 | $staticPath = $vendorDir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'autoload_static.php'; 198 | $contents = file_get_contents($staticPath); 199 | if ($contents === false) { 200 | $this->log("Unable to load `$staticPath`", 'error'); 201 | 202 | return false; 203 | } 204 | 205 | if (preg_match('/class (.*)\n/', $contents, $matches) === false) { 206 | $this->log("Unable to find class in `$staticPath`", 'error'); 207 | 208 | return false; 209 | } 210 | 211 | require $staticPath; 212 | 213 | $staticClass = '\\Composer\\Autoload\\' . $matches[1]; 214 | call_user_func($staticClass::getInitializer($loader)); 215 | 216 | return $loader; 217 | } 218 | 219 | /** 220 | * Finds or creates project namespace. 221 | * 222 | * @param string|null $qualifiedName Qualified name 223 | * @return self|null 224 | */ 225 | protected function getNamespace(?string $qualifiedName): ProjectNamespace 226 | { 227 | if ($qualifiedName === null) { 228 | return $this->namespaces[''] ??= new ProjectNamespace('Global', null); 229 | } 230 | 231 | $namespace = null; 232 | foreach ($this->namespaces as $root) { 233 | if (str_starts_with($qualifiedName, $root->qualifiedName)) { 234 | $namespace = $root; 235 | break; 236 | } 237 | } 238 | 239 | if ($namespace === null) { 240 | throw new RuntimeException(sprintf('Namespace `%s` is part of the project namespaces.', $qualifiedName)); 241 | } 242 | 243 | if ($namespace->qualifiedName === $qualifiedName) { 244 | return $namespace; 245 | } 246 | 247 | $names = explode('\\', substr($qualifiedName, strlen($namespace->qualifiedName) + 1)); 248 | foreach ($names as $name) { 249 | if (isset($namespace->children[$name])) { 250 | $namespace = $namespace->children[$name]; 251 | continue; 252 | } 253 | 254 | $child = new ProjectNamespace($name, $namespace->qualifiedName . '\\' . $name); 255 | $namespace->children[$name] = $child; 256 | ksort($namespace->children); 257 | 258 | $namespace = $child; 259 | } 260 | 261 | return $namespace; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CAKEPHP_SOURCE_DIR=../cakephp 2 | CHRONOS_SOURCE_DIR=../chronos 3 | ELASTIC_SOURCE_DIR=../elastic-search 4 | QUEUE_SOURCE_DIR=../queue 5 | AUTHENTICATION_SOURCE_DIR=../authentication 6 | AUTHORIZATION_SOURCE_DIR=../authorization 7 | BUILD_DIR=./build/api 8 | DEPLOY_DIR=./website 9 | PHP=php 10 | COMPOSER=$(PWD)/composer.phar 11 | 12 | .PHONY: clean help 13 | .PHONY: build-cakephp-3 14 | .PHONY: build-cakephp-4 15 | .PHONY: build-cakephp-5 16 | .PHONY: build-chronos 17 | .PHONY: build-elastic 18 | .PHONY: build-queue 19 | .PHONY: build-authentication 20 | .PHONY: build-authorization 21 | .PHONY: build-active-and-missing 22 | .ALL: help 23 | 24 | # Versions that can be built. 25 | CAKEPHP3_VERSIONS = 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 26 | CAKEPHP4_VERSIONS = 4.0 4.1 4.2 4.3 4.4 4.5 4.6 27 | CAKEPHP5_VERSIONS = 5.0 5.1 5.2 28 | 29 | CHRONOS_VERSIONS = 1.x 2.x 3.x 30 | 31 | ELASTIC_VERSIONS = 2.x 3.x 4.x 32 | 33 | QUEUE_VERSIONS = 1.x 2.x 34 | 35 | AUTHENTICATION_VERSIONS = 2.x 3.x 36 | 37 | AUTHORIZATION_VERSIONS = 2.x 3.x 38 | 39 | help: 40 | @echo "CakePHP API Documentation generator" 41 | @echo "-----------------------------------" 42 | @echo "" 43 | @echo "Tasks:" 44 | @echo "" 45 | @echo " clean - Clean the build output directory" 46 | @echo "" 47 | @echo " build-name-v - Build the version v documentation. The versions that can be" 48 | @echo " built are:" 49 | @echo " $(VERSIONS)" 50 | @echo "" 51 | @echo "Variables:" 52 | @echo "" 53 | @echo " CAKEPHP_SOURCE_DIR - Define where your cakephp clone is." 54 | @echo " CHRONOS_SOURCE_DIR - Define where your chronos clone is." 55 | @echo " ELASTIC_SOURCE_DIR - Define where your elastic-search clone is." 56 | @echo " QUEUE_SOURCE_DIR - Define where your queue clone is." 57 | @echo " AUTHENTICATION_SOURCE_DIR - Define where your authentication clone is." 58 | @echo " AUTHORIZATION_SOURCE_DIR - Define where your authentication clone is." 59 | @echo " QUEUE_SOURCE_DIR - Define where your queue clone is." 60 | @echo " BUILD_DIR - The directory where the output should go. Default: $(BUILD_DIR)" 61 | @echo " DEPLOY_DIR - The directory files shold be copied to in deploy Default: $(DEPLOY_DIR)" 62 | @echo "" 63 | @echo "NOTE: Source directories will have their checkout branch changed." 64 | @echo " Make sure all working directories are clean." 65 | 66 | clean: 67 | rm -rf $(DEPLOY_DIR) 68 | rm -rf $(BUILD_DIR) 69 | 70 | 71 | # Make the deployment directory 72 | $(DEPLOY_DIR): 73 | mkdir -p $(DEPLOY_DIR) 74 | 75 | deploy: $(DEPLOY_DIR) 76 | for release in $$(ls $(BUILD_DIR)); do \ 77 | rm -rf $(DEPLOY_DIR)/$$release; \ 78 | mkdir -p $(DEPLOY_DIR)/$$release; \ 79 | mv $(BUILD_DIR)/$$release $(DEPLOY_DIR)/; \ 80 | done 81 | 82 | 83 | composer.phar: 84 | curl -sS https://getcomposer.org/installer | php 85 | 86 | install: composer.phar 87 | $(PHP) $(COMPOSER) install 88 | 89 | define cakephp3-no-vendor 90 | build-cakephp-$(VERSION): install 91 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG) 92 | cd $(CAKEPHP_SOURCE_DIR) && rm -rf ./vendor 93 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION) 94 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION) 95 | 96 | $(PHP) bin/apitool.php generate --config cakephp3 --version $(VERSION) --tag $(TAG) \ 97 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR) 98 | endef 99 | 100 | define cakephp3 101 | build-cakephp-$(VERSION): install 102 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG) 103 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 104 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION) 105 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION) 106 | 107 | $(PHP) bin/apitool.php generate --config cakephp3 --version $(VERSION) --tag $(TAG) \ 108 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR) 109 | endef 110 | 111 | define cakephp4 112 | build-cakephp-$(VERSION): install 113 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG) 114 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 115 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION) 116 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION) 117 | 118 | $(PHP) bin/apitool.php generate --config cakephp4 --version $(VERSION) --tag $(TAG) \ 119 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR) 120 | endef 121 | 122 | define cakephp5 123 | build-cakephp-$(VERSION): install 124 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG) 125 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 126 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION) 127 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION) 128 | 129 | $(PHP) bin/apitool.php generate --config cakephp5 --version $(VERSION) --tag $(TAG) \ 130 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR) 131 | endef 132 | 133 | define chronos 134 | build-chronos-$(VERSION): install 135 | cd $(CHRONOS_SOURCE_DIR) && git checkout -f $(TAG) 136 | cd $(CHRONOS_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 137 | mkdir -p $(BUILD_DIR)/chronos/$(VERSION) 138 | cp -r static/assets/* $(BUILD_DIR)/chronos/$(VERSION) 139 | 140 | $(PHP) bin/apitool.php generate --config chronos --version $(VERSION) --tag $(TAG) \ 141 | --output-dir $(BUILD_DIR)/chronos/$(VERSION) $(CHRONOS_SOURCE_DIR) 142 | endef 143 | 144 | define elastic 145 | build-elastic-$(VERSION): install 146 | cd $(ELASTIC_SOURCE_DIR) && git checkout -f $(TAG) 147 | cd $(ELASTIC_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 148 | mkdir -p $(BUILD_DIR)/elastic-search/$(VERSION) 149 | cp -r static/assets/* $(BUILD_DIR)/elastic-search/$(VERSION) 150 | 151 | $(PHP) bin/apitool.php generate --config elastic --version $(VERSION) --tag $(TAG) \ 152 | --output-dir $(BUILD_DIR)/elastic-search/$(VERSION) $(ELASTIC_SOURCE_DIR) 153 | endef 154 | 155 | define queue 156 | build-queue-$(VERSION): install 157 | cd $(QUEUE_SOURCE_DIR) && git checkout -f $(TAG) 158 | cd $(QUEUE_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 159 | mkdir -p $(BUILD_DIR)/queue/$(VERSION) 160 | cp -r static/assets/* $(BUILD_DIR)/queue/$(VERSION) 161 | 162 | $(PHP) bin/apitool.php generate --config queue --version $(VERSION) --tag $(TAG) \ 163 | --output-dir $(BUILD_DIR)/queue/$(VERSION) $(QUEUE_SOURCE_DIR) 164 | endef 165 | 166 | define authentication 167 | build-authentication-$(VERSION): install 168 | cd $(AUTHENTICATION_SOURCE_DIR) && git checkout -f $(TAG) 169 | cd $(AUTHENTICATION_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 170 | mkdir -p $(BUILD_DIR)/authentication/$(VERSION) 171 | cp -r static/assets/* $(BUILD_DIR)/authentication/$(VERSION) 172 | 173 | $(PHP) bin/apitool.php generate --config authentication --version $(VERSION) --tag $(TAG) \ 174 | --output-dir $(BUILD_DIR)/authentication/$(VERSION) $(AUTHENTICATION_SOURCE_DIR) 175 | endef 176 | 177 | define authorization 178 | build-authorization-$(VERSION): install 179 | cd $(AUTHORIZATION_SOURCE_DIR) && git checkout -f $(TAG) 180 | cd $(AUTHORIZATION_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs 181 | mkdir -p $(BUILD_DIR)/authorization/$(VERSION) 182 | cp -r static/assets/* $(BUILD_DIR)/authorization/$(VERSION) 183 | 184 | $(PHP) bin/apitool.php generate --config authorization --version $(VERSION) --tag $(TAG) \ 185 | --output-dir $(BUILD_DIR)/authorization/$(VERSION) $(AUTHORIZATION_SOURCE_DIR) 186 | endef 187 | 188 | # Build all the versions in a loop. 189 | build-cakephp3-all: $(foreach version, $(CAKEPHP3_VERSIONS), build-cakephp-$(version)) 190 | build-cakephp4-all: $(foreach version, $(CAKEPHP4_VERSIONS), build-cakephp-$(version)) 191 | build-cakephp5-all: $(foreach version, $(CAKEPHP5_VERSIONS), build-cakephp-$(version)) 192 | 193 | build-chronos-all: $(foreach version, $(CHRONOS_VERSIONS), build-chronos-$(version)) 194 | build-elastic-all: $(foreach version, $(ELASTIC_VERSIONS), build-elastic-$(version)) 195 | build-queue-all: $(foreach version, $(QUEUE_VERSIONS), build-queue-$(version)) 196 | build-authentication-all: $(foreach version, $(AUTHENTICATION_VERSIONS), build-authentication-$(version)) 197 | build-authorization-all: $(foreach version, $(AUTHORIZATION_VERSIONS), build-authorization-$(version)) 198 | 199 | # Generate build targets for cakephp 200 | TAG:=3.0.19 201 | VERSION:=3.0 202 | $(eval $(cakephp3-no-vendor)) 203 | 204 | TAG:=3.1.14 205 | VERSION:=3.1 206 | $(eval $(cakephp3-no-vendor)) 207 | 208 | TAG:=3.2.14 209 | VERSION:=3.2 210 | $(eval $(cakephp3)) 211 | 212 | TAG:=3.3.16 213 | VERSION:=3.3 214 | $(eval $(cakephp3)) 215 | 216 | TAG:=3.4.14 217 | VERSION:=3.4 218 | $(eval $(cakephp3)) 219 | 220 | TAG:=3.5.18 221 | VERSION:=3.5 222 | $(eval $(cakephp3)) 223 | 224 | TAG:=3.6.15 225 | VERSION:=3.6 226 | $(eval $(cakephp3)) 227 | 228 | TAG:=3.7.9 229 | VERSION:=3.7 230 | $(eval $(cakephp3)) 231 | 232 | TAG:=3.8.13 233 | VERSION:=3.8 234 | $(eval $(cakephp3)) 235 | 236 | TAG:=3.9.10 237 | VERSION:=3.9 238 | $(eval $(cakephp3)) 239 | 240 | TAG:=origin/3.x 241 | VERSION:=3.10 242 | $(eval $(cakephp3)) 243 | 244 | TAG:=4.0.9 245 | VERSION:=4.0 246 | $(eval $(cakephp4)) 247 | 248 | TAG:=4.1.7 249 | VERSION:=4.1 250 | $(eval $(cakephp4)) 251 | 252 | TAG:=4.2.10 253 | VERSION:=4.2 254 | $(eval $(cakephp4)) 255 | 256 | TAG:=4.3.10 257 | VERSION:=4.3 258 | $(eval $(cakephp4)) 259 | 260 | TAG:=4.4.18 261 | VERSION:=4.4 262 | $(eval $(cakephp4)) 263 | 264 | TAG:=4.5.10 265 | VERSION:=4.5 266 | $(eval $(cakephp4)) 267 | 268 | TAG:=origin/4.x 269 | VERSION:=4.6 270 | $(eval $(cakephp4)) 271 | 272 | TAG:=5.0.11 273 | VERSION:=5.0 274 | $(eval $(cakephp5)) 275 | 276 | TAG:=5.1.6 277 | VERSION:=5.1 278 | $(eval $(cakephp5)) 279 | 280 | TAG:=origin/5.x 281 | VERSION:=5.2 282 | $(eval $(cakephp5)) 283 | 284 | # Generate build targets for chronos 285 | TAG:=origin/1.x 286 | VERSION:=1.x 287 | $(eval $(chronos)) 288 | 289 | TAG:=origin/2.x 290 | VERSION:=2.x 291 | $(eval $(chronos)) 292 | 293 | TAG:=origin/3.x 294 | VERSION:=3.x 295 | $(eval $(chronos)) 296 | 297 | # Generate build targets for elastic-search 298 | TAG:=origin/2.x 299 | VERSION:=2.x 300 | $(eval $(elastic)) 301 | 302 | TAG:=origin/3.x 303 | VERSION:=3.x 304 | $(eval $(elastic)) 305 | 306 | TAG:=origin/4.x 307 | VERSION:=4.x 308 | $(eval $(elastic)) 309 | 310 | # Generate build targets for queue 311 | TAG:=origin/1.x 312 | VERSION:=1.x 313 | $(eval $(queue)) 314 | 315 | TAG:=origin/2.x 316 | VERSION:=2.x 317 | $(eval $(queue)) 318 | 319 | # Generate build targets for authetication 320 | TAG:=origin/2.x 321 | VERSION:=2.x 322 | $(eval $(authentication)) 323 | 324 | TAG:=origin/3.x 325 | VERSION:=3.x 326 | $(eval $(authentication)) 327 | 328 | # Generate build targets for authorization 329 | TAG:=origin/2.x 330 | VERSION:=2.x 331 | $(eval $(authorization)) 332 | 333 | TAG:=origin/3.x 334 | VERSION:=3.x 335 | $(eval $(authorization)) 336 | -------------------------------------------------------------------------------- /static/assets/js/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ApiGen 3.0dev - API documentation generator for PHP 5.3+ 3 | * 4 | * Copyright (c) 2010-2011 David Grudl (http://davidgrudl.com) 5 | * Copyright (c) 2011-2012 Jaroslav Hanslík (https://github.com/kukulich) 6 | * Copyright (c) 2011-2012 Ondřej Nešpor (https://github.com/Andrewsville) 7 | * 8 | * For the full copyright and license information, please view 9 | * the file LICENSE.md that was distributed with this source code. 10 | */ 11 | 12 | $(function() { 13 | var $win = $(window); 14 | var $body = $('body'); 15 | var $document = $(document); 16 | var $left = $('#left'); 17 | var $right = $('#right'); 18 | var $rightInner = $('#rightInner'); 19 | var $groups = $('#groups'); 20 | var $content = $('#content'); 21 | 22 | // Hide deep packages and namespaces 23 | $('ul span', $groups).click(function(event) { 24 | event.preventDefault(); 25 | event.stopPropagation(); 26 | $(this) 27 | .toggleClass('collapsed') 28 | .parent() 29 | .next('ul') 30 | .toggleClass('collapsed'); 31 | }).click(); 32 | 33 | $active = $('ul li.active', $groups); 34 | if ($active.length > 0) { 35 | // Open active 36 | $('> a > span', $active).click(); 37 | } else { 38 | $main = $('> ul > li.main', $groups); 39 | if ($main.length > 0) { 40 | // Open first level of the main project 41 | $('> a > span', $main).click(); 42 | } else { 43 | // Open first level of all 44 | $('> ul > li > a > span', $groups).click(); 45 | } 46 | } 47 | 48 | /* Validate function */ 49 | function validate(data, def) { 50 | return (data !== undefined) ? data : def; 51 | } 52 | 53 | // Window width without scrollbar 54 | $windowWidth = $win.width(), 55 | 56 | // Media Query fix (outerWidth -- scrollbar) 57 | // Media queries width include the scrollbar 58 | mqWidth = $win.outerWidth(true, true), 59 | 60 | // Detect Mobile Devices 61 | isMobileDevice = (( navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone|IEMobile|Opera Mini|Mobi/i) || (mqWidth < 767) ) ? true : false ); 62 | 63 | // detect IE browsers 64 | var ie = (function(){ 65 | var rv = 0, 66 | ua = window.navigator.userAgent, 67 | msie = ua.indexOf('MSIE '), 68 | trident = ua.indexOf('Trident/'); 69 | 70 | if (msie > 0) { 71 | // IE 10 or older => return version number 72 | rv = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); 73 | } else if (trident > 0) { 74 | // IE 11 (or newer) => return version number 75 | var rvNum = ua.indexOf('rv:'); 76 | rv = parseInt(ua.substring(rvNum + 3, ua.indexOf('.', rvNum)), 10); 77 | } 78 | 79 | return ((rv > 0) ? rv : 0); 80 | }()); 81 | 82 | // Responsive Menus 83 | $('#modal').on('show.bs.modal', function (event) { 84 | var modal = $(this); 85 | var button = $(event.relatedTarget); 86 | var id = button.attr('id'); 87 | var contents, title; 88 | if (id == 'btn-menu') { 89 | title = 'Menu'; 90 | contents = $('#cake-nav').html(); 91 | } 92 | if (id == 'btn-nav') { 93 | title = 'Navigation'; 94 | contents = $('#nav-cook').html(); 95 | } 96 | if (id == 'btn-toc') { 97 | title = 'Class Navigation'; 98 | contents = $('#class-nav').html(); 99 | } 100 | 101 | modal.find('.modal-body').html(contents); 102 | modal.find('.modal-title-cookbook').text(title); 103 | 104 | // Bind click events for sub menus. 105 | modal.find('li > a').on('click', function() { 106 | var el = $(this).parent(), 107 | subMenu = el.find('.submenu, .megamenu'); 108 | // No menu, bail 109 | if (subMenu.length === 0) { 110 | return; 111 | } 112 | subMenu.toggle(); 113 | return false; 114 | }); 115 | }); 116 | 117 | /* ********************* Megamenu ********************* */ 118 | var menu = $(".menu"), 119 | Megamenu = { 120 | desktopMenu: function() { 121 | 122 | menu.children("li").show(0); 123 | 124 | // Mobile touch for tablets > 768px 125 | if (isMobileDevice) { 126 | menu.on("click touchstart","a", function(e){ 127 | if ($(this).attr('href') === '#') { 128 | e.preventDefault(); 129 | e.stopPropagation(); 130 | } 131 | 132 | var $this = $(this), 133 | $sub = $this.siblings(".submenu, .megamenu"); 134 | 135 | $this.parent("li").siblings("li").find(".submenu, .megamenu").stop(true, true).fadeOut(300); 136 | 137 | if ($sub.css("display") === "none") { 138 | $sub.stop(true, true).fadeIn(300); 139 | } else { 140 | $sub.stop(true, true).fadeOut(300); 141 | $this.siblings(".submenu").find(".submenu").stop(true, true).fadeOut(300); 142 | } 143 | }); 144 | 145 | $(document).on("click.menu touchstart.menu", function(e){ 146 | if ($(e.target).closest(menu).length === 0) { 147 | menu.find(".submenu, .megamenu").fadeOut(300); 148 | } 149 | }); 150 | 151 | // Desktop hover effect 152 | } else { 153 | menu.find('li').on({ 154 | "mouseenter": function() { 155 | $(this).children(".submenu, .megamenu").stop(true, true).fadeIn(300); 156 | }, 157 | "mouseleave": function() { 158 | $(this).children(".submenu, .megamenu").stop(true, true).fadeOut(300); 159 | } 160 | }); 161 | } 162 | }, 163 | 164 | mobileMenu: function() { 165 | var children = menu.children("li"), 166 | toggle = menu.children("li.toggle-menu"); 167 | 168 | toggle.show(0).on("click", function(){ 169 | 170 | if ($children.is(":hidden")){ 171 | children.slideDown(300); 172 | } else { 173 | toggle.show(0); 174 | } 175 | }); 176 | 177 | // Click (touch) effect 178 | menu.find("li").not(".toggle-menu").each(function(){ 179 | var el = $(this); 180 | if (el.children(".submenu, .megamenu").length) { 181 | el.children("a").on("click", function(e){ 182 | if ($(this).attr('href') === '#') { 183 | e.preventDefault(); 184 | e.stopPropagation(); 185 | } 186 | 187 | var $sub = $(this).siblings(".submenu, .megamenu"); 188 | 189 | if ($sub.hasClass("open")) { 190 | $sub.slideUp(300).removeClass("open"); 191 | } else { 192 | $sub.slideDown(300).addClass("open"); 193 | } 194 | }); 195 | } 196 | }); 197 | }, 198 | unbindEvents: function() { 199 | menu.find("li, a").off(); 200 | $(document).off("click.menu touchstart.menu"); 201 | menu.find(".submenu, .megamenu").hide(0); 202 | } 203 | }; // END Megamenu object 204 | 205 | if ($windowWidth < 768) { 206 | Megamenu.mobileMenu(); 207 | } else { 208 | Megamenu.desktopMenu(); 209 | } 210 | 211 | /* **************** Hide header on scroll down *************** */ 212 | (function() { 213 | // Hide Header on on scroll down 214 | var didScroll; 215 | var lastScrollTop = 0; 216 | var delta = 5; 217 | var navbarHeight = $('header').outerHeight(); 218 | 219 | $win.scroll(function(event){ 220 | didScroll = true; 221 | }); 222 | 223 | // Debounce the header toggling to ever 250ms 224 | var toggleHeader = function() { 225 | if (didScroll) { 226 | hasScrolled(); 227 | didScroll = false; 228 | } 229 | setTimeout(toggleHeader, 250); 230 | }; 231 | setTimeout(toggleHeader, 250); 232 | 233 | function hasScrolled() { 234 | var st = $win.scrollTop(); 235 | 236 | // Make sure they scroll more than delta 237 | if (Math.abs(lastScrollTop - st) <= delta) { 238 | return; 239 | } 240 | 241 | // If they scrolled down and are past the navbar, add class .nav-up. 242 | // This is necessary so you never see what is "behind" the navbar. 243 | if (st > lastScrollTop && st > navbarHeight){ 244 | // Scroll Down 245 | $('header').removeClass('nav-down').addClass('nav-up'); 246 | // Scroll Up 247 | } else if (st + $(window).height() < $(document).height()) { 248 | $('header').removeClass('nav-up').addClass('nav-down'); 249 | } 250 | lastScrollTop = st; 251 | } 252 | 253 | // If we're directly linking to a section, hide the nav. 254 | if (window.location.hash.length) { 255 | $('header').addClass('nav-up'); 256 | } 257 | }()); 258 | 259 | // Footer Tooltips 260 | $("[data-toggle='tooltip']").tooltip(); 261 | 262 | // Search autocompletion 263 | var autocompleteFound = false; 264 | var autocompleteFiles = {'c': 'class', 'i': 'interface', 't': 'trait', 'co': 'constant', 'f': 'function'}; 265 | 266 | var $search = $('.search input[name=q]'); 267 | $search 268 | .autocomplete(searchEntries, { 269 | matchContains: true, 270 | scrollHeight: 200, 271 | max: 20, 272 | formatItem: function(data) { 273 | return '' + data[1].replace(/^(.+\\)(.+)$/, '$1$2') + ''; 274 | }, 275 | formatMatch: function(data) { 276 | return data[1]; 277 | }, 278 | formatResult: function(data) { 279 | return data[1]; 280 | }, 281 | show: function(list) { 282 | var listWidth = list.width(); 283 | var items = $('li span', list); 284 | if (isMobileDevice) { 285 | // Make the results full width 286 | list.width('100%').css('left', 0); 287 | } else { 288 | var listLeft = parseInt(list.css('left'), 10); 289 | var maxWidth = Math.max.apply(null, items.map(function() { 290 | return $(this).width(); 291 | })); 292 | // Make the results wider, and shift left to accomodate new width. 293 | list 294 | .width(Math.max(maxWidth, $search.innerWidth())) 295 | .css('left', listLeft - Math.max(0, maxWidth - listWidth)); 296 | } 297 | } 298 | }).result(function(event, data) { 299 | autocompleteFound = true; 300 | var location = window.location.href.split('/'); 301 | location.pop(); 302 | var parts = data[1].split(/::|$/); 303 | var file = autocompleteFiles[data[0]] + '-' + parts[0].replace(/\\/g, '.') + '.html'; 304 | if (parts[1]) { 305 | file += '#' + parts[1]; 306 | } 307 | location.push(file); 308 | window.location = location.join('/'); 309 | 310 | // Workaround for Opera bug 311 | $(this).closest('form').attr('action', location.join('/')); 312 | }).closest('form') 313 | .submit(function() { 314 | var query = $search.val(); 315 | if ('' === query) { 316 | return false; 317 | } 318 | 319 | var label = $('#search input[name=more]').val(); 320 | if (!autocompleteFound && label && -1 === query.indexOf('more:')) { 321 | $search.val(query + ' more:' + label); 322 | } 323 | 324 | return !autocompleteFound && '' !== $('#search input[name=cx]').val(); 325 | }); 326 | 327 | // Open details 328 | if (false/*elementDetailsCollapsed*/) { 329 | $('tr', $content).filter(':has(.detailed)') 330 | .click(function() { 331 | var $this = $(this); 332 | $('.short', $this).hide(); 333 | $('.detailed', $this).show(); 334 | }); 335 | } 336 | 337 | // Select selected lines 338 | var matches = window.location.hash.substr(1).match(/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/); 339 | if (null !== matches) { 340 | var lists = matches[0].split(','); 341 | for (var i = 0; i < lists.length; i++) { 342 | var lines = lists[i].split('-'); 343 | lines[1] = lines[1] || lines[0]; 344 | for (var j = lines[0]; j <= lines[1]; j++) { 345 | $('#' + j).addClass('selected'); 346 | } 347 | } 348 | 349 | var scrollToLineNumber = function() { 350 | console.log('plop'); 351 | var offset = 0; 352 | var $header = $('header'); 353 | if ($header.length > 0) { 354 | offset = $header.height() + parseInt($header.css('top')); 355 | } 356 | 357 | var $firstLine = $('#' + parseInt(matches[0])); 358 | if ($firstLine.length > 0) { 359 | $(document).scrollTop($firstLine.offset().top - offset); 360 | } 361 | }; 362 | scrollToLineNumber(); 363 | setTimeout(scrollToLineNumber, 400); 364 | } 365 | 366 | // Save selected lines 367 | var lastLine; 368 | $('a.l').click(function(event) { 369 | event.preventDefault(); 370 | 371 | var $selectedLine = $(this).parent(); 372 | var selectedLine = parseInt($selectedLine.attr('id')); 373 | 374 | if (event.shiftKey) { 375 | if (lastLine) { 376 | for (var i = Math.min(selectedLine, lastLine); i <= Math.max(selectedLine, lastLine); i++) { 377 | $('#' + i).addClass('selected'); 378 | } 379 | } else { 380 | $selectedLine.addClass('selected'); 381 | } 382 | } else if (event.ctrlKey) { 383 | $selectedLine.toggleClass('selected'); 384 | } else { 385 | var $selected = $('.l.selected') 386 | .not($selectedLine) 387 | .removeClass('selected'); 388 | if ($selected.length > 0) { 389 | $selectedLine.addClass('selected'); 390 | } else { 391 | $selectedLine.toggleClass('selected'); 392 | } 393 | } 394 | 395 | lastLine = $selectedLine.hasClass('selected') ? selectedLine : null; 396 | 397 | // Update hash 398 | var lines = $('.l.selected') 399 | .map(function() { 400 | return parseInt($(this).attr('id')); 401 | }) 402 | .get() 403 | .sort(function(a, b) { 404 | return a - b; 405 | }); 406 | 407 | var hash = []; 408 | var list = []; 409 | for (var j = 0; j < lines.length; j++) { 410 | if (0 === j && j + 1 === lines.length) { 411 | hash.push(lines[j]); 412 | } else if (0 === j) { 413 | list[0] = lines[j]; 414 | } else if (lines[j - 1] + 1 !== lines[j] && j + 1 === lines.length) { 415 | hash.push(list.join('-')); 416 | hash.push(lines[j]); 417 | } else if (lines[j - 1] + 1 !== lines[j]) { 418 | hash.push(list.join('-')); 419 | list = [lines[j]]; 420 | } else if (j + 1 === lines.length) { 421 | list[1] = lines[j]; 422 | hash.push(list.join('-')); 423 | } else { 424 | list[1] = lines[j]; 425 | } 426 | } 427 | 428 | window.location.hash = hash.join(','); 429 | }); 430 | }); 431 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | getDocComment()?->getText()); 63 | 64 | $const = new ReflectedDefine($node->name->name, $doc, $context, $source); 65 | $const->value = PrintUtil::expr($node->value); 66 | 67 | if (isset($doc->tags['var'])) { 68 | $const->type = $doc->tags['var']->type; 69 | } 70 | 71 | return $const; 72 | } 73 | 74 | /** 75 | * @param \PhpParser\Node\Stmt\Function_ $node Function node 76 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context 77 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source 78 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 79 | */ 80 | public function createFunction(Function_ $node, Context $context, Source $source): ReflectedFunction 81 | { 82 | $doc = new DocBlock($node->getDocComment()?->getText()); 83 | 84 | $func = new ReflectedFunction($node->name->name, $doc, $context, $source); 85 | $this->reflectFuncLike($func, $node, $doc); 86 | 87 | return $func; 88 | } 89 | 90 | /** 91 | * @param \PhpParser\Node\Stmt\Interface_ $node Interface node 92 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context 93 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source 94 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 95 | */ 96 | public function createInterface(Interface_ $node, Context $context, Source $source): ReflectedInterface 97 | { 98 | $doc = new DocBlock($node->getDocComment()?->getText()); 99 | 100 | $interface = new ReflectedInterface($node->name->name, $doc, $context, $source); 101 | $this->reflectClassLike($interface, $node); 102 | 103 | $interface->extends = array_map(fn($name) => (string)$name, $node->extends); 104 | 105 | return $interface; 106 | } 107 | 108 | /** 109 | * @param \PhpParser\Node\Stmt\Class_ $node Class node 110 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context 111 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source 112 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 113 | */ 114 | public function createClass(Class_ $node, Context $context, Source $source): ReflectedClass 115 | { 116 | $doc = new DocBlock($node->getDocComment()?->getText()); 117 | 118 | $class = new ReflectedClass($node->name->name, $doc, $context, $source); 119 | $this->reflectClassLike($class, $node); 120 | 121 | $class->abstract = $node->isAbstract(); 122 | $class->final = $node->isFinal(); 123 | $class->extends = (string)$node->extends ?: null; 124 | $class->implements = array_map(fn($name) => (string)$name, $node->implements); 125 | 126 | return $class; 127 | } 128 | 129 | /** 130 | * @param \PhpParser\Node\Stmt\Trait_ $node Trait node 131 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context 132 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source 133 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 134 | */ 135 | public function createTrait(Trait_ $node, Context $context, Source $source): ReflectedTrait 136 | { 137 | $doc = new DocBlock($node->getDocComment()?->getText()); 138 | $trait = new ReflectedTrait($node->name->name, $doc, $context, $source); 139 | $this->reflectClassLike($trait, $node); 140 | 141 | return $trait; 142 | } 143 | 144 | /** 145 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 146 | * @param \PhpParser\Node\Stmt\ClassConst $classNode Constant node 147 | * @param \PhpParser\Node\Const_ $constNode Constant node 148 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 149 | */ 150 | protected function createConstant( 151 | ReflectedClassLike $classLike, 152 | ClassConst $classNode, 153 | Const_ $constNode 154 | ): ReflectedConstant { 155 | $doc = new DocBlock($classNode->getDocComment()?->getText()); 156 | $source = new Source( 157 | $classLike->source->path, 158 | $classLike->source->inProject, 159 | $classNode->getStartLine(), 160 | $classNode->getEndLine() 161 | ); 162 | 163 | $const = new ReflectedConstant( 164 | $constNode->name->name, 165 | $doc, 166 | $classLike->context, 167 | $source 168 | ); 169 | $const->owner = $classLike; 170 | 171 | $const->value = PrintUtil::expr($constNode->value); 172 | if (isset($doc->tags['var'])) { 173 | $const->type = $doc->tags['var']->type; 174 | } 175 | 176 | $const->visibility = $classNode->isPublic() ? 'public' : ($classNode->isProtected() ? 'protected' : 'private'); 177 | 178 | return $const; 179 | } 180 | 181 | /** 182 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 183 | * @param \PhpParser\Node\Stmt\Property $classNode Property node 184 | * @param \PhpParser\Node\Stmt\PropertyProperty $propNode Property node 185 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine 186 | */ 187 | protected function createProperty( 188 | ReflectedClassLike $classLike, 189 | Property $classNode, 190 | PropertyProperty $propNode 191 | ): ReflectedProperty { 192 | $doc = new DocBlock($classNode->getDocComment()?->getText()); 193 | $source = new Source( 194 | $classLike->source->path, 195 | $classLike->source->inProject, 196 | $classNode->getStartLine(), 197 | $classNode->getEndLine() 198 | ); 199 | 200 | $prop = new ReflectedProperty($propNode->name->name, $doc, $classLike->context, $source); 201 | $prop->owner = $classLike; 202 | 203 | $prop->nativeType = $classNode->type ? DocUtil::parseType(PrintUtil::node($classNode->type)) : null; 204 | $prop->type = $doc->tags['var']?->type ?? $prop->nativeType; 205 | $prop->default = $propNode->default ? PrintUtil::expr($propNode->default) : null; 206 | 207 | $prop->visibility = $classNode->isPublic() ? 'public' : ($classNode->isProtected() ? 'protected' : 'private'); 208 | $prop->static = $classNode->isStatic(); 209 | 210 | return $prop; 211 | } 212 | 213 | /** 214 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 215 | * @param string $tagName Property tag name 216 | * @param \PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode $tagValue Property tag value 217 | * @return \Cake\ApiDocs\Reflection\ReflectedProperty 218 | */ 219 | protected function createdAnnotatedProperty( 220 | ReflectedClassLike $classLike, 221 | string $tagName, 222 | PropertyTagValueNode $tagValue 223 | ): ReflectedProperty { 224 | $doc = new DocBlock(null); 225 | $doc->summary = $tagValue->description; 226 | $source = clone $classLike->source; 227 | 228 | $prop = new ReflectedProperty(substr($tagValue->propertyName, 1), $doc, $classLike->context, $source); 229 | $prop->owner = $classLike; 230 | $prop->annotation = $tagName; 231 | 232 | $prop->nativeType = $tagValue->type; 233 | $prop->type = $tagValue->type; 234 | 235 | return $prop; 236 | } 237 | 238 | /** 239 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 240 | * @param \PhpParser\Node\Stmt\ClassMethod $node Method node 241 | * @return \Cake\ApiDocs\Reflection\ReflectedMethod 242 | */ 243 | protected function createMethod(ReflectedClassLike $classLike, ClassMethod $node): ReflectedMethod 244 | { 245 | $doc = new DocBlock($node->getDocComment()?->getText()); 246 | $source = new Source( 247 | $classLike->source->path, 248 | $classLike->source->inProject, 249 | $node->getStartLine(), 250 | $node->getEndLine() 251 | ); 252 | 253 | $func = new ReflectedMethod( 254 | $node->name->name, 255 | $doc, 256 | $classLike->context, 257 | $source 258 | ); 259 | $func->owner = $classLike; 260 | 261 | $this->reflectFuncLike($func, $node, $doc); 262 | $func->visibility = $node->isPublic() ? 'public' : ($node->isProtected() ? 'protected' : 'private'); 263 | $func->abstract = $node->isAbstract(); 264 | $func->static = $node->isStatic(); 265 | 266 | return $func; 267 | } 268 | 269 | /** 270 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 271 | * @param string $tagName Method tag name 272 | * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode $tagValue Method tag value 273 | * @return \Cake\ApiDocs\Reflection\ReflectedMethod 274 | */ 275 | protected function createdAnnotatedMethod( 276 | ReflectedClassLike $classLike, 277 | string $tagName, 278 | MethodTagValueNode $tagValue 279 | ): ReflectedMethod { 280 | $doc = new DocBlock(null); 281 | $doc->summary = $tagValue->description; 282 | $source = clone $classLike->source; 283 | 284 | $method = new ReflectedMethod($tagValue->methodName, $doc, $classLike->context, $source); 285 | $method->owner = $classLike; 286 | $method->annotation = $tagName; 287 | 288 | foreach ($tagValue->parameters as $paramNode) { 289 | $param = new ReflectedParam(substr($paramNode->parameterName, 1)); 290 | $param->type = $paramNode->type; 291 | $param->byRef = $paramNode->isReference; 292 | $param->variadic = $paramNode->isVariadic; 293 | $param->default = $paramNode->defaultValue?->__toString(); 294 | $method->params[$param->name] = $param; 295 | } 296 | $method->returnType = $tagValue->returnType; 297 | 298 | return $method; 299 | } 300 | 301 | /** 302 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike 303 | * @param \PhpParser\Node\Stmt\ClassLike $node ClassLike node 304 | * @return void 305 | */ 306 | protected function reflectClassLike(ReflectedClassLike $classLike, ClassLike $node): void 307 | { 308 | foreach ($node->getConstants() as $classConstNode) { 309 | foreach ($classConstNode->consts as $constNode) { 310 | if (!$classConstNode->isPrivate()) { 311 | $constant = $this->createConstant($classLike, $classConstNode, $constNode); 312 | $classLike->constants[$constant->name] = $constant; 313 | } 314 | } 315 | } 316 | ksort($classLike->constants); 317 | 318 | foreach ($node->getProperties() as $classPropNode) { 319 | foreach ($classPropNode->props as $propNode) { 320 | if (!$classPropNode->isPrivate()) { 321 | $property = $this->createProperty($classLike, $classPropNode, $propNode); 322 | $classLike->properties[$property->name] = $property; 323 | } 324 | } 325 | } 326 | foreach (['@property', '@property-read', '@property-write'] as $tagName) { 327 | foreach ($classLike->doc->tags[$tagName] ?? [] as $tagValue) { 328 | $property = $this->createdAnnotatedProperty($classLike, $tagName, $tagValue); 329 | $classLike->properties[$property->name] = $property; 330 | } 331 | } 332 | ksort($classLike->properties); 333 | 334 | foreach ($node->getmethods() as $methodNode) { 335 | if (!$methodNode->isPrivate()) { 336 | $method = $this->createMethod($classLike, $methodNode); 337 | $classLike->methods[$method->name] = $method; 338 | } 339 | } 340 | foreach ($classLike->doc->tags['@method'] ?? [] as $tagValue) { 341 | $method = $this->createdAnnotatedMethod($classLike, '@method', $tagValue); 342 | $classLike->methods[$method->name] = $method; 343 | } 344 | ksort($classLike->methods); 345 | 346 | $traits = $node->getTraitUses(); 347 | foreach ($traits as $trait) { 348 | foreach ($trait->traits as $use) { 349 | $classLike->uses[] = (string)$use; 350 | } 351 | } 352 | } 353 | 354 | /** 355 | * @param \Cake\ApiDocs\Reflection\ReflectedFunction $func Reflected function 356 | * @param \PhpParser\Node\FunctionLike $node Function node 357 | * @param \Cake\ApiDocs\Reflection\DocBlock $doc Reflected docblock 358 | * @return void 359 | */ 360 | protected function reflectFuncLike(ReflectedFunction $func, FunctionLike $node, DocBlock $doc): void 361 | { 362 | $params = []; 363 | foreach ($node->getParams() as $paramNode) { 364 | $param = $this->createParam($func, $paramNode); 365 | $func->params[$param->name] = $param; 366 | } 367 | 368 | if ($node->getReturnType() !== null) { 369 | $func->nativeReturnType = DocUtil::parseType(PrintUtil::node($node->getReturnType())); 370 | } 371 | $func->returnType = $doc->tags['return']?->type ?? $func->nativeReturnType; 372 | $func->returnDescription = $doc->tags['return']?->description ?? ''; 373 | } 374 | 375 | /** 376 | * @param \Cake\ApiDocs\Reflection\ReflectedFunction $func Reflected function 377 | * @param \PhpParser\Node\Param $node Param node 378 | * @return \Cake\ApiDocs\Reflection\ReflectedParam 379 | */ 380 | protected function createParam(ReflectedFunction $func, Param $node): ReflectedParam 381 | { 382 | $param = new ReflectedParam($node->var->name); 383 | 384 | $tag = $this->getParamTag($param->name, $func->doc); 385 | $param->nativeType = $node->type ? DocUtil::parseType(PrintUtil::node($node->type)) : null; 386 | if ($tag) { 387 | $param->type = $tag->type; 388 | $param->description = $tag->description; 389 | } else { 390 | $param->type = $param->nativeType; 391 | } 392 | 393 | $param->variadic = $node->variadic; 394 | $param->byRef = $node->byRef; 395 | $param->default = $node->default ? PrintUtil::expr($node->default) : null; 396 | 397 | return $param; 398 | } 399 | 400 | /** 401 | * @param string $variable Variable name 402 | * @param \Cake\ApiDocs\Reflection\DocBlock $doc Reflected docblock 403 | * @return \PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|null 404 | */ 405 | protected function getParamTag(string $variable, DocBlock $doc): ?ParamTagValueNode 406 | { 407 | $variable = '$' . $variable; 408 | foreach ($doc->tags['param'] ?? [] as $tag) { 409 | if ($tag instanceof InvalidTagValueNode) { 410 | continue; 411 | } 412 | if ($tag->parameterName === $variable) { 413 | return $tag; 414 | } 415 | } 416 | 417 | return null; 418 | } 419 | } 420 | --------------------------------------------------------------------------------