├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── boring-cyborg.yml ├── renovate.json ├── settings.yml └── workflows │ ├── ci.yml │ └── release-managment.yaml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── composer.lock ├── etc └── qa │ ├── composer-require-checker.json │ ├── phpcs.xml │ ├── phpstan.neon │ ├── phpunit.xml │ └── psalm.xml ├── infection.json.dist ├── src ├── Lister.php ├── functions.php └── functions_include.php ├── test-app ├── Commands │ └── AwesomesauceCommand.php ├── Foo │ └── Bar │ │ └── BarAndFoo.php └── Handlers │ └── AwesomesauceHandler.php ├── test-classes ├── InstantiatableClass.php └── NonInstantiatableClass.php └── var └── .gitkeep /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.json] 11 | indent_size = 2 12 | 13 | [*.yml] 14 | indent_size = 2 15 | 16 | [*.yaml] 17 | indent_size = 2 18 | 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @WyriHaximus 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: WyriHaximus 2 | -------------------------------------------------------------------------------- /.github/boring-cyborg.yml: -------------------------------------------------------------------------------- 1 | labelPRBasedOnFilePath: 2 | "Documentation 📚": 3 | - README.md 4 | - CONTRIBUTING.md 5 | "Dependencies 📦": 6 | - Dockerfile* 7 | - composer.* 8 | - package.json 9 | - package-lock.json 10 | - yarn.lock 11 | "Docker 🐳": 12 | - Dockerfile* 13 | - .docker/**/* 14 | "Image 🖼": 15 | - "**/*.gif" 16 | - "**/*.jpg" 17 | - "**/*.jpeg" 18 | - "**/*.png" 19 | - "**/*.webp" 20 | "CSS 👩‍🎨": 21 | - "**/*.css" 22 | "HTML 👷‍♀️": 23 | - "**/*.htm" 24 | - "**/*.html" 25 | "NEON 🦹‍♂️": 26 | - "**/*.neon" 27 | "MarkDown 📝": 28 | - "**/*.md" 29 | "YAML 🍄": 30 | - "**/*.yml" 31 | - "**/*.yaml" 32 | "JSON 👨‍💼": 33 | - "**/*.json" 34 | "Go 🐹": 35 | - "**/*.go" 36 | "JavaScript 🦏": 37 | - "**/*.js" 38 | - package.json 39 | - package-lock.json 40 | - yarn.lock 41 | "PHP 🐘": 42 | - "**/*.php" 43 | - composer.* 44 | "Configuration ⚙": 45 | - .github/* 46 | "CI 🚧": 47 | - .github/workflows/* 48 | - .scrutinizer.yml 49 | "Templates 🌲": 50 | - "**/*.twig" 51 | - "**/*.tpl" 52 | "Helm ☸": 53 | - .helm/**/* 54 | "Tests 🧪": 55 | - tests/**/* 56 | "Source 🔮": 57 | - src/**/* 58 | 59 | labelerFlags: 60 | labelOnPRUpdates: true 61 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>WyriHaximus/renovate-config:php-package" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | private: false 3 | has_issues: true 4 | has_wiki: false 5 | has_downloads: true 6 | default_branch: master 7 | allow_squash_merge: false 8 | allow_merge_commit: true 9 | allow_rebase_merge: false 10 | 11 | # Labels: define labels for Issues and Pull Requests 12 | labels: 13 | - name: "Dependencies 📦" 14 | color: 0025ff 15 | description: "Pull requests that update a dependency file" 16 | - name: "Image 🖼" 17 | color: 00ffff 18 | - name: "HTML 👷‍♀️" 19 | color: ffffff 20 | - name: "CSS 👩‍🎨" 21 | color: b3b3b3 22 | - name: "JavaScript 🦏" 23 | color: ffff00 24 | - name: "Go 🐹" 25 | color: 00ADD8 26 | - name: "JSON 👨‍💼" 27 | color: 00ADD8 28 | - name: "NEON 🦹‍♂️" 29 | color: CE3262 30 | - name: "MarkDown 📝" 31 | color: 000000 32 | - name: "YAML 🍄" 33 | color: ff1aff 34 | - name: "Templates 🌲" 35 | color: 009933 36 | - name: "Helm ☸" 37 | color: 091C84 38 | - name: "Tests 🧪" 39 | color: ffe6e6 40 | - name: "Source 🔮" 41 | color: e6ffe6 42 | - name: "Configuration ⚙" 43 | color: b3b3cc 44 | - name: "PHP 🐘" 45 | color: 8892BF 46 | description: "Hypertext Pre Processor" 47 | - name: "Docker 🐳" 48 | color: 0db7ed 49 | description: "Pull requests that relate to Docker" 50 | - name: "CI 🚧" 51 | color: ffff00 52 | - name: "Feature 🏗" 53 | color: 66ff99 54 | - name: "Documentation 📚" 55 | color: 6666ff 56 | - name: "Security 🕵️‍♀️" 57 | color: ff0000 58 | - name: "Hacktoberfest 🎃" 59 | color: 152347 60 | - name: "Bug 🐞" 61 | color: d73a4a 62 | description: "Something isn't working" 63 | oldname: bug 64 | - name: "Duplicate ♊" 65 | color: cfd3d7 66 | description: "This issue or pull request already exists" 67 | oldname: duplicate 68 | - name: "Enhancement ✨" 69 | color: a2eeef 70 | description: "New feature or request" 71 | oldname: enhancement 72 | - name: "Good First Issue" 73 | color: 7057ff 74 | description: "Good for newcomers" 75 | oldname: "good first issue" 76 | - name: "Help Wanted" 77 | color: 008672 78 | description: "Extra attention is needed" 79 | oldname: "help wanted" 80 | - name: Invalid 81 | color: e4e669 82 | description: "This doesn't seem right" 83 | oldname: invalid 84 | - name: "Question ❓" 85 | color: d876e3 86 | description: "Further information is requested" 87 | oldname: question 88 | - name: "Will not be fixed 🛑" 89 | color: ffffff 90 | description: "This will not be worked on" 91 | oldname: wontfix 92 | - name: "Sponsor Request ❤️" 93 | color: fedbf0 94 | description: "Issue/PR opened by sponsor" 95 | 96 | branches: 97 | - name: master 98 | protection: 99 | required_pull_request_reviews: 100 | required_approving_review_count: 1 101 | dismiss_stale_reviews: true 102 | require_code_owner_reviews: true 103 | # Required. Require status checks to pass before merging. Set to null to disable 104 | required_status_checks: 105 | # Required. Require branches to be up to date before merging. 106 | strict: true 107 | # Required. The list of status checks to require in order to merge into this branch 108 | contexts: [] 109 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 110 | enforce_admins: true 111 | # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. 112 | restrictions: 113 | apps: [] 114 | users: [] 115 | teams: [] 116 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | - 'master' 7 | - 'refs/heads/v[0-9]+.[0-9]+.[0-9]+' 8 | pull_request: 9 | ## This workflow needs the `pull-request` permissions to work for the package diffing 10 | ## Refs: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions 11 | permissions: 12 | pull-requests: write 13 | contents: read 14 | jobs: 15 | ci: 16 | name: Continuous Integration 17 | uses: WyriHaximus/github-workflows/.github/workflows/package.yaml@main 18 | -------------------------------------------------------------------------------- /.github/workflows/release-managment.yaml: -------------------------------------------------------------------------------- 1 | name: Release Management 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - labeled 7 | - unlabeled 8 | - synchronize 9 | - reopened 10 | milestone: 11 | types: 12 | - closed 13 | permissions: 14 | contents: write 15 | issues: write 16 | pull-requests: write 17 | jobs: 18 | release-managment: 19 | name: Create Release 20 | uses: WyriHaximus/github-workflows/.github/workflows/package-release-managment.yaml@main 21 | with: 22 | milestone: ${{ github.event.milestone.title }} 23 | description: ${{ github.event.milestone.title }} 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are highly appreciated. Here's a quick guide. 4 | 5 | Fork, then clone the repo: 6 | 7 | git clone git@github.com:your-username/php-tactician-command-handler-mapper.git 8 | 9 | Set up your machine: 10 | 11 | composer install 12 | 13 | Make sure the tests pass: 14 | 15 | make unit 16 | 17 | Make your change. Add tests for your change. Make the tests pass: 18 | 19 | make unit 20 | 21 | Before committing and submitting your pull request make sure it passes PSR2 coding style, unit tests pass and pass on all supported PHP versions: 22 | 23 | make contrib 24 | 25 | Push to your fork and [submit a pull request][pr]. 26 | 27 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 28 | 29 | At this point you're waiting on me. I like to at least comment on pull requests 30 | within a day or two. I may suggest some changes or improvements or alternatives. 31 | 32 | Some things that will increase the chance that your pull request is accepted: 33 | 34 | * Write tests. 35 | * Follow PSR2 (travis will also check for this). 36 | * Write a [good commit message][commit]. 37 | 38 | [commit]: http://chris.beams.io/posts/git-commit/ 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cees-Jan Kiewiet 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # set all to phony 2 | SHELL=bash 3 | 4 | .PHONY: * 5 | 6 | COMPOSER_SHOW_EXTENSION_LIST=$(shell composer show -t | grep -o "\-\-\(ext-\).\+" | sort | uniq | cut -d- -f4- | tr -d '\n' | grep . | sed '/^$$/d' | xargs | sed -e 's/ /, /g' | tr -cd '[:alnum:],' | sed 's/.$$//') 7 | SLIM_DOCKER_IMAGE=$(shell php -r 'echo count(array_intersect(["gd", "vips"], explode(",", "${COMPOSER_SHOW_EXTENSION_LIST}"))) > 0 ? "" : "-slim";') 8 | COMPOSER_CACHE_DIR=$(shell composer config --global cache-dir -q || echo ${HOME}/.composer-php/cache) 9 | PHP_VERSION:=$(shell docker run --rm -v "`pwd`:`pwd`" jess/jq jq -r -c '.config.platform.php' "`pwd`/composer.json" | php -r "echo str_replace('|', '.', explode('.', implode('|', explode('.', stream_get_contents(STDIN), 2)), 2)[0]);") 10 | COMPOSER_CONTAINER_CACHE_DIR=$(shell docker run --rm -it "ghcr.io/wyrihaximusnet/php:${PHP_VERSION}-nts-alpine${SLIM_DOCKER_IMAGE}-dev" composer config --global cache-dir -q || echo ${HOME}/.composer-php/cache) 11 | 12 | ifneq ("$(wildcard /.you-are-in-a-wyrihaximus.net-php-docker-image)","") 13 | IN_DOCKER=TRUE 14 | else 15 | IN_DOCKER=FALSE 16 | endif 17 | 18 | ifeq ("$(IN_DOCKER)","TRUE") 19 | DOCKER_RUN:= 20 | else 21 | DOCKER_RUN:=docker run --rm -it \ 22 | -v "`pwd`:`pwd`" \ 23 | -v "${COMPOSER_CACHE_DIR}:${COMPOSER_CONTAINER_CACHE_DIR}" \ 24 | -w "`pwd`" \ 25 | "ghcr.io/wyrihaximusnet/php:${PHP_VERSION}-nts-alpine${SLIM_DOCKER_IMAGE}-dev" 26 | endif 27 | 28 | ifneq (,$(findstring icrosoft,$(shell cat /proc/version))) 29 | THREADS=1 30 | else 31 | THREADS=$(shell nproc) 32 | endif 33 | 34 | all: ## Runs everything ### 35 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "###" | awk 'BEGIN {FS = ":.*?## "}; {printf "%s\n", $$1}' | xargs --open-tty $(MAKE) 36 | 37 | syntax-php: ## Lint PHP syntax 38 | $(DOCKER_RUN) vendor/bin/parallel-lint --exclude vendor . 39 | 40 | cs-fix: ## Fix any automatically fixable code style issues 41 | $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml || $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml || $(DOCKER_RUN) vendor/bin/phpcbf --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml -vvvv 42 | 43 | cs: ## Check the code for code style issues 44 | $(DOCKER_RUN) vendor/bin/phpcs --parallel=$(THREADS) --cache=./var/.phpcs.cache.json --standard=./etc/qa/phpcs.xml 45 | 46 | stan: ## Run static analysis (PHPStan) 47 | $(DOCKER_RUN) vendor/bin/phpstan analyse src tests --level max --ansi -c ./etc/qa/phpstan.neon 48 | 49 | psalm: ## Run static analysis (Psalm) 50 | $(DOCKER_RUN) vendor/bin/psalm --threads=$(THREADS) --shepherd --stats --config=./etc/qa/psalm.xml 51 | 52 | unit-testing: ## Run tests 53 | $(DOCKER_RUN) vendor/bin/phpunit --colors=always -c ./etc/qa/phpunit.xml --coverage-text --coverage-html ./var/tests-unit-coverage-html --coverage-clover ./var/tests-unit-clover-coverage.xml 54 | $(DOCKER_RUN) test -n "$(COVERALLS_REPO_TOKEN)" && test -n "$(COVERALLS_RUN_LOCALLY)" && test -f ./var/tests-unit-clover-coverage.xml && vendor/bin/php-coveralls -v --coverage_clover ./build/logs/clover.xml --json_path ./var/tests-unit-clover-coverage-upload.json || true 55 | 56 | unit-testing-raw: ## Run tests ### 57 | php vendor/phpunit/phpunit/phpunit --colors=always -c ./etc/qa/phpunit.xml --coverage-text --coverage-html ./var/tests-unit-coverage-html --coverage-clover ./var/tests-unit-clover-coverage.xml 58 | test -n "$(COVERALLS_REPO_TOKEN)" && test -n "$(COVERALLS_RUN_LOCALLY)" && test -f ./var/tests-unit-clover-coverage.xml && ./vendor/bin/php-coveralls -v --coverage_clover ./build/logs/clover.xml --json_path ./var/tests-unit-clover-coverage-upload.json || true 59 | 60 | mutation-testing: ## Run mutation testing 61 | $(DOCKER_RUN) vendor/bin/roave-infection-static-analysis-plugin --ansi --log-verbosity=all --threads=$(THREADS) --psalm-config etc/qa/psalm.xml || (cat ./var/infection.log && false) 62 | 63 | mutation-testing-raw: ## Run mutation testing ### 64 | php vendor/roave/infection-static-analysis-plugin/bin/roave-infection-static-analysis-plugin --ansi --log-verbosity=all --threads=$(THREADS) --psalm-config etc/qa/psalm.xml || (cat ./var/infection.log && false) 65 | 66 | composer-require-checker: ## Ensure we require every package used in this package directly 67 | $(DOCKER_RUN) vendor/bin/composer-require-checker --ignore-parse-errors --ansi -vvv --config-file=./etc/qa/composer-require-checker.json 68 | 69 | composer-unused: ## Ensure we don't require any package we don't use in this package directly 70 | $(DOCKER_RUN) vendor/bin/composer-unused --ansi 71 | 72 | composer-install: ## Install dependencies 73 | $(DOCKER_RUN) composer install --no-progress --ansi --no-interaction --prefer-dist -o 74 | 75 | backward-compatibility-check: ## Check code for backwards incompatible changes 76 | $(MAKE) backward-compatibility-check-raw || true 77 | 78 | backward-compatibility-check-raw: ## Check code for backwards incompatible changes, doesn't ignore the failure ### 79 | $(DOCKER_RUN) vendor/bin/roave-backward-compatibility-check 80 | 81 | shell: ## Provides Shell access in the expected environment ### 82 | $(DOCKER_RUN) ash 83 | 84 | task-list-ci: ## CI: Generate a JSON array of jobs to run, matches the commands run when running `make (|all)` ### 85 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | grep -v "###" | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%s\n", $$1}' | jq --raw-input --slurp -c 'split("\n")| .[0:-1]' 86 | 87 | help: ## Show this help ### 88 | @printf "\033[33mUsage:\033[0m\n make [target]\n\n\033[33mTargets:\033[0m\n" 89 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-32s\033[0m %s\n", $$1, $$2}' | tr -d '#' 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # List all PHP classes in directories and files 2 | 3 | [![Continuous Integration](https://github.com/WyriHaximus/php-list-classes-in-directory/actions/workflows/ci.yml/badge.svg)](https://github.com/WyriHaximus/php-list-classes-in-directory/actions/workflows/ci.yml) 4 | [![Latest Stable Version](https://poser.pugx.org/wyrihaximus/list-classes-in-directory/v/stable.png)](https://packagist.org/packages/wyrihaximus/list-classes-in-directory) 5 | [![Total Downloads](https://poser.pugx.org/wyrihaximus/list-classes-in-directory/downloads.png)](https://packagist.org/packages/wyrihaximus/list-classes-in-directory/stats) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/wyrihaximus/php-list-classes-in-directory/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/wyrihaximus/php-list-classes-in-directory/?branch=master) 7 | [![License](https://poser.pugx.org/wyrihaximus/list-classes-in-directory/license.png)](https://packagist.org/packages/wyrihaximus/list-classes-in-directory) 8 | 9 | 10 | # Install 11 | 12 | To install via [Composer](http://getcomposer.org/), use the command below, it will automatically detect the latest version and bind it with `^`. 13 | 14 | ``` 15 | composer require wyrihaximus/list-classes-in-directory 16 | ``` 17 | 18 | # Usage 19 | 20 | #### get a list of classes from multiple directories. 21 | 22 | ```php 23 | use function WyriHaximus\Lister; 24 | 25 | // $classes now contains a list of full qualified class names from 'src/' and 'tests/' 26 | $classes = Lister::classesInDirectories( 27 | __DIR__ . '/src', 28 | __DIR__ . '/tests' 29 | ); 30 | 31 | 32 | // use listInstantiatableClassesInDirectories() or listNonInstantiatableClassesInDirectories() to only consider classes that can actually be instantiated, or not. 33 | $instantiatableClasses = Lister::instantiatableClassesInDirectory( 34 | __DIR__ . '/src', 35 | __DIR__ . '/tests' 36 | ); 37 | $nonInstantiatableClasses = Lister::nonInstantiatableClassesInDirectory( 38 | __DIR__ . '/src', 39 | __DIR__ . '/tests' 40 | ); 41 | ``` 42 | 43 | #### get a list of classes from one directory. 44 | ```php 45 | use function WyriHaximus\Lister; 46 | 47 | // $classes now contains a list of full qualified class names from __DIR__ 48 | $classes = Lister::classesInDirectory(__DIR__); 49 | 50 | // use listInstantiatableClassesInDirectory() or listNonInstantiatableClassesInDirectory() to only consider classes that can actually be instantiated, or not. 51 | $instantiatableClasses = Lister::instantiatableClassesInDirectory(__DIR__); 52 | $nonInstantiatableClasses = Lister::nonInstantiatableClassesInDirectory(__DIR__); 53 | 54 | ``` 55 | 56 | #### get a list of classes from multiple files. 57 | ```php 58 | use function WyriHaximus\Lister; 59 | 60 | // $classes now contains a list of full qualified class names from 'Bar.php' and 'Foo.php' 61 | $classes = Lister::classesInFiles( 62 | __DIR__ . '/Bar.php', 63 | __DIR__ . '/Foo.php' 64 | ); 65 | ``` 66 | 67 | #### get a list of classes from one file. 68 | ```php 69 | use function WyriHaximus\Lister; 70 | 71 | // $classes now contains a list of full qualified class names from 'Foo.php' 72 | $classes = Lister::classesInFile(__DIR__.'/Foo.php'); 73 | ``` 74 | 75 | # Acknowledgement 76 | 77 | This package is a shorthand function for using [`better reflection`](https://github.com/Roave/BetterReflection/) and is based on one of the [`examples`](https://github.com/Roave/BetterReflection/blob/396a07c9d276cb9ffba581b24b2dadbb542d542e/demo/parsing-whole-directory/example2.php). 78 | 79 | # License 80 | 81 | The MIT License (MIT) 82 | 83 | Copyright (c) 2024 Cees-Jan Kiewiet 84 | 85 | Permission is hereby granted, free of charge, to any person obtaining a copy 86 | of this software and associated documentation files (the "Software"), to deal 87 | in the Software without restriction, including without limitation the rights 88 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 89 | copies of the Software, and to permit persons to whom the Software is 90 | furnished to do so, subject to the following conditions: 91 | 92 | The above copyright notice and this permission notice shall be included in all 93 | copies or substantial portions of the Software. 94 | 95 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 96 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 97 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 98 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 99 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 100 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 101 | SOFTWARE. 102 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wyrihaximus/list-classes-in-directory", 3 | "description": "Allows you to list full qualified class names in directories and files.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Cees-Jan Kiewiet", 8 | "email": "ceesjank@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^8.2", 13 | "roave/better-reflection": "^6.5" 14 | }, 15 | "require-dev": { 16 | "wyrihaximus/test-utilities": "^6.0.9" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "WyriHaximus\\": "src" 21 | }, 22 | "files": [ 23 | "src/functions_include.php" 24 | ] 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Test\\App\\": "test-app/", 29 | "Test\\Classes\\": "test-classes/", 30 | "WyriHaximus\\Tests\\": "tests/" 31 | }, 32 | "files": [ 33 | "test-app/Foo/Bar/BarAndFoo.php" 34 | ] 35 | }, 36 | "config": { 37 | "allow-plugins": { 38 | "composer/package-versions-deprecated": true, 39 | "dealerdirect/phpcodesniffer-composer-installer": true, 40 | "ergebnis/composer-normalize": true, 41 | "icanhazstring/composer-unused": true, 42 | "infection/extension-installer": true 43 | }, 44 | "platform": { 45 | "php": "8.2.13" 46 | }, 47 | "sort-packages": true 48 | }, 49 | "scripts": { 50 | "post-install-cmd": [ 51 | "composer normalize" 52 | ], 53 | "post-update-cmd": [ 54 | "composer normalize" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /etc/qa/composer-require-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist" : [ 3 | "null", "true", "false", 4 | "static", "self", "parent", 5 | "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", 6 | "Safe\\date", "WyriHaximus\\Constants\\Boolean\\FALSE_", "WyriHaximus\\Constants\\Boolean\\TRUE_", 7 | "WyriHaximus\\Constants\\Numeric\\ONE", "WyriHaximus\\Constants\\Numeric\\ZERO", 8 | "ApiClients\\Tools\\Rx\\observableFromArray" 9 | ], 10 | "php-core-extensions" : [ 11 | "Core", 12 | "date", 13 | "pcre", 14 | "Phar", 15 | "Reflection", 16 | "SPL", 17 | "standard" 18 | ], 19 | "scan-files" : [] 20 | } 21 | -------------------------------------------------------------------------------- /etc/qa/phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ../../src/ 10 | ../../tests/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/qa/phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ergebnis: 3 | noExtends: 4 | classesAllowedToBeExtended: 5 | - FilterIterator 6 | 7 | includes: 8 | - ../../vendor/wyrihaximus/test-utilities/rules.neon 9 | -------------------------------------------------------------------------------- /etc/qa/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ../../tests/ 6 | 7 | 8 | 9 | 10 | ../../src/ 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /etc/qa/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": 120, 3 | "source": { 4 | "directories": [ 5 | "src" 6 | ] 7 | }, 8 | "logs": { 9 | "text": "./var/infection.log", 10 | "summary": "./var/infection-summary.log", 11 | "json": "./var/infection.json", 12 | "perMutator": "./var/infection-per-mutator.md" 13 | }, 14 | "mutators": { 15 | "@default": true, 16 | "Identical": false 17 | }, 18 | "phpUnit": { 19 | "configDir": "./etc/qa/" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Lister.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | public static function classesInDirectories(string ...$directories): iterable 31 | { 32 | $sourceLocator = new AggregateSourceLocator([ 33 | new DirectoriesSourceLocator( 34 | array_values($directories), 35 | (new BetterReflection())->astLocator(), 36 | ), 37 | // ↓ required to autoload parent classes/interface from another directory than /src (e.g. /vendor) 38 | new AutoloadSourceLocator((new BetterReflection())->astLocator()), 39 | ]); 40 | 41 | foreach (self::classesInSourceLocator($sourceLocator) as $class) { 42 | yield $class->getName(); 43 | } 44 | } 45 | 46 | /** 47 | * get a list of all classes in the given directory. 48 | * 49 | * @return iterable 50 | */ 51 | public static function classesInDirectory(string $directory): iterable 52 | { 53 | yield from self::classesInDirectories($directory); 54 | } 55 | 56 | /** 57 | * get a list of all classes in the given file. 58 | * 59 | * @param non-empty-string $file 60 | * 61 | * @return iterable 62 | */ 63 | public static function classesInFile(string $file): iterable 64 | { 65 | $sourceLocator = new AggregateSourceLocator([ 66 | new SingleFileSourceLocator( 67 | $file, 68 | (new BetterReflection())->astLocator(), 69 | ), 70 | // ↓ required to autoload parent classes/interface from another directory (e.g. /vendor) 71 | new AutoloadSourceLocator((new BetterReflection())->astLocator()), 72 | ]); 73 | 74 | foreach (self::classesInSourceLocator($sourceLocator) as $class) { 75 | yield $class->getName(); 76 | } 77 | } 78 | 79 | /** 80 | * get a list of all classes in the given files. 81 | * 82 | * @param non-empty-string ...$files 83 | * 84 | * @return iterable 85 | */ 86 | public static function classesInFiles(string ...$files): iterable 87 | { 88 | foreach ($files as $file) { 89 | foreach (self::classesInFile($file) as $class) { 90 | yield $class; 91 | } 92 | } 93 | } 94 | 95 | /** @return iterable */ 96 | public static function instantiatableClassesInDirectories(string ...$directories): iterable 97 | { 98 | $iterator = self::classesInDirectories(...$directories); 99 | 100 | /** 101 | * @psalm-suppress MissingTemplateParam 102 | * @psalm-suppress InvalidOperand 103 | */ 104 | return new class (new ArrayIterator([...$iterator])) extends FilterIterator { 105 | private const DOES_NOT_ACCEPT_CLASS = false; 106 | 107 | public function accept(): bool 108 | { 109 | $className = $this->getInnerIterator()->current(); 110 | if (! is_string($className)) { 111 | return self::DOES_NOT_ACCEPT_CLASS; 112 | } 113 | 114 | try { 115 | $reflectionClass = ReflectionClass::createFromName($className); 116 | 117 | return $reflectionClass->isInstantiable(); 118 | } catch (IdentifierNotFound) { 119 | return self::DOES_NOT_ACCEPT_CLASS; 120 | } 121 | } 122 | }; 123 | } 124 | 125 | /** @return iterable */ 126 | public static function instantiatableClassesInDirectory(string $directory): iterable 127 | { 128 | yield from self::instantiatableClassesInDirectories($directory); 129 | } 130 | 131 | /** @return iterable */ 132 | public static function nonInstantiatableClassesInDirectories(string ...$directories): iterable 133 | { 134 | $iterator = self::classesInDirectories(...$directories); 135 | 136 | /** 137 | * @psalm-suppress MissingTemplateParam 138 | * @psalm-suppress InvalidOperand 139 | */ 140 | return new class (new ArrayIterator([...$iterator])) extends FilterIterator { 141 | private const DOES_NOT_ACCEPT_CLASS = false; 142 | 143 | public function accept(): bool 144 | { 145 | $className = $this->getInnerIterator()->current(); 146 | if (! is_string($className)) { 147 | return self::DOES_NOT_ACCEPT_CLASS; 148 | } 149 | 150 | try { 151 | $reflectionClass = ReflectionClass::createFromName($className); 152 | 153 | return $reflectionClass->isInstantiable() === self::DOES_NOT_ACCEPT_CLASS; 154 | } catch (IdentifierNotFound) { 155 | return self::DOES_NOT_ACCEPT_CLASS; 156 | } 157 | } 158 | }; 159 | } 160 | 161 | /** @return iterable */ 162 | public static function nonInstantiatableClassesInDirectory(string $directory): iterable 163 | { 164 | yield from self::nonInstantiatableClassesInDirectories($directory); 165 | } 166 | 167 | /** 168 | * @internal 169 | * 170 | * @return iterable 171 | */ 172 | public static function classesInSourceLocator(AggregateSourceLocator $sourceLocator): iterable 173 | { 174 | /** @psalm-suppress UndefinedClass */ 175 | yield from (new DefaultReflector($sourceLocator))->reflectAllClasses(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | function listClassesInDirectories(string ...$directories): iterable 18 | { 19 | return Lister::classesInDirectories(...$directories); 20 | } 21 | 22 | /** 23 | * get a list of all classes in the given directory. 24 | * 25 | * @return iterable 26 | */ 27 | function listClassesInDirectory(string $directory): iterable 28 | { 29 | return Lister::classesInDirectory($directory); 30 | } 31 | 32 | /** 33 | * get a list of all classes in the given file. 34 | * 35 | * @param non-empty-string $file 36 | * 37 | * @return iterable 38 | */ 39 | function listClassesInFile(string $file): iterable 40 | { 41 | return Lister::classesInFile($file); 42 | } 43 | 44 | /** 45 | * get a list of all classes in the given files. 46 | * 47 | * @param non-empty-string ...$files 48 | * 49 | * @return iterable 50 | */ 51 | function listClassesInFiles(string ...$files): iterable 52 | { 53 | return Lister::classesInFiles(...$files); 54 | } 55 | 56 | /** @return iterable */ 57 | function listInstantiatableClassesInDirectories(string ...$directories): iterable 58 | { 59 | return Lister::instantiatableClassesInDirectories(...$directories); 60 | } 61 | 62 | /** @return iterable */ 63 | function listInstantiatableClassesInDirectory(string $directory): iterable 64 | { 65 | return Lister::instantiatableClassesInDirectories($directory); 66 | } 67 | 68 | /** @return iterable */ 69 | function listNonInstantiatableClassesInDirectories(string ...$directories): iterable 70 | { 71 | return Lister::nonInstantiatableClassesInDirectories(...$directories); 72 | } 73 | 74 | /** @return iterable */ 75 | function listNonInstantiatableClassesInDirectory(string $directory): iterable 76 | { 77 | return Lister::nonInstantiatableClassesInDirectory($directory); 78 | } 79 | 80 | /** 81 | * @internal 82 | * 83 | * @return iterable 84 | */ 85 | function listClassesInSourceLocator(AggregateSourceLocator $sourceLocator): iterable 86 | { 87 | return Lister::classesInSourceLocator($sourceLocator); 88 | } 89 | -------------------------------------------------------------------------------- /src/functions_include.php: -------------------------------------------------------------------------------- 1 | value = $value; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getValue() 30 | { 31 | return $this->value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test-app/Foo/Bar/BarAndFoo.php: -------------------------------------------------------------------------------- 1 | getValue(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-classes/InstantiatableClass.php: -------------------------------------------------------------------------------- 1 |