├── .github
└── workflows
│ └── php.yml
├── .gitignore
├── .php-cs-fixer.php
├── LICENSE
├── README.md
├── bin
├── release.sh
├── split-linux.sh
├── split.sh
├── splitsh-lite
└── splitsh-lite-linux
├── composer.json
├── phpstan.neon
├── phpunit.xml
├── src
├── aop
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Aop.php
│ │ ├── AstManager.php
│ │ ├── Attribute
│ │ └── AspectConfig.php
│ │ ├── Collector
│ │ ├── AbstractCollector.php
│ │ ├── AspectCollector.php
│ │ └── PropertyAttributeCollector.php
│ │ ├── Composer.php
│ │ ├── Contract
│ │ ├── AspectInterface.php
│ │ ├── CollectorInterface.php
│ │ └── PropertyAttribute.php
│ │ ├── Exception
│ │ └── PropertyHandleException.php
│ │ ├── Metadata.php
│ │ ├── ProceedingJoinPoint.php
│ │ ├── PropertyHandler.php
│ │ ├── PropertyHandlerVisitor.php
│ │ ├── ProxyHandler.php
│ │ └── ProxyHandlerVisitor.php
├── cache
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Cache.php
│ │ ├── CacheException.php
│ │ ├── Contract
│ │ │ └── CacheDriverInterface.php
│ │ ├── Driver
│ │ │ ├── AbstractDriver.php
│ │ │ ├── ApcuDriver.php
│ │ │ ├── FileDriver.php
│ │ │ ├── MemcachedDriver.php
│ │ │ └── RedisDriver.php
│ │ └── InvalidArgumentException.php
│ └── tests
│ │ └── CacheTest.php
├── config
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Contract
│ │ │ └── ConfigInterface.php
│ │ └── Repository.php
│ └── tests
│ │ └── RepositoryTest.php
├── database
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Contract
│ │ └── ConfigInterface.php
│ │ ├── Database.php
│ │ ├── Event
│ │ └── QueryExecuted.php
│ │ ├── PDOConfig.php
│ │ └── Query.php
├── di
│ ├── .phpstorm.meta.php
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Container.php
│ │ ├── Context.php
│ │ ├── Exception
│ │ │ ├── ContainerException.php
│ │ │ └── NotFoundException.php
│ │ ├── Reflection.php
│ │ └── helpers.php
│ └── tests
│ │ ├── Bar.php
│ │ ├── ContainerTest.php
│ │ ├── ContextTest.php
│ │ ├── Foo.php
│ │ └── FooInterface.php
├── event
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Contract
│ │ │ └── EventListenerInterface.php
│ │ ├── EventDispatcher.php
│ │ ├── EventListener.php
│ │ └── ListenerProvider.php
│ └── tests
│ │ ├── EventDispatcherTest.php
│ │ ├── Events
│ │ ├── BarEvent.php
│ │ └── FooEvent.php
│ │ ├── ListenerProviderTest.php
│ │ └── Listeners
│ │ ├── BarListener.php
│ │ └── FooListener.php
├── foundation
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Cache
│ │ └── Aspect
│ │ │ └── Cacheable.php
│ │ ├── Config
│ │ └── Attribute
│ │ │ └── Config.php
│ │ ├── Console
│ │ ├── Attribute
│ │ │ └── Command.php
│ │ └── Collector
│ │ │ └── CommandCollector.php
│ │ ├── Di
│ │ └── Attribute
│ │ │ └── Inject.php
│ │ ├── Event
│ │ ├── Attribute
│ │ │ └── Listen.php
│ │ └── Collector
│ │ │ └── ListenerCollector.php
│ │ └── Routing
│ │ ├── Attribute
│ │ ├── Controller.php
│ │ ├── DeleteMapping.php
│ │ ├── GetMapping.php
│ │ ├── PatchMapping.php
│ │ ├── PostMapping.php
│ │ ├── PutMapping.php
│ │ └── RequestMapping.php
│ │ └── Collector
│ │ └── RouteCollector.php
├── http-message
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Bag
│ │ ├── CookieBag.php
│ │ ├── FileBag.php
│ │ ├── HeaderBag.php
│ │ ├── ParameterBag.php
│ │ └── ServerBag.php
│ │ ├── Contract
│ │ └── StatusCodeInterface.php
│ │ ├── Cookie.php
│ │ ├── Exception
│ │ └── HttpException.php
│ │ ├── Message.php
│ │ ├── Request.php
│ │ ├── Response.php
│ │ ├── ServerRequest.php
│ │ ├── Stream
│ │ ├── FileStream.php
│ │ └── StandardStream.php
│ │ ├── UploadedFile.php
│ │ ├── Uri.php
│ │ └── helpers.php
├── http-server
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Exception
│ │ ├── MethodNotAllowedException.php
│ │ └── NotFoundException.php
│ │ └── RequestHandler.php
├── http-throttle
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Handlers
│ │ ├── CounterFixed.php
│ │ ├── CounterSlider.php
│ │ ├── LeakyBucket.php
│ │ ├── ThrottleAbstract.php
│ │ └── TokenBucket.php
│ │ ├── RateLimitException.php
│ │ └── ThrottleMiddleware.php
├── json-rpc
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Attribute
│ │ └── RpcService.php
│ │ ├── Client.php
│ │ ├── Event
│ │ └── RpcCalled.php
│ │ ├── Message
│ │ ├── Error.php
│ │ ├── Request.php
│ │ └── Response.php
│ │ ├── Server.php
│ │ └── ServiceCollector.php
├── pipeline
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Abort.php
│ │ └── Context.php
├── pool
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── BasePool.php
│ │ ├── BasePoolItem.php
│ │ └── Contract
│ │ ├── PoolInterface.php
│ │ └── PoolItemInterface.php
├── redis
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Client.php
│ │ ├── Connector
│ │ ├── BaseConnector.php
│ │ ├── BasePoolConnector.php
│ │ └── SwoolePoolConnector.php
│ │ └── Contract
│ │ └── ConnectorInterface.php
├── routing
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Exception
│ │ ├── MethodNotAllowedException.php
│ │ └── RouteNotFoundException.php
│ │ ├── Route.php
│ │ ├── RouteCollection.php
│ │ ├── Router.php
│ │ └── UrlMatcher.php
├── session
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Handler
│ │ ├── FileHandler.php
│ │ └── RedisHandler.php
│ │ ├── Session.php
│ │ └── SessionException.php
├── swoole
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Context.php
│ │ ├── Exception
│ │ │ └── ParallelExecutionException.php
│ │ ├── Parallel.php
│ │ └── Table
│ │ │ ├── Exception
│ │ │ ├── DuplicateKeyException.php
│ │ │ └── ModelNotFoundException.php
│ │ │ ├── Manager.php
│ │ │ └── Model.php
│ └── tests
│ │ └── Concerns
│ │ └── UserTable.php
├── utils
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── src
│ │ ├── Arr.php
│ │ ├── BC.php
│ │ ├── Collection.php
│ │ ├── Composer.php
│ │ ├── Contract
│ │ │ ├── Arrayable.php
│ │ │ ├── CanBeEscapedWhenCastToString.php
│ │ │ ├── DeferringDisplayableValue.php
│ │ │ ├── Enumerable.php
│ │ │ ├── Htmlable.php
│ │ │ ├── Jsonable.php
│ │ │ ├── MessageBag.php
│ │ │ ├── MessageProvider.php
│ │ │ ├── PackerInterface.php
│ │ │ ├── ValidatedData.php
│ │ │ └── Xmlable.php
│ │ ├── Exception
│ │ │ ├── FileNotFoundException.php
│ │ │ ├── ItemNotFoundException.php
│ │ │ └── MultipleItemsFoundException.php
│ │ ├── Filesystem.php
│ │ ├── Fluent.php
│ │ ├── LazyCollection.php
│ │ ├── Macroable.php
│ │ ├── MessageBag.php
│ │ ├── NamespacedItemResolver.php
│ │ ├── Optional.php
│ │ ├── Packer
│ │ │ ├── JsonPacker.php
│ │ │ └── PhpSerializePacker.php
│ │ ├── Pipeline.php
│ │ ├── Pluralizer.php
│ │ ├── Proxy
│ │ │ ├── HigherOrderCollectionProxy.php
│ │ │ ├── HigherOrderTapProxy.php
│ │ │ └── HigherOrderWhenProxy.php
│ │ ├── Str.php
│ │ ├── Stringable.php
│ │ ├── Traits
│ │ │ ├── AutoFillProperties.php
│ │ │ ├── Conditionable.php
│ │ │ ├── EnumeratesValues.php
│ │ │ └── Tappable.php
│ │ ├── ValidatedInput.php
│ │ ├── Xml.php
│ │ └── helpers.php
│ └── tests
│ │ └── BCTest.php
├── validation
│ └── src
│ │ ├── RuleInterface.php
│ │ └── Rules
│ │ └── In.php
├── var-dumper
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ │ ├── Adapter
│ │ └── HyperfDumperHandler.php
│ │ ├── Dumper.php
│ │ ├── DumperHandler.php
│ │ └── helpers.php
├── view
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ ├── publish
│ │ └── view.php
│ └── src
│ │ ├── Contract
│ │ └── ViewEngineInterface.php
│ │ ├── Engine
│ │ ├── Blade
│ │ │ └── Compiler.php
│ │ ├── BladeEngine.php
│ │ └── PhpEngine.php
│ │ ├── Exception
│ │ └── ViewNotExistException.php
│ │ ├── Renderer.php
│ │ └── ViewFactory.php
└── watcher
│ ├── LICENSE
│ ├── README.md
│ ├── composer.json
│ └── src
│ ├── Contract
│ └── DriverInterface.php
│ ├── Driver
│ ├── FindDriver.php
│ └── InotifyDriver.php
│ └── Watcher.php
└── tests
└── bootstrap.php
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 |
20 | - name: Setup PHP
21 | uses: shivammathur/setup-php@v2
22 | with:
23 | php-version: '8.2'
24 | tools: composer
25 |
26 | - name: Validate composer.json and composer.lock
27 | run: composer validate --strict
28 |
29 | - name: Cache Composer packages
30 | id: composer-cache
31 | uses: actions/cache@v3
32 | with:
33 | path: vendor
34 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
35 | restore-keys: |
36 | ${{ runner.os }}-php-
37 |
38 | - name: Install dependencies
39 | run: composer install --prefer-dist --no-progress
40 |
41 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
42 | # Docs: https://getcomposer.org/doc/articles/scripts.md
43 |
44 | - name: Run test suite
45 | run: composer test
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 | .phpunit.cache
4 | .phpunit.result.cache
5 | composer.lock
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 轻量 • 简单 • 快速
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 这是一款组件包,提供了一些web开发常用的组件,而且大部分组件都是可以独立使用的。参与开发可以直接向该包提交代码,将会同步至相应的包。
15 |
16 | ## 主要组件
17 |
18 | - 符合 Psr7 的 [next/http-message](https://github.com/next-laboratory/http-message) HTTP消息
19 | - 符合 Psr7 的 [next/routing](https://github.com/next-laboratory/routing) 路由组件
20 | - 符合 Psr11 的 [next/di](https://github.com/next-laboratory/di) 依赖注入容器
21 | - 符合 Psr14 的 [next/event](https://github.com/next-laboratory/event) 事件
22 | - 符合 Psr15 的 [next/http-server](https://github.com/next-laboratory/http-server) HTTP服务
23 | - 符合 Psr16 的 [next/cache](https://github.com/next-laboratory/cache),支持 File,Memcached,Redis,APC [可扩展]
24 | - AOP [next/aop](https://github.com/next-laboratory/aop)
25 | - database [next/database](https://github.com/next-laboratory/database)
26 | - session [next/session](https://github.com/next-laboratory/session)
27 |
28 | ## 贡献一览
29 |
30 | [](https://contributor-overtime-api.apiseven.com/contributors-svg?chart=contributorOverTime&repo=next-laboratory/next)
31 |
32 | 欢迎有兴趣的朋友参与开发
33 |
34 | ## 致谢
35 |
36 | 感谢PHP最好用IDE: PHPStorm
37 |
--------------------------------------------------------------------------------
/bin/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | if (( "$#" == 0 ))
6 | then
7 | echo "Tag has to be provided"
8 |
9 | exit 1
10 | fi
11 |
12 | NOW=$(date +%s)
13 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
14 | VERSION=$1
15 | BASEPATH=$(cd `dirname $0`; cd ../src/; pwd)
16 |
17 | # Always prepend with "v"
18 | #if [[ $VERSION != v* ]]
19 | #then
20 | # VERSION="v$VERSION"
21 | #fi
22 |
23 | if [ -z $2 ] ; then
24 | repos=$(ls $BASEPATH)
25 | else
26 | repos=${@:2}
27 | fi
28 |
29 | for REMOTE in $repos
30 | do
31 | echo ""
32 | echo ""
33 | echo "Cloning $REMOTE";
34 | TMP_DIR="/tmp/nextphp-split"
35 | REMOTE_URL="git@github.com:next-laboratory/$REMOTE.git"
36 |
37 | rm -rf $TMP_DIR;
38 | mkdir $TMP_DIR;
39 |
40 | (
41 | cd $TMP_DIR;
42 |
43 | git clone $REMOTE_URL .
44 | git checkout "$CURRENT_BRANCH";
45 |
46 | if [[ $(git log --pretty="%d" -n 1 | grep tag --count) -eq 0 ]]; then
47 | echo "Releasing $REMOTE"
48 | git tag $VERSION
49 | git push origin --tags
50 | fi
51 | )
52 | done
53 |
54 | TIME=$(echo "$(date +%s) - $NOW" | bc)
55 |
56 | printf "Execution time: %f seconds" $TIME
57 |
--------------------------------------------------------------------------------
/bin/split-linux.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -x
5 |
6 | CURRENT_BRANCH="master"
7 | BASEPATH=$(cd `dirname $0`; cd ../src/; pwd)
8 | REPOS=$@
9 |
10 | function split()
11 | {
12 | SHA1=`./bin/splitsh-lite-linux --prefix=$1`
13 | git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
14 | }
15 |
16 | function remote()
17 | {
18 | git remote add $1 $2 || true
19 | }
20 |
21 | git pull origin $CURRENT_BRANCH
22 |
23 | if [[ $# -eq 0 ]]; then
24 | REPOS=$(ls $BASEPATH)
25 | fi
26 |
27 | for REPO in $REPOS ; do
28 | remote $REPO git@github.com:next-laboratory/$REPO.git
29 |
30 | split "src/$REPO" $REPO
31 | done
32 |
--------------------------------------------------------------------------------
/bin/split.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -x
5 |
6 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
7 | BASEPATH=$(cd `dirname $0`; cd ../src/; pwd)
8 | REPOS=$@
9 |
10 | function split()
11 | {
12 | SHA1=`./bin/splitsh-lite --prefix=$1`
13 | git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
14 | }
15 |
16 | function remote()
17 | {
18 | git remote add $1 $2 || true
19 | }
20 |
21 | git pull origin $CURRENT_BRANCH
22 |
23 | if [[ $# -eq 0 ]]; then
24 | REPOS=$(ls $BASEPATH)
25 | fi
26 |
27 | for REPO in $REPOS ; do
28 | remote $REPO git@github.com:next-laboratory/$REPO.git
29 |
30 | split "src/$REPO" $REPO
31 | done
32 |
--------------------------------------------------------------------------------
/bin/splitsh-lite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-laboratory/next/e284de7d30f202ceac161371d20c48c5afedd03b/bin/splitsh-lite
--------------------------------------------------------------------------------
/bin/splitsh-lite-linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-laboratory/next/e284de7d30f202ceac161371d20c48c5afedd03b/bin/splitsh-lite-linux
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/next",
3 | "license": "Apache-2.0",
4 | "description": "一款支持swoole, workerman, FPM环境的框架的组件化的轻量`PHP`框架。",
5 | "homepage": "https://github.com/next-laboratory/next",
6 | "keywords": [
7 | "nextphp",
8 | "flexible",
9 | "php framework"
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "Next\\Aop\\": "src/aop/src",
14 | "Next\\Cache\\": "src/cache/src",
15 | "Next\\Config\\": "src/config/src",
16 | "Next\\Database\\": "src/database/src",
17 | "Next\\Di\\": "src/di/src",
18 | "Next\\Event\\": "src/event/src",
19 | "Next\\Foundation\\": "src/foundation/src",
20 | "Next\\Http\\Message\\": "src/http-message/src",
21 | "Next\\Http\\Server\\": "src/http-server/src",
22 | "Next\\Routing\\": "src/routing/src",
23 | "Next\\Session\\": "src/session/src",
24 | "Next\\Utils\\": "src/utils/src",
25 | "Next\\VarDumper\\": "src/var-dumper/src"
26 | },
27 | "files": [
28 | "src/di/src/helpers.php",
29 | "src/utils/src/helpers.php",
30 | "src/var-dumper/src/helpers.php"
31 | ]
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Tests\\": "tests/",
36 | "Next\\Event\\Tests\\": "src/event/tests/"
37 | }
38 | },
39 | "authors": [
40 | {
41 | "name": "chengyao",
42 | "email": "chengyao0320@foxmail.com"
43 | }
44 | ],
45 | "require": {
46 | "php": "^8.2",
47 | "psr/container": "^2.0",
48 | "psr/simple-cache": "^1.0",
49 | "psr/event-dispatcher": "^1.0",
50 | "psr/http-message": "^2.0",
51 | "psr/http-server-middleware": "^1.0",
52 | "psr/http-server-handler": "^1.0",
53 | "psr/log": "^3.0",
54 | "symfony/var-dumper": "^7.0",
55 | "voku/portable-ascii": "^2.0",
56 | "workerman/workerman": "^4.1",
57 | "hyperf/exception-handler": "^3.1",
58 | "league/commonmark": "^2.4",
59 | "ramsey/uuid": "^4.7"
60 | },
61 | "require-dev": {
62 | "friendsofphp/php-cs-fixer": "*",
63 | "phpstan/phpstan": "*",
64 | "doctrine/inflector": "*",
65 | "pestphp/pest": "^2.34"
66 | },
67 | "replace": {
68 | "next/aop": "*",
69 | "next/cache": "*",
70 | "next/config": "*",
71 | "next/database": "*",
72 | "next/di": "*",
73 | "next/event": "*",
74 | "next/http-message": "*",
75 | "next/http-server": "*",
76 | "next/foundation": "*",
77 | "next/routing": "*",
78 | "next/session": "*",
79 | "next/utils": "*",
80 | "next/var-dumper": "*"
81 | },
82 | "scripts": {
83 | "test": "@php ./vendor/bin/phpunit -c phpunit.xml --colors=always",
84 | "cs-fix": "@php ./vendor/bin/php-cs-fixer fix $1",
85 | "analyse": "@php ./vendor/bin/phpstan analyse --memory-limit 300M -l 0 -c phpstan.neon"
86 | },
87 | "config": {
88 | "allow-plugins": {
89 | "pestphp/pest-plugin": true
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | # Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :)
2 | # Fortunately, You can ingore it by the following config.
3 | #
4 | # vendor/bin/phpstan analyse app --memory-limit 200M -l 0
5 | #
6 | parameters:
7 | reportUnmatchedIgnoredErrors: false
8 | ignoreErrors:
9 | - '#Unsafe usage of new static\(\)#'
10 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./src/aop
6 | ./src/cache
7 | ./src/config
8 | ./src/di
9 | ./src/event
10 | ./src/http-message
11 | ./src/http-server
12 | ./src/routing
13 | ./src/session
14 | ./src/utils
15 |
16 |
17 |
18 |
19 | ./src/aop/tests
20 | ./src/cache/tests
21 | ./src/config/tests
22 | ./src/di/tests
23 | ./src/event/tests
24 | ./src/http-message/tests
25 | ./src/http-server/tests
26 | ./src/routing/tests
27 | ./src/session/tests
28 | ./src/utils/tests
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/aop/README.md:
--------------------------------------------------------------------------------
1 | # !!! 不能生产使用,仅作为研究用途
2 |
3 | 一款简单Aop实现。支持常驻内存型PHP应用。可以方便接入nextphp, Swoole,WebMan等框架。
4 |
5 | # 环境要求
6 |
7 | ```
8 | php 8.2
9 | 开启passthru函数
10 | ```
11 |
12 | # 安装
13 |
14 | ```shell
15 | composer require next/aop
16 | ```
17 |
18 | # 使用,以下以webman为例
19 |
20 | ## 修改start.php文件
21 |
22 | ```php
23 | Aop::init(
24 | [__DIR__ . '/../app'],
25 | [
26 | \Next\Aop\Collector\PropertyAttributeCollector::class,
27 | \Next\Aop\Collector\AspectCollector::class,
28 | ],
29 | __DIR__ . '/../runtime/aop',
30 | );
31 | ```
32 |
33 | * paths 注解扫描路径
34 | * collectors 注解收集器
35 | - \Next\Aop\Collector\AspectCollector::class 切面收集器,取消后不能使用切面
36 | - \Next\Aop\Collector\PropertyAttributeCollector::class 属性注解收集器,取消后不支持属性自动注入
37 | * runtimeDir 运行时,生成的代理类和代理类地图会被缓存到这里
38 |
39 | ## 编写切面类,实现AspectInterface接口
40 |
41 | ```php
42 | proceed(); // 直接调用被代理的方法
56 | // $result = $joinPoint->process(); // 继续执行其他切面逻辑
57 | echo 'after';
58 | return $result;
59 | }
60 | }
61 | ```
62 |
63 | 修改方法添加切面注解
64 |
65 | ```php
66 |
87 | 注意上面添加了两个注解,属性和方法注解的作用分别为注入属性和切入方法,可以直接在控制器中打印属性$request发现已经被注入了,切面注解可以有多个,会按照顺序执行。具体实现可以参考这两个类,注意这里的Inject注解并不是从webman容器中获取实例,所以使用的话需要重新定义Inject以保证单例
88 |
89 | 你也可以使用`AspectConfig`注解类配置要切入的类,例如上面的切面类
90 |
91 | ```php
92 | process();
108 | echo 'after';
109 | return $result;
110 | }
111 | }
112 |
113 | ```
114 |
115 | 那么`BaconQrCode\Writer`类的`writeFile`方法将会被切入,该注解可以传递第三个参数数组,作为该切面构造函数的参数
116 |
117 | ## 启动
118 |
119 | ```shell
120 | php start.php start
121 | ```
122 |
123 | 打开浏览器打开~~对应页面~~
124 |
125 | ## 控制台输出内容为
126 |
127 | ```
128 | before--controller--after
129 | ```
--------------------------------------------------------------------------------
/src/aop/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/aop",
3 | "description": "An AOP package that is resident in in-memory PHP frameworks, such as nextphp, WebMan.",
4 | "homepage": "https://github.com/next-laboratory/aop",
5 | "license": "Apache-2.0",
6 | "autoload": {
7 | "psr-4": {
8 | "Next\\Aop\\": "src/"
9 | }
10 | },
11 | "authors": [
12 | {
13 | "name": "chengyao",
14 | "email": "chengyao0320@foxmail.com"
15 | }
16 | ],
17 | "require": {
18 | "php": "^8.2",
19 | "next/di": "^0.1",
20 | "nikic/php-parser": "^5.1",
21 | "symfony/finder": "^7.0",
22 | "symfony/string": "^7.0"
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "0.1.x-dev"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/aop/src/AstManager.php:
--------------------------------------------------------------------------------
1 | parser = (new ParserFactory())->createForHostVersion();
28 | }
29 |
30 | public function getNodes(string $realpath)
31 | {
32 | return $this->parser->parse(file_get_contents($realpath));
33 | }
34 |
35 | public function getClassesByRealPath(string $realpath): array
36 | {
37 | $classes = [];
38 | foreach ($this->getNodes($realpath) as $stmt) {
39 | if ($stmt instanceof Namespace_) {
40 | $namespace = $stmt->name->toCodeString();
41 | foreach ($stmt->stmts as $subStmt) {
42 | // TODO 不支持Trait
43 | if ($subStmt instanceof Class_) {
44 | $classes[] = $namespace . '\\' . $subStmt->name->toString();
45 | }
46 | }
47 | }
48 | }
49 | return $classes;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/aop/src/Attribute/AspectConfig.php:
--------------------------------------------------------------------------------
1 | getMethods() as $reflectionMethod) {
40 | if (! $reflectionMethod->isConstructor()) {
41 | self::$container[$class][$reflectionMethod->getName()][] = $attribute;
42 | }
43 | }
44 | } elseif ($attribute instanceof AspectConfig) {
45 | $reflectionClass = Reflection::class($attribute->class);
46 | $annotation = new $class(...$attribute->params);
47 | $methods = $attribute->methods;
48 | if ($methods === '*') {
49 | foreach ($reflectionClass->getMethods() as $reflectionMethod) {
50 | if (! $reflectionMethod->isConstructor()) {
51 | self::$container[$attribute->class][$reflectionMethod->getName()][] = $annotation;
52 | }
53 | }
54 | } else {
55 | foreach ((array) $methods as $method) {
56 | self::$container[$attribute->class][$method][] = $annotation;
57 | }
58 | }
59 | Aop::addClass($attribute->class, $reflectionClass->getFileName());
60 | }
61 | }
62 |
63 | /**
64 | * 返回某个类方法的切面.
65 | */
66 | public static function getMethodAspects(string $class, string $method): array
67 | {
68 | return self::$container[$class][$method] ?? [];
69 | }
70 |
71 | /**
72 | * 返回被收集过的类.
73 | *
74 | * @return string[]
75 | */
76 | public static function getCollectedClasses(): array
77 | {
78 | return array_keys(self::$container);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/aop/src/Collector/PropertyAttributeCollector.php:
--------------------------------------------------------------------------------
1 | $aspects
23 | */
24 | public function __construct(
25 | protected array $aspects,
26 | protected \Closure $callback,
27 | public string $class,
28 | public string $method,
29 | public \ArrayObject $parameters,
30 | ) {}
31 |
32 | public function process()
33 | {
34 | if ($aspect = array_shift($this->aspects)) {
35 | return $aspect->process($this);
36 | }
37 |
38 | return $this->proceed();
39 | }
40 |
41 | /**
42 | * 执行代理方法.
43 | */
44 | public function proceed(): mixed
45 | {
46 | return call_user_func_array($this->callback, $this->parameters->getArrayCopy());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/aop/src/PropertyHandler.php:
--------------------------------------------------------------------------------
1 | __propertyHandled) {
23 | foreach (PropertyAttributeCollector::getByClass(self::class) as $property => $attributes) {
24 | foreach ($attributes as $attribute) {
25 | $attribute->handle($this, $property);
26 | }
27 | }
28 | $this->__propertyHandled = true;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/aop/src/ProxyHandler.php:
--------------------------------------------------------------------------------
1 | fn(JoinPoint $joinPoint) => $aspect->process($joinPoint, $stack),
29 | // fn(JoinPoint $joinPoint) => $joinPoint->process()
30 | // );
31 | $args = new \ArrayObject();
32 | $methodParameters = Reflection::methodParameterNames($class, $method);
33 | foreach ($parameters as $key => $parameter) {
34 | $args->offsetSet($methodParameters[$key], $parameter);
35 | }
36 |
37 | $aspects = AspectCollector::getMethodAspects($class, $method);
38 | return (new ProceedingJoinPoint($aspects, $callback, $class, $method, $args))->process();
39 | // return $pipeline(new JoinPoint($class, $method, $funcArgs, $callback));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/aop/src/ProxyHandlerVisitor.php:
--------------------------------------------------------------------------------
1 | stmts = array_merge(
44 | [new TraitUse([new Name('\Next\Aop\ProxyHandler')])],
45 | $node->stmts
46 | );
47 | }
48 | if ($node instanceof ClassMethod) {
49 | $methodName = $node->name->toString();
50 | if ($methodName === '__construct') {
51 | $this->metadata->hasConstructor = true;
52 | return;
53 | }
54 | if (AspectCollector::getMethodAspects($this->metadata->className, $methodName)) {
55 | $methodCall = new Node\Expr\StaticCall(
56 | new Name('self'),
57 | '__callViaProxy',
58 | [
59 | new Arg(new Function_()),
60 | new Arg(new Closure([
61 | 'params' => $node->getParams(),
62 | 'stmts' => $node->stmts,
63 | ])),
64 | new Arg(new FuncCall(new Name('func_get_args'))),
65 | ]
66 | );
67 | $returnType = $node->getReturnType();
68 | if ($returnType instanceof Identifier && $returnType->name === 'void') {
69 | $node->stmts = [new Expression($methodCall)];
70 | } else {
71 | if ($node->returnsByRef()) {
72 | $valueRef = new Variable('__returnValueRef_' . ByteString::fromRandom(16));
73 | $node->stmts = [new Expression(new Assign($valueRef, $methodCall)), new Return_($valueRef)];
74 | } else {
75 | $node->stmts = [new Return_($methodCall)];
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/cache/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 轻量 • 简单 • 快速
6 |
7 |
8 |
9 |
10 |
11 |
12 | # 起步
13 |
14 | 符合Psr16的缓存组件,支持File,Memcached,Redis,Apcu驱动。协程环境下需要自定义驱动
15 |
16 | ## 安装
17 |
18 | ```
19 | composer require next/cache
20 | ```
21 |
22 | ## 使用
23 |
24 | ```php
25 | set('stat', 12, 10);
32 | //读取缓存
33 | var_dump($cache->get('stat'));
34 |
35 | ```
36 |
--------------------------------------------------------------------------------
/src/cache/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/cache",
3 | "type": "library",
4 | "license": "Apache-2.0",
5 | "homepage": "https://github.com/next-laboratory/cache",
6 | "description": "A cache component based on PSR16 specification which supports File,Redis,Memcached and APC.",
7 | "keywords": [
8 | "cache",
9 | "nextphp"
10 | ],
11 | "authors": [
12 | {
13 | "name": "chengyao",
14 | "email": "chengyao0320@foxmail.com",
15 | "homepage": "https://www.chengyao.xyz"
16 | }
17 | ],
18 | "autoload": {
19 | "psr-4": {
20 | "Next\\Cache\\": "src/"
21 | }
22 | },
23 | "autoload-dev": {
24 | "psr-4": {
25 | "Next\\Cache\\Tests\\": "tests/"
26 | }
27 | },
28 | "require": {
29 | "php": "^8.2",
30 | "next/utils": "~0.1",
31 | "psr/simple-cache": "^1.0"
32 | },
33 | "extra": {
34 | "branch-alias": {
35 | "dev-master": "0.1.x-dev"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/cache/src/CacheException.php:
--------------------------------------------------------------------------------
1 | get($key) + $step;
21 | $this->set($key, $value);
22 | return $value;
23 | }
24 |
25 | public function decrement(string $key, int $step = 1): bool|int
26 | {
27 | return $this->increment($key, -$step);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/cache/src/Driver/ApcuDriver.php:
--------------------------------------------------------------------------------
1 | memcached = new \Memcached();
21 | }
22 |
23 | public function addServer($host = '127.0.0.1', $port = 11211, $weight = 0): void
24 | {
25 | $this->memcached->addServer($host, $port, $weight);
26 | }
27 |
28 | public function delete($key): bool
29 | {
30 | return $this->memcached->delete($key);
31 | }
32 |
33 | public function set($key, $value, $ttl = null): bool
34 | {
35 | return $this->memcached->set($key, serialize($value), (int) $ttl);
36 | }
37 |
38 | public function has($key): bool
39 | {
40 | $status = $this->memcached->get($key);
41 | return $status !== false && ! is_null($status);
42 | }
43 |
44 | public function clear(): bool
45 | {
46 | return $this->memcached->flush();
47 | }
48 |
49 | public function get(string $key): mixed
50 | {
51 | return $this->memcached->get($key);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/cache/src/Driver/RedisDriver.php:
--------------------------------------------------------------------------------
1 | redis = new \Redis();
34 | if ($this->redis->connect($host, $port, $timeout, $persistentId, $retryInterval, $readTimeout, $context)) {
35 | $this->redis->select($database);
36 | if ($password) {
37 | $this->redis->auth($password);
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * @param mixed $key
44 | * @throws \RedisException
45 | */
46 | public function delete($key): bool
47 | {
48 | return (bool) $this->redis->del($this->normalizeKey($key));
49 | }
50 |
51 | /**
52 | * @param mixed $key
53 | * @throws \RedisException
54 | */
55 | public function has($key): bool
56 | {
57 | return (bool) $this->redis->exists($this->normalizeKey($key));
58 | }
59 |
60 | /**
61 | * @throws \RedisException
62 | */
63 | public function clear(): bool
64 | {
65 | return $this->redis->eval(<<<'LUA'
66 | local keys = redis.call('keys', ARGV[1])
67 | for _, key in ipairs(keys) do
68 | redis.call('del', key)
69 | end
70 | LUA
71 | , [$this->normalizeKey('*')]);
72 | }
73 |
74 | /**
75 | * @throws \RedisException
76 | */
77 | public function get(string $key): mixed
78 | {
79 | return $this->redis->get($this->normalizeKey($key));
80 | }
81 |
82 | /**
83 | * @throws \RedisException
84 | */
85 | public function set(string $key, mixed $value, ?int $ttl = null): bool
86 | {
87 | return $this->redis->set($this->normalizeKey($key), $value, $ttl);
88 | }
89 |
90 | protected function normalizeKey($id)
91 | {
92 | $key = 'cache:' . $id;
93 | if ($this->cachePrefix) {
94 | $key = $this->cachePrefix . ':' . $key;
95 | }
96 |
97 | return $key;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/cache/src/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 | fileCache = new Cache(new FileDriver($tmpDir . '/cache'));
36 | }
37 |
38 | /**
39 | * @throws InvalidArgumentException
40 | */
41 | public function testSet()
42 | {
43 | $this->assertTrue($this->fileCache->set('foo', 'bar'));
44 | }
45 |
46 | /**
47 | * @throws InvalidArgumentException
48 | */
49 | public function testHas()
50 | {
51 | $this->assertTrue($this->fileCache->has('foo'));
52 | }
53 |
54 | /**
55 | * @throws InvalidArgumentException
56 | */
57 | public function testGet()
58 | {
59 | $this->assertEquals('bar', $this->fileCache->get('foo'));
60 | }
61 |
62 | /**
63 | * @throws InvalidArgumentException
64 | */
65 | public function testGetMultiple()
66 | {
67 | $multiValues = $this->fileCache->getMultiple(['foo']);
68 | $this->assertArrayHasKey('foo', $multiValues);
69 | $this->assertEquals('bar', $multiValues['foo']);
70 | }
71 |
72 | /**
73 | * @throws InvalidArgumentException
74 | */
75 | public function testDelete()
76 | {
77 | $this->fileCache->delete('foo');
78 | $this->assertFalse($this->fileCache->has('foo'));
79 | }
80 |
81 | /**
82 | * @throws InvalidArgumentException
83 | */
84 | public function testGetDefault()
85 | {
86 | $this->assertEquals('bio', $this->fileCache->get('foo', 'bio'));
87 | }
88 |
89 | /**
90 | * @throws InvalidArgumentException
91 | */
92 | public function testRemember()
93 | {
94 | $this->fileCache->remember('bar', function () {
95 | return 'foo';
96 | });
97 | $this->assertEquals('foo', $this->fileCache->get('bar'));
98 | }
99 |
100 | /**
101 | * @throws InvalidArgumentException
102 | */
103 | public function testIncrement()
104 | {
105 | $this->fileCache->delete('count');
106 | $this->fileCache->increment('count');
107 | $this->assertEquals(1, $this->fileCache->get('count'));
108 | }
109 |
110 | /**
111 | * @throws InvalidArgumentException
112 | */
113 | public function testDecrement()
114 | {
115 | $this->fileCache->decrement('count');
116 | $this->assertEquals(0, $this->fileCache->get('count'));
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/config/README.md:
--------------------------------------------------------------------------------
1 | 这是一款独立的组件包,可以使用点语法来获取加载的配置
2 |
3 | ```php
4 | $repository = new \Next\Config\Repository();
5 | $repository->set('app', [
6 | 'debug' => false,
7 | ]);
8 | // 获取配置
9 | $repository->get('app.debug');
10 | ```
11 |
12 | > 注意:$repository示例应该保持单例,避免重复加载,加载配置的规则如下
13 |
14 | 例如对于app.php配置文件内容如下
15 |
16 | ```php
17 | return [
18 | 'debug' => true,
19 | ];
20 | ```
21 |
22 | 加载后会按照文件名作为外层数组的键,因此获取配置需要使用`$repository->get('app.debug'')`,支持使用点语法。
23 |
--------------------------------------------------------------------------------
/src/config/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/config",
3 | "license": "Apache-2.0",
4 | "authors": [
5 | {
6 | "name": "chengyao",
7 | "email": "chengyao0320@foxmail.com"
8 | }
9 | ],
10 | "autoload": {
11 | "psr-4": {
12 | "Next\\Config\\": "src/"
13 | }
14 | },
15 | "autoload-dev": {
16 | "psr-4": {
17 | "Next\\Config\\Tests\\": "tests/"
18 | }
19 | },
20 | "require": {
21 | "php": "^8.2",
22 | "next/utils": "~0.1"
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "0.1.x-dev"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/config/src/Contract/ConfigInterface.php:
--------------------------------------------------------------------------------
1 | repository = new Repository([
28 | 'app' => [
29 | 'debug' => true,
30 | 'name' => 'nextphp',
31 | ],
32 | 'cache' => [
33 | 'driver' => 'file',
34 | ],
35 | ]);
36 | }
37 |
38 | public function testGet()
39 | {
40 | $this->assertTrue($this->repository->get('app.debug'));
41 | $this->assertEquals(['driver' => 'file'], $this->repository->get('cache'));
42 | }
43 |
44 | public function testSet()
45 | {
46 | $this->repository->set('cookie.name', 'nextphp');
47 | $this->assertEquals('nextphp', $this->repository->get('cookie.name'));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/database/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 轻量 • 简单 • 快速
8 |
9 |
10 |
11 |
12 |
13 |
14 | > 简单高效操作数据库,不支持swoole协程
15 |
16 | ### 使用示例
17 |
18 | ```php
19 | $db = new \Next\Database\Database(new \Next\Database\PDOConfig());
20 |
21 | $query = $db->query(); // 实例化,建立连接
22 | $query->select('select * from users');
23 | $query->selectOne('select * from users limit 1');
24 | $query->delete('delete from users where id = 1');
25 |
26 | //...
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/src/database/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/database",
3 | "type": "library",
4 | "license": "Apache-2.0",
5 | "homepage": "https://github.com/next-laboratory/database",
6 | "authors": [
7 | {
8 | "name": "chengyao",
9 | "email": "chengyao0320@foxmail.com"
10 | }
11 | ],
12 | "autoload": {
13 | "psr-4": {
14 | "Next\\Database\\": "src/"
15 | }
16 | },
17 | "require": {
18 | "php": "^8.2",
19 | "ext-pdo": "*"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/database/src/Contract/ConfigInterface.php:
--------------------------------------------------------------------------------
1 | config->getDSN(),
29 | $this->config->getUser(),
30 | $this->config->getPassword(),
31 | $this->config->getOptions()
32 | ),
33 | $this->eventDispatcher
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/database/src/Event/QueryExecuted.php:
--------------------------------------------------------------------------------
1 | DSN)) {
30 | $this->DSN = sprintf('%s:host=%s;port=%s;', $driver, $host, $port);
31 | if (! empty($database)) {
32 | $this->DSN .= 'dbname=' . $database . ';';
33 | }
34 | if (! empty($unixSocket)) {
35 | $this->DSN .= 'unix_socket=' . $unixSocket . ';';
36 | }
37 | }
38 | }
39 |
40 | public function getDSN(): string
41 | {
42 | return $this->DSN;
43 | }
44 |
45 | public function getUser(): string
46 | {
47 | return $this->user;
48 | }
49 |
50 | public function getPassword(): string
51 | {
52 | return $this->password;
53 | }
54 |
55 | public function getOptions(): array
56 | {
57 | return $this->options;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/di/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 | 环境要求 PHP >= 8.0
6 |
7 | ```shell
8 | composer require next/di
9 | ```
10 |
11 | # 使用
12 |
13 | 获取容器实例,注意不要直接实例化
14 |
15 | ```php
16 | $container = \Next\Di\Context::getContainer();
17 | $container = container();
18 | ```
19 |
20 | 绑定类和别名,之后所有容器接口都可以使用TestInterface::class标识来获取Test::class实例
21 |
22 | ```php
23 | $container->bind(TestInterface::class, Test::class);
24 | ```
25 |
26 | 实例化
27 |
28 | ```php
29 | $container->make(Test::class);
30 | ```
31 |
32 | 获取对象
33 |
34 | ```php
35 | $container->get(Test::class);
36 | ```
37 |
38 | 调用方法
39 | ```php
40 | $conatiner->call(callable $callable, array $arguments = []);
41 | ```
42 |
43 | > 注意:所有需要传参的api均需要关联数组,数组的键为参数的名字
44 |
--------------------------------------------------------------------------------
/src/di/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/di",
3 | "description": "A lightweight dependency injection tool, container based on the PSR-11 specification.",
4 | "homepage": "https://github.com/next-laboratory/di",
5 | "license": "Apache-2.0",
6 | "keywords": [
7 | "container",
8 | "nextphp",
9 | "dependence injection"
10 | ],
11 | "authors": [
12 | {
13 | "name": "chengyao",
14 | "email": "chengyao0320@foxmail.com"
15 | }
16 | ],
17 | "autoload": {
18 | "psr-4": {
19 | "Next\\Di\\": "src/"
20 | },
21 | "files": [
22 | "src/helpers.php"
23 | ]
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "Next\\Di\\Tests\\": "tests/"
28 | }
29 | },
30 | "require": {
31 | "php": "^8.2",
32 | "psr/container": "^1.0|^2.0"
33 | },
34 | "extra": {
35 | "branch-alias": {
36 | "dev-master": "0.1.x-dev"
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/di/src/Context.php:
--------------------------------------------------------------------------------
1 | set(ContainerInterface::class, self::$container);
30 | self::$container->set(Container::class, self::$container);
31 | }
32 | return self::$container;
33 | }
34 |
35 | public static function setContainer(ContainerInterface $container): void
36 | {
37 | self::$container = $container;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/di/src/Exception/ContainerException.php:
--------------------------------------------------------------------------------
1 | call($callback, $arguments);
40 | }
41 | }
42 |
43 | if (function_exists('make') === false) {
44 | /**
45 | * @template T
46 | *
47 | * @param class-string $id
48 | *
49 | * @return T
50 | * @throws NotFoundException
51 | * @throws ContainerExceptionInterface|ReflectionException
52 | */
53 | function make(string $id, array $parameters = [])
54 | {
55 | return container()->make($id, $parameters);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/di/tests/Bar.php:
--------------------------------------------------------------------------------
1 | container = Context::getContainer();
30 | }
31 |
32 | public function testBind()
33 | {
34 | $this->container->bind(FooInterface::class, Foo::class);
35 | $this->assertEquals($this->container->getBinding(FooInterface::class), Foo::class);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/di/tests/ContextTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(Context::hasContainer());
34 | }
35 |
36 | public function testGetContainer()
37 | {
38 | $this->assertTrue(Context::getContainer() instanceof ContainerInterface);
39 | }
40 |
41 | public function testSetContainer()
42 | {
43 | Context::setContainer(new Container());
44 | $this->assertTrue(Context::getContainer() instanceof ContainerInterface);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/di/tests/Foo.php:
--------------------------------------------------------------------------------
1 | user = false;
29 | }
30 |
31 | /**
32 | * 监听器优先级
33 | * 如果一个事件被多个监听器监听,那么执行顺序可以通过该方法调整
34 | * 优先级数字越大,优先级越高,越先执行
35 | * @return int
36 | */
37 | public function getPriority(): int
38 | {
39 | return 0;
40 | }
41 | }
42 | ```
43 |
44 | > 如果你不需要调整优先级,可以直接继承`Next\Event\EventListener`类
45 |
46 | ### 需要创建一个事件类
47 |
48 | ```php
49 | class UserRegistered
50 | {
51 | public $user;
52 |
53 | public function __construct($user)
54 | {
55 | $this->user = $user;
56 | }
57 | }
58 | ```
59 |
60 | ### 实例化`ListenerProvider`, 使用`addListener`添加监听器
61 |
62 | ```php
63 | $listenerProvider = new ListenerProvider();
64 | $listenerProvider->addListener(new UserStatusListener());
65 | ```
66 |
67 | ### 实例化调度器,给构造函数传入`ListenerProvider`实例
68 |
69 | ```php
70 | $dispatcher = new \Next\Event\EventDispatcher($listenerProvider);
71 | ```
72 |
73 | ### 事件调度
74 |
75 | ```php
76 | $user = User::find(1);
77 |
78 | $event = $dispatcher->dispatch(new UserRegistered($user));
79 | ```
80 |
81 | ## 可终止事件
82 |
83 | > 事件实现`StoppableEventInterface`接口中的`isPropagationStopped`方法,并且返回true,则不会触发该事件之后的事件
84 |
85 | ```php
86 | class UserRegistered implements StoppableEventInterface
87 | {
88 | public $user;
89 |
90 | public function __construct($user)
91 | {
92 | $this->user = $user;
93 | }
94 |
95 | public function isPropagationStopped() : bool
96 | {
97 | return true;
98 | }
99 | }
100 | ```
101 |
--------------------------------------------------------------------------------
/src/event/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/event",
3 | "license": "Apache-2.0",
4 | "description": "Event based on the PSR-14 specification",
5 | "homepage": "https://github.com/next-laboratory/event.git",
6 | "keywords": [
7 | "event",
8 | "psr-14"
9 | ],
10 | "authors": [
11 | {
12 | "name": "chengyao",
13 | "email": "chengyao0320@foxmail.com"
14 | }
15 | ],
16 | "autoload": {
17 | "psr-4": {
18 | "Next\\Event\\": "src/"
19 | }
20 | },
21 | "autoload-dev": {
22 | "psr-4": {
23 | "Next\\Event\\Tests\\": "tests/"
24 | }
25 | },
26 | "require": {
27 | "php": "^8.2",
28 | "psr/event-dispatcher": "^1.0"
29 | },
30 | "extra": {
31 | "branch-alias": {
32 | "dev-master": "0.1.x-dev"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/event/src/Contract/EventListenerInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | public function listen(): iterable;
20 |
21 | public function process(object $event): void;
22 |
23 | public function getPriority(): int;
24 | }
25 |
--------------------------------------------------------------------------------
/src/event/src/EventDispatcher.php:
--------------------------------------------------------------------------------
1 | listenerProvider->getListenersForEvent($event) as $listener) {
37 | $listener->process($event);
38 | if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
39 | break;
40 | }
41 | }
42 | return $event;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/event/src/EventListener.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | protected array $events = [];
23 |
24 | /**
25 | * 已经注册的监听器.
26 | *
27 | * @var array
28 | */
29 | protected array $listeners = [];
30 |
31 | /**
32 | * 注册单个事件监听.
33 | */
34 | public function addListener(EventListenerInterface ...$eventListeners): void
35 | {
36 | if (empty($eventListeners)) {
37 | return;
38 | }
39 | foreach ($eventListeners as $eventListener) {
40 | $listenerClass = $eventListener::class;
41 | if (! isset($this->listeners[$listenerClass])) {
42 | $this->listeners[$listenerClass] = $eventListener;
43 | foreach ($eventListener->listen() as $event) {
44 | $this->events[$event][] = $eventListener;
45 | }
46 | }
47 | }
48 | }
49 |
50 | /**
51 | * @return iterable
52 | */
53 | public function getListenersForEvent(object $event): iterable
54 | {
55 | $listeners = $this->events[$event::class] ?? [];
56 | $splPriorityQueue = new \SplPriorityQueue();
57 | foreach ($listeners as $listener) {
58 | $splPriorityQueue->insert($listener, $listener->getPriority());
59 | }
60 |
61 | return $splPriorityQueue;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/event/tests/EventDispatcherTest.php:
--------------------------------------------------------------------------------
1 | addListener(new FooListener(), new BarListener());
34 | $this->eventDispatcher = new EventDispatcher($listenerProvider);
35 | }
36 |
37 | public function testDispatch()
38 | {
39 | $fooEvent = $this->eventDispatcher->dispatch(new FooEvent());
40 |
41 | $this->assertEquals(FooListener::class, $fooEvent->value);
42 | }
43 |
44 | public function testStoppableEventDispatch()
45 | {
46 | $barEvent = $this->eventDispatcher->dispatch(new BarEvent());
47 |
48 | $this->assertEquals(2, $barEvent->value);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/event/tests/Events/BarEvent.php:
--------------------------------------------------------------------------------
1 | listenerProvider = new ListenerProvider();
31 | }
32 |
33 | public function testAddListeners()
34 | {
35 | $this->listenerProvider->addListener(new FooListener(), new BarListener());
36 | $listeners = $this->listenerProvider->getListenersForEvent(new FooEvent());
37 | $listenerClasses = [];
38 | foreach ($listeners as $listener) {
39 | $listenerClasses[] = $listener::class;
40 | }
41 | $this->assertEquals([BarListener::class, FooListener::class], $listenerClasses);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/event/tests/Listeners/BarListener.php:
--------------------------------------------------------------------------------
1 | value = self::class;
33 | break;
34 | case $event instanceof BarEvent:
35 | $event->value = 2;
36 | }
37 | }
38 |
39 | public function getPriority(): int
40 | {
41 | return 10;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/event/tests/Listeners/FooListener.php:
--------------------------------------------------------------------------------
1 | value = self::class;
33 | break;
34 | case $event instanceof BarEvent:
35 | $event->value = 1;
36 | break;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/foundation/README.md:
--------------------------------------------------------------------------------
1 | Foundation
--------------------------------------------------------------------------------
/src/foundation/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/foundation",
3 | "autoload": {
4 | "psr-4": {
5 | "Next\\Foundation\\": "src/"
6 | }
7 | },
8 | "authors": [
9 | {
10 | "name": "chengyao",
11 | "email": "chengyao0320@foxmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^8.2",
16 | "next/aop": "~0.1"
17 | },
18 | "extra": {
19 | "branch-alias": {
20 | "dev-master": "0.1.x-dev"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/foundation/src/Cache/Aspect/Cacheable.php:
--------------------------------------------------------------------------------
1 | remember($this->getKey($joinPoint), fn () => $joinPoint->process(), $this->ttl);
36 | }
37 |
38 | protected function getKey(ProceedingJoinPoint $joinPoint): string
39 | {
40 | $key = $this->key ?? ($joinPoint->class . ':' . $joinPoint->method . ':' . serialize(array_filter($joinPoint->parameters->getArrayCopy(), fn ($item) => ! is_object($item))));
41 | return $this->prefix ? ($this->prefix . ':' . $key) : $key;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/foundation/src/Config/Attribute/Config.php:
--------------------------------------------------------------------------------
1 | setValue($object, $this->getValue());
37 | } catch (\Throwable $e) {
38 | throw new PropertyHandleException('Property assign failed. ' . $e->getMessage());
39 | }
40 | }
41 |
42 | /**
43 | * 获取配置值
44 | *
45 | * @throws ContainerExceptionInterface
46 | * @throws \ReflectionException
47 | */
48 | protected function getValue()
49 | {
50 | return make(Repository::class)->get($this->key, $this->default);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/foundation/src/Console/Attribute/Command.php:
--------------------------------------------------------------------------------
1 | getType()) && $type = $type->getName()) || $type = $this->id) {
34 | $reflectionProperty->setValue($object, $this->getBinding($type));
35 | }
36 | } catch (\Throwable $e) {
37 | throw new PropertyHandleException('Property assign failed. ' . $e->getMessage());
38 | }
39 | }
40 |
41 | /**
42 | * @throws ContainerExceptionInterface
43 | * @throws \ReflectionException
44 | */
45 | protected function getBinding(string $type): object
46 | {
47 | return make($type);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/foundation/src/Event/Attribute/Listen.php:
--------------------------------------------------------------------------------
1 | addListener(make($class));
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/Controller.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['DELETE'];
21 | }
22 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/GetMapping.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['GET', 'HEAD'];
21 | }
22 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/PatchMapping.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['PATCH'];
21 | }
22 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/PostMapping.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['POST'];
21 | }
22 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/PutMapping.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['PUT'];
21 | }
22 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Attribute/RequestMapping.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public array $methods = ['GET', 'HEAD', 'POST'];
21 |
22 | /**
23 | * @param string $path 路径
24 | * @param array $methods 方法
25 | * @param array $middlewares 中间件
26 | */
27 | public function __construct(
28 | public string $path = '/',
29 | array $methods = [],
30 | public array $middlewares = [],
31 | ) {
32 | if (! empty($methods)) {
33 | $this->methods = $methods;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/foundation/src/Routing/Collector/RouteCollector.php:
--------------------------------------------------------------------------------
1 | make(RouteCollection::class);
43 | $router = new Router($attribute->prefix, $attribute->patterns, middlewares: $attribute->middlewares, routeCollection: $routeCollection);
44 | self::$router = $router;
45 | self::$class = $class;
46 | }
47 | }
48 |
49 | /**
50 | * @throws NotFoundException
51 | */
52 | public static function collectMethod(string $class, string $method, object $attribute): void
53 | {
54 | if ($attribute instanceof RequestMapping && self::$class === $class && ! is_null(self::$router)) {
55 | self::$router->request($attribute->path, [$class, $method], $attribute->methods)->withMiddleware(...$attribute->middlewares);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/http-message/README.md:
--------------------------------------------------------------------------------
1 | 一款基于Psr7的HTTP-Message
2 |
--------------------------------------------------------------------------------
/src/http-message/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/http-message",
3 | "license": "Apache-2.0",
4 | "home-page": "https://github.com/next-laboratory/http-message",
5 | "autoload": {
6 | "psr-4": {
7 | "Next\\Http\\Message\\": "src/"
8 | },
9 | "files": [
10 | "src/helpers.php"
11 | ]
12 | },
13 | "authors": [
14 | {
15 | "name": "chengyao",
16 | "email": "chengyao0320@foxmail.com"
17 | }
18 | ],
19 | "require": {
20 | "php": "^8.2",
21 | "ext-fileinfo": "*",
22 | "psr/http-message": "^2.0"
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "0.1.x-dev"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/http-message/src/Bag/CookieBag.php:
--------------------------------------------------------------------------------
1 | parameters = array_change_key_case($parameters, CASE_UPPER);
21 | }
22 |
23 | public function get(string $key, $default = null): mixed
24 | {
25 | return parent::get(strtoupper($key), $default);
26 | }
27 |
28 | public function set(string $key, $value)
29 | {
30 | parent::set(strtoupper($key), $value);
31 | }
32 |
33 | public function has(string $key): bool
34 | {
35 | return parent::has(strtoupper($key));
36 | }
37 |
38 | public function remove(string $key)
39 | {
40 | parent::remove(strtoupper($key));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/http-message/src/Bag/HeaderBag.php:
--------------------------------------------------------------------------------
1 | has($key)) {
21 | return $this->parameters[$this->map[strtoupper($key)]];
22 | }
23 | return $default;
24 | }
25 |
26 | public function set(string $key, $value)
27 | {
28 | $uppercaseKey = strtoupper($key);
29 | if (isset($this->map[$key])) {
30 | $this->parameters[$this->map[$key]] = $this->formatValue($value);
31 | } else {
32 | $this->map[$uppercaseKey] = $key;
33 | $this->parameters[$key] = $this->formatValue($value);
34 | }
35 | }
36 |
37 | public function has(string $key): bool
38 | {
39 | return isset($this->map[strtoupper($key)]);
40 | }
41 |
42 | public function remove(string $key)
43 | {
44 | if ($this->has($key)) {
45 | $uppercaseKey = strtoupper($key);
46 | $key = $this->map[$uppercaseKey];
47 | unset($this->parameters[$key], $this->map[$uppercaseKey]);
48 | }
49 | }
50 |
51 | public function replace(array $parameters = [])
52 | {
53 | $this->parameters = [];
54 | $this->map = [];
55 | foreach ($parameters as $key => $value) {
56 | $this->map[strtoupper($key)] = $key;
57 | $this->parameters[$key] = $this->formatValue($value);
58 | }
59 | }
60 |
61 | public function add(string $key, $value)
62 | {
63 | $uppercaseKey = strtoupper($key);
64 | if (isset($this->map[$uppercaseKey])) {
65 | array_push($this->parameters[$this->map[$uppercaseKey]], ...(array) $value);
66 | } else {
67 | $this->map[$uppercaseKey] = $key;
68 | $this->parameters[$key] = $this->formatValue($value);
69 | }
70 | }
71 |
72 | /**
73 | * @return array|string[]
74 | */
75 | protected function formatValue($value): array
76 | {
77 | if (is_scalar($value)) {
78 | $value = [(string) $value];
79 | }
80 | if (! is_array($value)) {
81 | throw new \InvalidArgumentException('The given header cannot be set.');
82 | }
83 |
84 | return array_values($value);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/http-message/src/Bag/ParameterBag.php:
--------------------------------------------------------------------------------
1 | replace($parameters);
21 | }
22 |
23 | public function get(string $key, $default = null)
24 | {
25 | return $this->parameters[$key] ?? $default;
26 | }
27 |
28 | public function set(string $key, $value)
29 | {
30 | $this->parameters[$key] = $value;
31 | }
32 |
33 | public function has(string $key): bool
34 | {
35 | return isset($this->parameters[$key]);
36 | }
37 |
38 | public function remove(string $key)
39 | {
40 | unset($this->parameters[$key]);
41 | }
42 |
43 | public function replace(array $parameters = [])
44 | {
45 | $this->parameters = $parameters;
46 | }
47 |
48 | public function all(): array
49 | {
50 | return $this->parameters;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/http-message/src/Exception/HttpException.php:
--------------------------------------------------------------------------------
1 | statusCode;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/http-message/src/Request.php:
--------------------------------------------------------------------------------
1 | uri = $uri instanceof UriInterface ? $uri : new Uri($uri);
33 | $this->formatBody($body);
34 | $this->headers = new HeaderBag($headers);
35 | }
36 |
37 | public function getRequestTarget(): string
38 | {
39 | if ($this->requestTarget === '/') {
40 | return $this->uri->getPath() . $this->uri->getQuery();
41 | }
42 | return '/';
43 | }
44 |
45 | public function withRequestTarget($requestTarget): RequestInterface
46 | {
47 | if ($requestTarget === $this->requestTarget) {
48 | return $this;
49 | }
50 | $new = clone $this;
51 | $new->requestTarget = $requestTarget;
52 |
53 | return $new;
54 | }
55 |
56 | public function getMethod(): string
57 | {
58 | return $this->method;
59 | }
60 |
61 | public function withMethod($method): RequestInterface
62 | {
63 | if ($method === $this->method) {
64 | return $this;
65 | }
66 | $new = clone $this;
67 | $new->method = $method;
68 |
69 | return $new;
70 | }
71 |
72 | public function getUri(): UriInterface
73 | {
74 | return $this->uri;
75 | }
76 |
77 | public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
78 | {
79 | if ($uri === $this->uri) {
80 | return $this;
81 | }
82 | $new = clone $this;
83 | if ($preserveHost === true) {
84 | $uri = $uri->withHost($this->getHeaderLine('Host'));
85 | }
86 | $new->uri = $uri;
87 |
88 | return $new;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/http-server/README.md:
--------------------------------------------------------------------------------
1 | 符合Psr7规范的兼容多容器的 Http Server
2 |
3 | # 设计思路
4 |
5 | request -> kernel -> response
6 |
7 | # 使用
8 |
9 | 新建类继承`\Next\Http\Server\Kernel`类
10 |
11 | ```php
12 | class HttpKernel extends Kernel
13 | {
14 | // 全局中间件
15 | protected array $middlewares = [];
16 |
17 | // 注册路由
18 | protected function map(Router $router)
19 | {
20 | $router->get('/', 'IndexController@index');
21 | }
22 | }
23 | ```
24 |
25 | 然后使用容器实例化`HttpKernel`类
26 |
27 | ```php
28 | $kernel = \Next\Di\Context::getContainer->make(HttpKernel::class);
29 |
30 | // 获取一个PsrServerRequest
31 | $request = \Next\Http\Message\ServerRequest::createFromGlobals();
32 |
33 | // 返回PsrResponse
34 | $response = $kernel->handle($request);
35 |
36 | // 发送响应
37 | (new \Next\Http\Server\FPMResponseEmitter())->emit($response);
38 |
39 | ```
40 |
41 | > 框架内置三种环境的ResponseEmitter,均可以自定义
42 |
43 | # 示例
44 |
45 | > FPM 环境
46 |
47 | ```php
48 | (function() {
49 | $loader = require_once '../vendor/autoload.php';
50 | /** @var Kernel $kernel */
51 | $kernel = Context::getContainer()->make(Kernel::class);
52 | $response = $kernel->handle(ServerRequest::createFromGlobals());
53 | (new FPMResponseEmitter())->emit($response);
54 | })();
55 | ```
56 |
57 | 你还可以通过继承Kernel类的方式来改写其中的某些方法或者放入全局中间件
58 |
--------------------------------------------------------------------------------
/src/http-server/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/http-server",
3 | "license": "Apache-2.0",
4 | "homepage": "https://github.com/next-laboratory/http-server",
5 | "autoload": {
6 | "psr-4": {
7 | "Next\\Http\\Server\\": "src/"
8 | }
9 | },
10 | "authors": [
11 | {
12 | "name": "chengyao",
13 | "email": "chengyao0320@foxmail.com"
14 | }
15 | ],
16 | "require": {
17 | "php": "^8.2",
18 | "next/http-message": "^0.1",
19 | "psr/http-server-middleware": "^1.0",
20 | "psr/http-server-handler": "^1.0"
21 | },
22 | "extra": {
23 | "branch-alias": {
24 | "dev-master": "0.1.x-dev"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/http-server/src/Exception/MethodNotAllowedException.php:
--------------------------------------------------------------------------------
1 | */
15 | protected array $middlewares = [];
16 | protected RequestHandlerInterface $handler;
17 |
18 | public function handle(ServerRequestInterface $request): ResponseInterface
19 | {
20 | if ($middleware = current($this->middlewares)) {
21 | next($this->middlewares);
22 | return $middleware->process($request, $this);
23 | }
24 |
25 | if (!isset($this->handler)) {
26 | throw new RuntimeException('Handler has not bee set');
27 | }
28 |
29 | return $this->handler->handle($request);
30 | }
31 |
32 | public function withHandler(RequestHandlerInterface $handler): static
33 | {
34 | if ($handler instanceof self) {
35 | throw new InvalidArgumentException('Handler must not be an instance of RequestHandler');
36 | }
37 | $this->handler = $handler;
38 |
39 | return $this;
40 | }
41 |
42 | public function withMiddleware(MiddlewareInterface ...$middlewares): static
43 | {
44 | array_push($this->middlewares, ...$middlewares);
45 |
46 | return $this;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/http-throttle/README.md:
--------------------------------------------------------------------------------
1 | 开发中,本包提取自thinkphp
2 |
--------------------------------------------------------------------------------
/src/http-throttle/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/http-throttle",
3 | "autoload": {
4 | "psr-4": {
5 | "Next\\Http\\Throttle\\": "src/"
6 | }
7 | },
8 | "authors": [
9 | {
10 | "name": "chengyao",
11 | "email": "chengyao0320@foxmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^8.0",
16 | "psr/http-message": "^1.0"
17 | },
18 | "extra": {
19 | "branch-alias": {
20 | "dev-master": "1.x-dev"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/http-throttle/src/Handlers/CounterFixed.php:
--------------------------------------------------------------------------------
1 | get($key, 0);
28 | $now = (int) $micronow;
29 | $wait_reset_seconds = $duration - $now % $duration; // 距离下次重置还有n秒时间
30 | $this->waitSeconds = $wait_reset_seconds % $duration + 1;
31 | $this->currentRequests = $cur_requests;
32 |
33 | if ($cur_requests < $max_requests) { // 允许访问
34 | $cache->set($key, $this->currentRequests + 1, $wait_reset_seconds);
35 | return true;
36 | }
37 |
38 | return false;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/http-throttle/src/Handlers/CounterSlider.php:
--------------------------------------------------------------------------------
1 | get($key, []);
29 | $now = (int) $micronow;
30 | // 移除过期的请求的记录
31 | $history = array_values(array_filter($history, function ($val) use ($now, $duration) {
32 | return $val >= $now - $duration;
33 | }));
34 |
35 | $this->currentRequests = count($history);
36 | if ($this->currentRequests < $max_requests) {
37 | // 允许访问
38 | $history[] = $now;
39 | $cache->set($key, $history, $duration);
40 | return true;
41 | }
42 |
43 | if ($history) {
44 | $waitSeconds = $duration - ($now - $history[0]) + 1;
45 | $this->waitSeconds = max($waitSeconds, 0);
46 | }
47 |
48 | return false;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/http-throttle/src/Handlers/LeakyBucket.php:
--------------------------------------------------------------------------------
1 | get($key, 0); // 最近一次请求
29 | $rate = (float) $duration / $max_requests; // 平均 n 秒一个请求
30 | if ($micronow - $last_time < $rate) {
31 | $this->currentRequests = 1;
32 | $this->waitSeconds = ceil($rate - ($micronow - $last_time));
33 | return false;
34 | }
35 |
36 | $cache->set($key, $micronow, $duration);
37 | return true;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/http-throttle/src/Handlers/ThrottleAbstract.php:
--------------------------------------------------------------------------------
1 | waitSeconds;
45 | }
46 |
47 | /**
48 | * 当前已有的请求数.
49 | */
50 | public function getCurRequests(): int
51 | {
52 | return $this->currentRequests;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/http-throttle/src/Handlers/TokenBucket.php:
--------------------------------------------------------------------------------
1 | get($key, null);
35 | $store_num = $cache->get($assist_key, null);
36 |
37 | if ($last_time === null || $store_num === null) { // 首次访问
38 | $cache->set($key, $micronow, $duration);
39 | $cache->set($assist_key, $max_requests - 1, $duration);
40 | return true;
41 | }
42 |
43 | $create_num = floor(($micronow - $last_time) * $rate); // 推算生成的 token 数
44 | $token_left = (int) min($max_requests, $store_num + $create_num); // 当前剩余 tokens 数量
45 |
46 | if ($token_left < 1) {
47 | $tmp = (int) ceil($duration / $max_requests);
48 | $this->waitSeconds = $tmp - (int) ($micronow - $last_time) % $tmp;
49 | return false;
50 | }
51 | $this->currentRequests = $max_requests - $token_left;
52 | $cache->set($key, $micronow, $duration);
53 | $cache->set($assist_key, $token_left - 1, $duration);
54 | return true;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/http-throttle/src/RateLimitException.php:
--------------------------------------------------------------------------------
1 | server->serveHttp($request);
75 | }
76 | }
77 | ```
78 |
79 | 如上新建了一个控制器,路由地址为/jsonrpc,启动服务
80 |
81 | > 服务端必须使用swoole/workerman等常驻内存的环境
82 |
83 | ### 测试请求
84 |
85 | > GET 127.0.0.1:8989/jsonrpc
86 | ```json
87 | {
88 | "jsonrpc": "2.0",
89 | "id": 123,
90 | "method": "calc.sum",
91 | "params": {
92 | "a": 1,
93 | "b": 2
94 | }
95 | }
96 | ```
97 |
98 | 响应
99 |
100 | ```json
101 | {
102 | "jsonrpc": "2.0",
103 | "result": 3,
104 | "id": 123
105 | }
106 | ```
107 |
108 | ## 客户端
109 |
110 | 请求
111 |
112 | ```php
113 | $client = new Client('http://127.0.0.1:8989');
114 | $response = $client->call(new Request('calc.sum', ['a' => 1, 'b' => 2]));
115 | dump($response);
116 | ```
117 |
118 | 响应
119 |
120 | ```
121 | ^ array:3 [
122 | "jsonrpc" => "2.0"
123 | "result" => 3
124 | "id" => "d562e03439af8a69d71ee833b8972bbd"
125 | ]
126 | ```
127 |
128 | 如果要发送通知可以使用notify方法
129 |
--------------------------------------------------------------------------------
/src/json-rpc/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/json-rpc",
3 | "autoload": {
4 | "psr-4": {
5 | "Next\\JsonRpc\\": "src/"
6 | }
7 | },
8 | "authors": [
9 | {
10 | "name": "chengyao",
11 | "email": "chengyao0320@foxmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^8.0",
16 | "max/http-message": "dev-master",
17 | "max/di": "dev-master"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/json-rpc/src/Attribute/RpcService.php:
--------------------------------------------------------------------------------
1 | client = new GzClient(['base_uri' => $this->uri]);
29 | }
30 |
31 | /**
32 | * @throws GuzzleException
33 | * @throws Exception
34 | */
35 | public function call(Request $request, string $requestMethod = 'GET')
36 | {
37 | if (!$request->hasId()) {
38 | $request->setId(md5(uniqid()));
39 | }
40 | return Response::createFromPsrResponse($this->sendRequest($request, $requestMethod));
41 | }
42 |
43 | /**
44 | * @throws GuzzleException
45 | */
46 | public function sendRequest(Request $request, string $requestMethod = 'GET'): ResponseInterface
47 | {
48 | return $this->client->request($requestMethod, '/', ['json' => $request]);
49 | }
50 |
51 | /**
52 | * @throws GuzzleException
53 | */
54 | public function notify(Request $request, string $requestMethod = 'GET'): void
55 | {
56 | $this->sendRequest($request, $requestMethod);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/json-rpc/src/Event/RpcCalled.php:
--------------------------------------------------------------------------------
1 | getHeaderLine('Content-Type'), 'application/json')) {
31 | throw new InvalidArgumentException('Invalid Request', -32600);
32 | }
33 | $body = $request->getBody()->getContents();
34 | $parts = json_decode($body, true);
35 | if (!isset($parts['jsonrpc'], $parts['method'])) {
36 | throw new InvalidArgumentException('Parse error', -32700);
37 | }
38 | return new static($parts['method'], $parts['params'] ?? [], $parts['id'] ?? null, $parts['jsonrpc']);
39 | }
40 |
41 | public function getMethod(): string
42 | {
43 | return $this->method;
44 | }
45 |
46 | public function getParams(): array
47 | {
48 | return $this->params;
49 | }
50 |
51 | public function getId(): mixed
52 | {
53 | return $this->id;
54 | }
55 |
56 | public function hasId(): bool
57 | {
58 | return isset($this->id);
59 | }
60 |
61 | public function getJsonrpc(): string
62 | {
63 | return $this->jsonrpc;
64 | }
65 |
66 | public function setMethod(string $method): void
67 | {
68 | $this->method = $method;
69 | }
70 |
71 | public function setParams(array $params): void
72 | {
73 | $this->params = $params;
74 | }
75 |
76 | public function setId(mixed $id): void
77 | {
78 | $this->id = $id;
79 | }
80 |
81 | public function setJsonrpc(string $jsonrpc): void
82 | {
83 | $this->jsonrpc = $jsonrpc;
84 | }
85 |
86 | public function jsonSerialize()
87 | {
88 | return get_object_vars($this);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/json-rpc/src/Message/Response.php:
--------------------------------------------------------------------------------
1 | getHeaderLine('Content-Type'), 'application/json')) {
30 | throw new \Exception('Invalid Response', -32600);
31 | }
32 | $body = $response->getBody()->getContents();
33 | return json_decode($body, true);
34 | }
35 |
36 | public function getResult(): mixed
37 | {
38 | return $this->result;
39 | }
40 |
41 | public function getId(): mixed
42 | {
43 | return $this->id;
44 | }
45 |
46 | public function getError(): ?Error
47 | {
48 | return $this->error;
49 | }
50 |
51 | public function getJsonrpc(): string
52 | {
53 | return $this->jsonrpc;
54 | }
55 |
56 | public function jsonSerialize()
57 | {
58 | return array_filter(get_object_vars($this));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/json-rpc/src/ServiceCollector.php:
--------------------------------------------------------------------------------
1 | name;
29 | make(Server::class)->register($service, $class);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/pipeline/README.md:
--------------------------------------------------------------------------------
1 | pipeline
--------------------------------------------------------------------------------
/src/pipeline/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/pipeline",
3 | "license": "Apache-2.0",
4 | "autoload": {
5 | "psr-4": {
6 | "Next\\Pipeline\\": "src/"
7 | }
8 | },
9 | "authors": [
10 | {
11 | "name": "chengyao",
12 | "email": "chengyao0320@foxmail.com"
13 | }
14 | ],
15 | "require": {
16 | "php": "^8.0||^8.1||^8.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/pipeline/src/Abort.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | protected array $handlers = [];
27 |
28 | final public function withHandlers(callable ...$handlers): static
29 | {
30 | if (!empty($handlers)) {
31 | array_push($this->handlers, ...$handlers);
32 | }
33 |
34 | return $this;
35 | }
36 |
37 | final public function next(): void
38 | {
39 | if (count($this->handlers) === 0) {
40 | throw new RuntimeException('There is no handler that can be executed');
41 | }
42 | array_shift($this->handlers)($this);
43 | }
44 |
45 | /**
46 | * @throws Abort
47 | */
48 | final public function abort(string $message = '', int $code = 0)
49 | {
50 | throw new Abort($message, $code);
51 | }
52 |
53 | final public function setValues(array $values): void
54 | {
55 | $this->values = $values;
56 | }
57 |
58 | final public function setValue(string $key, mixed $value): void
59 | {
60 | $this->values[$key] = $value;
61 | }
62 |
63 | final public function hasValue(string $key): bool
64 | {
65 | return isset($this->values[$key]);
66 | }
67 |
68 | final public function getValue(string $key): mixed
69 | {
70 | return $this->values[$key] ?? null;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/pool/README.md:
--------------------------------------------------------------------------------
1 | Pool
2 |
--------------------------------------------------------------------------------
/src/pool/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/pool",
3 | "autoload": {
4 | "psr-4": {
5 | "Next\\Pool\\": "src/"
6 | }
7 | },
8 | "authors": [
9 | {
10 | "name": "chengyao",
11 | "email": "chengyao0320@foxmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^8.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/pool/src/BasePool.php:
--------------------------------------------------------------------------------
1 | isOpen) {
18 | $this->splQueue = new SplQueue();
19 | $this->isOpen = true;
20 | } else {
21 | throw new RuntimeException('Pool is opened');
22 | }
23 | }
24 |
25 | public function get()
26 | {
27 | $this->isOpen();
28 | $isMaximum = $this->currentSize >= $this->getPoolCapacity();
29 | if ($this->splQueue->isEmpty() && $isMaximum) {
30 | throw new RuntimeException('Too many connections');
31 | }
32 | if (!$isMaximum) {
33 | $this->splQueue->enqueue($this->newPoolItem());
34 | $this->currentSize++;
35 | }
36 | return $this->splQueue->dequeue();
37 | }
38 |
39 | public function release($poolItem)
40 | {
41 | $this->isOpen();
42 | if ($this->splQueue->count() < $this->getPoolCapacity()) {
43 | $this->splQueue->enqueue($poolItem);
44 | }
45 | }
46 |
47 | public function discard($poolItem)
48 | {
49 | $this->isOpen();
50 | $this->currentSize--;
51 | }
52 |
53 | protected function isOpen()
54 | {
55 | if (!$this->isOpen) {
56 | throw new RuntimeException('Pool is not opened');
57 | }
58 | }
59 |
60 | public function close()
61 | {
62 | $this->isOpen();
63 | $this->splQueue = new SplQueue();
64 | $this->isOpen = false;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/pool/src/BasePoolItem.php:
--------------------------------------------------------------------------------
1 | failed) {
27 | throw new Exception('Object unavailable');
28 | }
29 | return $this->object->{$name}(...$arguments);
30 | } catch (Throwable $e) {
31 | $this->failed = true;
32 | throw $e;
33 | }
34 | }
35 |
36 | public function __destruct()
37 | {
38 | if ($this->failed) {
39 | $this->pool->discard($this);
40 | } else {
41 | $this->pool->release($this);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/pool/src/Contract/PoolInterface.php:
--------------------------------------------------------------------------------
1 | exists('maxphp');
16 | // 执行多条
17 | $redis->wrap(function(Redis $redis) {
18 | if(!$redis->exists('maxphp')) {
19 | $redis->set('maxphp', 'good');
20 | }
21 | });
22 | ```
23 |
24 | 执行多条命令时需要使用wrap方法,方法接收一个接收\Redis实例的闭包,在闭包内通过调用\Redis实例的方法实现。
25 |
--------------------------------------------------------------------------------
/src/redis/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/redis",
3 | "autoload": {
4 | "psr-4": {
5 | "Next\\Redis\\": "src/"
6 | }
7 | },
8 | "authors": [
9 | {
10 | "name": "chengyao",
11 | "email": "chengyao0320@foxmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^8.0",
16 | "max/pool": "dev-master",
17 | "ext-redis": "*"
18 | },
19 | "require-dev": {
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/redis/src/Client.php:
--------------------------------------------------------------------------------
1 | wrap(function ($redis) use ($name, $arguments) {
36 | return $redis->{$name}(...$arguments);
37 | });
38 | }
39 |
40 | public function multi(Closure $callback, int $mode = Redis::MULTI)
41 | {
42 | return $this->wrap(function (Redis $redis) use ($callback, $mode) {
43 | try {
44 | $redis = $redis->multi($mode);
45 | $result = $callback($redis);
46 | $redis->exec();
47 | return $result;
48 | } catch (Throwable $e) {
49 | $redis->discard();
50 | throw $e;
51 | }
52 | });
53 | }
54 |
55 | /**
56 | * @param string|string[] $key
57 | * @throws Throwable
58 | */
59 | public function watch(string|array $key, Closure $callback)
60 | {
61 | return $this->wrap(function (Redis $redis) use ($callback, $key) {
62 | $redis->watch($key);
63 | return $callback($redis);
64 | });
65 | }
66 |
67 | /**
68 | * @throws Throwable
69 | */
70 | public function wrap(Closure $callable)
71 | {
72 | try {
73 | $redis = $this->connector->get();
74 | return $callable($redis);
75 | } catch (Throwable $e) {
76 | $redis = null;
77 | throw $e;
78 | } finally {
79 | $this->connector->release($redis);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/redis/src/Connector/BaseConnector.php:
--------------------------------------------------------------------------------
1 | connect(
38 | $this->host,
39 | $this->port,
40 | $this->timeout,
41 | $this->reserved,
42 | $this->retryInterval,
43 | $this->readTimeout
44 | );
45 | $redis->select($this->database);
46 | $this->auth && $redis->auth($this->auth);
47 | return $redis;
48 | }
49 |
50 | public function release($connection)
51 | {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/redis/src/Connector/BasePoolConnector.php:
--------------------------------------------------------------------------------
1 | open();
24 | }
25 |
26 | public function getPoolCapacity(): int
27 | {
28 | return $this->poolSize;
29 | }
30 |
31 | /**
32 | * @throws RedisException
33 | */
34 | public function newPoolItem()
35 | {
36 | $redis = new Redis();
37 | $redis->pconnect(
38 | $this->host,
39 | $this->port,
40 | $this->timeout,
41 | $this->reserved,
42 | $this->retryInterval,
43 | $this->readTimeout
44 | );
45 | $redis->select($this->database);
46 | $this->auth && $redis->auth($this->auth);
47 | return $redis;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/redis/src/Connector/SwoolePoolConnector.php:
--------------------------------------------------------------------------------
1 | withHost($this->host)
35 | ->withPort($this->port)
36 | ->withTimeout($this->timeout)
37 | ->withReadTimeout($this->readTimeout)
38 | ->withRetryInterval($this->retryInterval)
39 | ->withReserved($this->reserved)
40 | ->withDbIndex($this->database)
41 | ->withAuth($this->auth);
42 | $this->pool = new RedisPool($redisConfig, $this->poolSize);
43 | $this->pool->fill();
44 | }
45 |
46 | public function get()
47 | {
48 | return $this->pool->get();
49 | }
50 |
51 | public function release($connection)
52 | {
53 | $this->pool->put($connection);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/redis/src/Contract/ConnectorInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | protected array $routes = [];
25 |
26 | /**
27 | * 添加一个路由.
28 | */
29 | public function add(Route $route): Route
30 | {
31 | foreach ($route->getMethods() as $method) {
32 | $this->routes[$method][] = $route;
33 | }
34 | return $route;
35 | }
36 |
37 | /**
38 | * 全部.
39 | *
40 | * @return array
41 | */
42 | public function all(): array
43 | {
44 | return $this->routes;
45 | }
46 |
47 | /**
48 | * @return Route[]
49 | */
50 | public function list(string $method): array
51 | {
52 | return $this->routes[$method]
53 | ?? throw new MethodNotAllowedException(StatusCodeInterface::STATUS_METHOD_NOT_ALLOWED, 'Method not allowed: ' . $method);
54 | }
55 |
56 | public function count(): int
57 | {
58 | return \count($this->routes);
59 | }
60 |
61 | /**
62 | * @return \ArrayIterator
63 | */
64 | public function getIterator(): \ArrayIterator
65 | {
66 | return new \ArrayIterator($this->routes);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/routing/src/UrlMatcher.php:
--------------------------------------------------------------------------------
1 | getUri()->getPath(), '/');
32 | $method = $request->getMethod();
33 | return $this->match($method, $path);
34 | }
35 |
36 | /**
37 | * 使用请求方法和请求path来匹配路由.
38 | */
39 | public function match(string $method, string $path): Route
40 | {
41 | foreach ($this->routeCollection->list($method) as $route) {
42 | if (($compiledPath = $route->getCompiledPath()) && \preg_match($compiledPath, $path, $match)) {
43 | $matchedRoute = clone $route;
44 | if (!empty($match)) {
45 | foreach ($route->getParameters() as $key => $value) {
46 | if (\array_key_exists($key, $match)) {
47 | $matchedRoute->setParameter($key, $match[$key]);
48 | }
49 | }
50 | }
51 | return $matchedRoute;
52 | }
53 | }
54 |
55 | throw new NotFoundException(StatusCodeInterface::STATUS_NOT_FOUND, 'Not Found');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/session/README.md:
--------------------------------------------------------------------------------
1 | ## Session组件,支持File和Redis(不支持协程) Handler,可以自定义SesssionHandler
2 |
3 | ```php
4 | composer require next/session
5 | ```
6 |
7 | ```php
8 | $sessionHandler = new \Next\Session\Handler\FileHandler();
9 |
10 | $session = new \Next\Session\Session($sessionHandler);
11 |
12 | $session->start(null); // 如果为null则创建id
13 | $session->set('foo', 'bar');
14 | $session->get('foo');
15 | $session->save();
16 | $session->close();
17 |
18 | $sessionId = $session->getId();
19 | ```
20 |
--------------------------------------------------------------------------------
/src/session/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/session",
3 | "license": "Apache-2.0",
4 | "homepage": "https://github.com/next-laboratory/session",
5 | "authors": [
6 | {
7 | "name": "chengyao",
8 | "email": "chengyao0320@foxmail.com"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "Next\\Session\\": "src/"
14 | }
15 | },
16 | "require": {
17 | "php": "^8.2"
18 | },
19 | "extra": {
20 | "branch-alias": {
21 | "dev-master": "0.1.x-dev"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/session/src/Handler/RedisHandler.php:
--------------------------------------------------------------------------------
1 | redis->del($id);
41 | } catch (\RedisException) {
42 | return false;
43 | }
44 | }
45 |
46 | public function gc(int $max_lifetime): false|int
47 | {
48 | return 1;
49 | }
50 |
51 | /**
52 | * @throws \RedisException
53 | */
54 | public function open(string $path, string $name): bool
55 | {
56 | $this->redis = new \Redis();
57 | if ($this->redis->connect(
58 | $this->host,
59 | $this->port,
60 | $this->timeout,
61 | $this->persistentId,
62 | $this->retryInterval,
63 | $this->readTimeout,
64 | $this->context
65 | )) {
66 | $this->redis->select($this->database);
67 | if ($this->password) {
68 | $this->redis->auth($this->password);
69 | }
70 | }
71 |
72 | return false;
73 | }
74 |
75 | public function read(string $id): false|string
76 | {
77 | try {
78 | if ($data = $this->redis->get($this->normalizeId($id))) {
79 | return (string) $data;
80 | }
81 | return false;
82 | } catch (\RedisException) {
83 | return false;
84 | }
85 | }
86 |
87 | public function write(string $id, string $data): bool
88 | {
89 | try {
90 | return (bool) $this->redis->set($this->normalizeId($id), $data, $this->sessionTTL);
91 | } catch (\RedisException) {
92 | return false;
93 | }
94 | }
95 |
96 | protected function normalizeId(string $id): string
97 | {
98 | $key = 'session:' . $id;
99 | if ($this->sessionPrefix) {
100 | $key = $this->sessionPrefix . ':' . $key;
101 | }
102 |
103 | return $key;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/session/src/SessionException.php:
--------------------------------------------------------------------------------
1 | =4.6"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/swoole/src/Context.php:
--------------------------------------------------------------------------------
1 | 0) {
32 | self::for($cid)[$key] = $item;
33 | } else {
34 | self::$container[$key] = $item;
35 | }
36 | }
37 |
38 | public static function delete(string $key = ''): void
39 | {
40 | if (($cid = self::getCid()) > 0) {
41 | if (!empty($key)) {
42 | unset(self::for($cid)[$key]);
43 | }
44 | } else {
45 | if ($key) {
46 | unset(self::$container[$key]);
47 | } else {
48 | self::$container = [];
49 | }
50 | }
51 | }
52 |
53 | public static function has(string $key): bool
54 | {
55 | if (($cid = self::getCid()) > 0) {
56 | return isset(self::for($cid)[$key]);
57 | }
58 | return isset(self::$container[$key]);
59 | }
60 |
61 | public static function for(?int $cid = null): ?SwooleContext
62 | {
63 | return Coroutine::getContext($cid);
64 | }
65 |
66 | protected static function getCid(): int
67 | {
68 | if (class_exists('Swoole\Coroutine')) {
69 | return Coroutine::getCid();
70 | }
71 | return -1;
72 | }
73 |
74 | public static function inCoroutine(): bool
75 | {
76 | return self::getCid() >= 0;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/swoole/src/Exception/ParallelExecutionException.php:
--------------------------------------------------------------------------------
1 | results;
23 | }
24 |
25 | public function setResults(array $results)
26 | {
27 | $this->results = $results;
28 | }
29 |
30 | public function getThrowables(): array
31 | {
32 | return $this->throwables;
33 | }
34 |
35 | public function setThrowables(array $throwables): array
36 | {
37 | return $this->throwables = $throwables;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/swoole/src/Table/Exception/DuplicateKeyException.php:
--------------------------------------------------------------------------------
1 | create();
39 | static::$tables[$tableName] = $table;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/swoole/tests/Concerns/UserTable.php:
--------------------------------------------------------------------------------
1 | 'json'];
12 | }
--------------------------------------------------------------------------------
/src/utils/README.md:
--------------------------------------------------------------------------------
1 | 这个包中的大部分代码均来自于laravel, 感谢laravel. 使用这个包让你在不使用laravel的场景下使用laravel中强大的辅助函数或类
--------------------------------------------------------------------------------
/src/utils/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/utils",
3 | "type": "library",
4 | "license": "Apache-2.0",
5 | "homepage": "https://github.com/next-laboratory/utils",
6 | "authors": [
7 | {
8 | "name": "chengyao",
9 | "email": "chengyao0320@foxmail.com"
10 | }
11 | ],
12 | "autoload": {
13 | "files": [
14 | "src/helpers.php"
15 | ],
16 | "psr-4": {
17 | "Next\\Utils\\": "src/"
18 | }
19 | },
20 | "autoload-dev": {
21 | "psr-4": {
22 | "Next\\Utils\\Tests\\": "tests/"
23 | }
24 | },
25 | "require": {
26 | "php": "^8.2",
27 | "symfony/finder": "*",
28 | "ramsey/uuid": "*",
29 | "symfony/mime": "*",
30 | "voku/portable-ascii": "*",
31 | "league/commonmark": "*",
32 | "doctrine/inflector": "*"
33 | },
34 | "suggest": {
35 | "ext-bcmath": "*"
36 | },
37 | "extra": {
38 | "branch-alias": {
39 | "dev-master": "0.1.x-dev"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/src/Composer.php:
--------------------------------------------------------------------------------
1 | bindTo(null, static::class);
45 | }
46 |
47 | return $macro(...$parameters);
48 | }
49 |
50 | /**
51 | * Dynamically handle calls to the class.
52 | *
53 | * @return mixed
54 | * @throws \BadMethodCallException
55 | */
56 | public function __call(string $method, array $parameters)
57 | {
58 | if (! static::hasMacro($method)) {
59 | throw new \BadMethodCallException(sprintf(
60 | 'Method %s::%s does not exist.',
61 | static::class,
62 | $method
63 | ));
64 | }
65 |
66 | $macro = static::$macros[$method];
67 |
68 | if ($macro instanceof \Closure) {
69 | $macro = $macro->bindTo($this, static::class);
70 | }
71 |
72 | return $macro(...$parameters);
73 | }
74 |
75 | /**
76 | * Register a custom macro.
77 | */
78 | public static function macro(string $name, callable|object $macro): void
79 | {
80 | static::$macros[$name] = $macro;
81 | }
82 |
83 | /**
84 | * Mix another object into the class.
85 | *
86 | * @throws \ReflectionException
87 | */
88 | public static function mixin(object $mixin, bool $replace = true): void
89 | {
90 | $methods = (new \ReflectionClass($mixin))->getMethods(
91 | \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED
92 | );
93 |
94 | foreach ($methods as $method) {
95 | if ($replace || ! static::hasMacro($method->name)) {
96 | $method->setAccessible(true);
97 | static::macro($method->name, $method->invoke($mixin));
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * Checks if macro is registered.
104 | */
105 | public static function hasMacro(string $name): bool
106 | {
107 | return isset(static::$macros[$name]);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/utils/src/Optional.php:
--------------------------------------------------------------------------------
1 | value)) {
39 | return $this->value->{$key} ?? null;
40 | }
41 | return null;
42 | }
43 |
44 | /**
45 | * Dynamically check a property exists on the underlying object.
46 | *
47 | * @return bool
48 | */
49 | public function __isset(mixed $name)
50 | {
51 | if (is_object($this->value)) {
52 | return isset($this->value->{$name});
53 | }
54 |
55 | if (is_array($this->value) || $this->value instanceof \ArrayObject) {
56 | return isset($this->value[$name]);
57 | }
58 |
59 | return false;
60 | }
61 |
62 | /**
63 | * Dynamically pass a method to the underlying object.
64 | *
65 | * @return mixed
66 | */
67 | public function __call(string $method, array $parameters)
68 | {
69 | if (static::hasMacro($method)) {
70 | return $this->macroCall($method, $parameters);
71 | }
72 |
73 | if (is_object($this->value)) {
74 | return $this->value->{$method}(...$parameters);
75 | }
76 | }
77 |
78 | #[\ReturnTypeWillChange]
79 | public function offsetExists($key)
80 | {
81 | return Arr::accessible($this->value) && Arr::exists($this->value, $key);
82 | }
83 |
84 | #[\ReturnTypeWillChange]
85 | public function offsetGet($key)
86 | {
87 | return Arr::get($this->value, $key);
88 | }
89 |
90 | #[\ReturnTypeWillChange]
91 | public function offsetSet($key, $value)
92 | {
93 | if (Arr::accessible($this->value)) {
94 | $this->value[$key] = $value;
95 | }
96 | }
97 |
98 | #[\ReturnTypeWillChange]
99 | public function offsetUnset($key)
100 | {
101 | if (Arr::accessible($this->value)) {
102 | unset($this->value[$key]);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/utils/src/Packer/JsonPacker.php:
--------------------------------------------------------------------------------
1 | method = $method;
43 | $this->collection = $collection;
44 | }
45 |
46 | /**
47 | * Proxy accessing an attribute onto the collection items.
48 | *
49 | * @param string $key
50 | *
51 | * @return mixed
52 | */
53 | public function __get($key)
54 | {
55 | return $this->collection->{$this->method}(function ($value) use ($key) {
56 | return is_array($value) ? $value[$key] : $value->{$key};
57 | });
58 | }
59 |
60 | /**
61 | * Proxy a method call onto the collection items.
62 | *
63 | * @param string $method
64 | * @param array $parameters
65 | *
66 | * @return mixed
67 | */
68 | public function __call($method, $parameters)
69 | {
70 | return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
71 | return $value->{$method}(...$parameters);
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/utils/src/Proxy/HigherOrderTapProxy.php:
--------------------------------------------------------------------------------
1 | target = $target;
35 | }
36 |
37 | /**
38 | * Dynamically pass method calls to the target.
39 | *
40 | * @param string $method
41 | * @param array $parameters
42 | * @return mixed
43 | */
44 | public function __call($method, $parameters)
45 | {
46 | $this->target->{$method}(...$parameters);
47 |
48 | return $this->target;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/src/Proxy/HigherOrderWhenProxy.php:
--------------------------------------------------------------------------------
1 | target = $target;
52 | }
53 |
54 | /**
55 | * Proxy accessing an attribute onto the target.
56 | *
57 | * @param string $key
58 | * @return mixed
59 | */
60 | public function __get($key)
61 | {
62 | if (! $this->hasCondition) {
63 | $condition = $this->target->{$key};
64 |
65 | return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
66 | }
67 |
68 | return $this->condition
69 | ? $this->target->{$key}
70 | : $this->target;
71 | }
72 |
73 | /**
74 | * Proxy a method call on the target.
75 | *
76 | * @param string $method
77 | * @param array $parameters
78 | * @return mixed
79 | */
80 | public function __call($method, $parameters)
81 | {
82 | if (! $this->hasCondition) {
83 | $condition = $this->target->{$method}(...$parameters);
84 |
85 | return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
86 | }
87 |
88 | return $this->condition
89 | ? $this->target->{$method}(...$parameters)
90 | : $this->target;
91 | }
92 |
93 | /**
94 | * Set the condition on the proxy.
95 | *
96 | * @param bool $condition
97 | * @return $this
98 | */
99 | public function condition($condition)
100 | {
101 | [$this->condition, $this->hasCondition] = [$condition, true];
102 |
103 | return $this;
104 | }
105 |
106 | /**
107 | * Indicate that the condition should be negated.
108 | *
109 | * @return $this
110 | */
111 | public function negateConditionOnCapture()
112 | {
113 | $this->negateConditionOnCapture = true;
114 |
115 | return $this;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/utils/src/Traits/AutoFillProperties.php:
--------------------------------------------------------------------------------
1 | $value) {
22 | if ($force || property_exists($this, $key)) {
23 | $this->{$key} = $value;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/src/Traits/Conditionable.php:
--------------------------------------------------------------------------------
1 | condition($value);
44 | }
45 |
46 | if ($value) {
47 | return $callback($this, $value) ?? $this;
48 | }
49 | if ($default) {
50 | return $default($this, $value) ?? $this;
51 | }
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * Apply the callback if the given "value" is (or resolves to) falsy.
58 | *
59 | * @template TUnlessParameter
60 | * @template TUnlessReturnType
61 | *
62 | * @param null|mixed $value
63 | * @param null|(callable($this, TUnlessParameter): TUnlessReturnType) $callback
64 | * @param null|(callable($this, TUnlessParameter): TUnlessReturnType) $default
65 | *
66 | * @return $this|TUnlessReturnType
67 | */
68 | public function unless($value = null, ?callable $callback = null, ?callable $default = null)
69 | {
70 | $value = $value instanceof \Closure ? $value($this) : $value;
71 |
72 | if (func_num_args() === 0) {
73 | return (new HigherOrderWhenProxy($this))->negateConditionOnCapture();
74 | }
75 |
76 | if (func_num_args() === 1) {
77 | return (new HigherOrderWhenProxy($this))->condition(! $value);
78 | }
79 |
80 | if (! $value) {
81 | return $callback($this, $value) ?? $this;
82 | }
83 | if ($default) {
84 | return $default($this, $value) ?? $this;
85 | }
86 |
87 | return $this;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/utils/src/Traits/Tappable.php:
--------------------------------------------------------------------------------
1 | toArray();
26 | } else {
27 | $data = (array) $data;
28 | }
29 | if ($parentNode === null) {
30 | $xml = new \SimpleXMLElement('' . "<{$root}>{$root}>");
31 | } else {
32 | $xml = $parentNode;
33 | }
34 | foreach ($data as $key => $value) {
35 | if (is_array($value)) {
36 | self::toXml($value, $xml->addChild($key));
37 | } else {
38 | if (is_numeric($key)) {
39 | $xml->addChild('item' . $key, (string) $value);
40 | } else {
41 | $xml->addChild($key, (string) $value);
42 | }
43 | }
44 | }
45 | return trim($xml->asXML());
46 | }
47 |
48 | public static function toArray($xml)
49 | {
50 | // For PHP 8.0, libxml_disable_entity_loader() has been deprecated.
51 | // As libxml 2.9.0 is now required, external entity loading is guaranteed to be disabled by default.
52 | // And this function is no longer needed to protect against XXE attacks, unless the (still vulnerable). LIBXML_NOENT is used.
53 | // In that case, it is recommended to refactor the code using libxml_set_external_entity_loader() to suppress loading of external entities.
54 | if (\PHP_VERSION_ID < 80000) {
55 | $disableLibxmlEntityLoader = libxml_disable_entity_loader(true);
56 | $respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
57 | libxml_disable_entity_loader($disableLibxmlEntityLoader);
58 | } else {
59 | $respObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOERROR);
60 | }
61 |
62 | if ($respObject === false) {
63 | throw new \InvalidArgumentException('Syntax error.');
64 | }
65 |
66 | return json_decode(json_encode($respObject), true);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/utils/tests/BCTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(BC::class, $b);
36 | $this->assertEquals($b->toString(), $value);
37 | }
38 |
39 | /**
40 | * @throws InvalidArgumentException
41 | * @throws ExpectationFailedException
42 | */
43 | public function testAdd()
44 | {
45 | $b = BC::new(1.23);
46 | $b = $b->add(2.34, 2);
47 | $this->assertEquals(3.57, $b->toString());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/validation/src/RuleInterface.php:
--------------------------------------------------------------------------------
1 | value = $value;
22 | }
23 |
24 | public function failure(): string
25 | {
26 | return $this->message;
27 | }
28 |
29 | public function valid(): bool
30 | {
31 | if (!$valid = in_array($this->value, $this->haystack)) {
32 | $this->message = '验证不通过';
33 | }
34 |
35 | return $valid;
36 | }
37 | }
--------------------------------------------------------------------------------
/src/var-dumper/README.md:
--------------------------------------------------------------------------------
1 | var-dumper 适配包,用来将变量打印到浏览器
2 |
3 | # 安装
4 |
5 | ```shell
6 | composer require next/var-dumper
7 | ```
8 |
9 | # 使用
10 |
11 | ## hyperf
12 |
13 | 修改`app/config/autoload/exceptions.php`
14 |
15 | ```php
16 | [
22 | 'http' => [
23 | Next\VarDumper\Adapter\HyperfDumperHandler::class,
24 | Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
25 | App\Exception\Handler\AppExceptionHandler::class,
26 | ],
27 | ],
28 | ];
29 |
30 | ```
31 |
32 | ## webman
33 |
34 | 建立新的异常处理类
35 |
36 | ```php
37 | \App\ExceptionHandler::class,
68 | ];
69 | ```
70 |
71 | ## 其他框架可参考webman配置,引入DumperHandler,将异常转为响应即可
72 |
73 | # 打印
74 |
75 | ```php
76 | d($request);
77 | ```
78 |
--------------------------------------------------------------------------------
/src/var-dumper/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next/var-dumper",
3 | "license": "Apache-2.0",
4 | "homepage": "https://github.com/next-laboratory/var-dumper",
5 | "autoload": {
6 | "psr-4": {
7 | "Next\\VarDumper\\": "src/"
8 | },
9 | "files": [
10 | "src/helpers.php"
11 | ]
12 | },
13 | "authors": [
14 | {
15 | "name": "chengyao",
16 | "email": "chengyao0320@foxmail.com"
17 | }
18 | ],
19 | "require": {
20 | "php": ">=7.4",
21 | "symfony/var-dumper": "^7.0"
22 | },
23 | "extra": {
24 | "branch-alias": {
25 | "dev-master": "0.1.x-dev"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/var-dumper/src/Adapter/HyperfDumperHandler.php:
--------------------------------------------------------------------------------
1 | stopPropagation();
32 |
33 | return $response->withBody(new SwooleStream(self::convertToHtml($e)));
34 | }
35 |
36 | public function isValid(\Throwable $e): bool
37 | {
38 | return $e instanceof Dumper;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/var-dumper/src/Dumper.php:
--------------------------------------------------------------------------------
1 | vars = $vars;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/var-dumper/src/DumperHandler.php:
--------------------------------------------------------------------------------
1 | addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
25 | foreach ($abort->vars as $var) {
26 | (new HtmlDumper())->dump($cloner->cloneVar($var));
27 | }
28 | return (string) ob_get_clean();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/var-dumper/src/helpers.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 轻量 • 简单 • 快速
8 |
9 |
10 |
11 |
12 |
13 |
14 | `MaxPHP`视图组件,支持`Blade`,可扩展驱动。 可以独立使用!
15 |
16 | # 安装
17 |
18 | ```
19 | composer require max/view
20 | ```
21 |
22 | # 使用
23 |
24 | > Blade引擎支持的语法如下
25 |
26 | - {{}}
27 | - {{-- --}}
28 | - {!! !!}
29 | - @extends
30 | - @yield
31 | - @php
32 | - @include
33 | - @if
34 | - @unless
35 | - @empty
36 | - @isset
37 | - @foreach
38 | - @for
39 | - @switch
40 | - @section
41 |
42 | > 如果使用`extends` + `yield` + `section`, 务必保证子模板中除了`extends` 之外的所有代码均被`section` 包裹
43 |
44 | ## 配置文件
45 |
46 | 安装完成后框架会自动将配置文件`view.php`移动到根包的`config`目录下,如果创建失败,可以手动创建。文件内容如下:
47 |
48 | ```php
49 | '\Next\View\Engine\BladeEngine',
53 | 'config' => [
54 | // 模板目录
55 | 'path' => __DIR__ . '/../views/',
56 | // 编译和缓存目录
57 | 'compile_dir' => __DIR__ . '/../runtime/cache/views/compile',
58 | // 模板缓存
59 | 'cache' => false,
60 | // 模板后缀
61 | 'suffix' => '.blade.php',
62 | ],
63 | ];
64 |
65 | ```
66 |
67 | ## 使用
68 |
69 | ```php
70 | // 如果你使用maxphp的容器实例化该类,则不需要传入任何参数,只需要添加相应配置文件即可。
71 | $viewFactory = new ViewFactory($config);
72 | $renderer = $viewFactory->getRenderer();
73 |
74 | // 如果你没有使用maxphp, 则需要实例化renderer, 传入对应的驱动
75 | $renderer = new \Next\View\Renderer(new \Next\View\Engine\BladeEngine($options));
76 |
77 | $renderer->assign('key', 'value');
78 | $renderer->render('index', ['key2' => 'value2']);
79 | ```
80 |
81 | ## 自定义引擎
82 |
83 | 自定义引擎必须实现`ViewEngineInterface`接口
84 |
--------------------------------------------------------------------------------
/src/view/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/view",
3 | "license": "Apache-2.0",
4 | "homepage": "https://github.com/marxphp/view",
5 | "description": "A simple blade view engine.",
6 | "authors": [
7 | {
8 | "name": "chengyao",
9 | "email": "chengyao0320@foxmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^8.0",
14 | "max/utils": "dev-master",
15 | "max/config": "dev-master"
16 | },
17 | "autoload": {
18 | "psr-4": {
19 | "Next\\View\\": "src/"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/view/publish/view.php:
--------------------------------------------------------------------------------
1 | 'Next\View\Engine\BladeEngine',
14 | 'config' => [
15 | // 模板目录
16 | 'path' => __DIR__ . '/../views/',
17 | // 编译和缓存目录
18 | 'compileDir' => __DIR__ . '/../runtime/cache/views/',
19 | // 模板缓存
20 | 'cache' => false,
21 | // 模板后缀
22 | 'suffix' => '.blade.php',
23 | ],
24 | // 'engine' => 'Next\View\Engine\PhpEngine',
25 | // 'config' => [
26 | // // 模板目录
27 | // 'path' => __DIR__ . '/../views/',
28 | // // 模板后缀
29 | // 'suffix' => '.blade.php',
30 | // ],
31 | ];
32 |
--------------------------------------------------------------------------------
/src/view/src/Contract/ViewEngineInterface.php:
--------------------------------------------------------------------------------
1 | path;
37 | }
38 |
39 | public function isCache(): bool
40 | {
41 | return $this->cache;
42 | }
43 |
44 | public function getSuffix(): string
45 | {
46 | return $this->suffix;
47 | }
48 |
49 | public function getCompileDir(): string
50 | {
51 | return $this->compileDir;
52 | }
53 |
54 | /**
55 | * @throws ViewNotExistException
56 | */
57 | public function render(string $template, array $arguments = [])
58 | {
59 | $this->renderView($template, $arguments);
60 | }
61 |
62 | /**
63 | * @throws ViewNotExistException
64 | */
65 | protected function renderView(): void
66 | {
67 | extract(\func_get_arg(1));
68 | include (new Compiler($this))->compile(\func_get_arg(0));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/view/src/Engine/PhpEngine.php:
--------------------------------------------------------------------------------
1 | fillProperties($config);
28 | }
29 |
30 | public function render(string $template, array $arguments = []): void
31 | {
32 | $this->renderView($template, $arguments);
33 | }
34 |
35 | protected function renderView(): void
36 | {
37 | extract(func_get_arg(1));
38 | include $this->findViewFile(func_get_arg(0));
39 | }
40 |
41 | protected function findViewFile(string $view): string
42 | {
43 | return sprintf('%s/%s%s', rtrim($this->path, '/'), trim($view, '/'), $this->suffix);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/view/src/Exception/ViewNotExistException.php:
--------------------------------------------------------------------------------
1 | arguments[$name] = $value;
27 | }
28 |
29 | public function render(string $template, array $arguments = []): string
30 | {
31 | ob_start();
32 | echo (string) $this->engine->render($template, array_merge($this->arguments, $arguments));
33 | return (string) ob_get_clean();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/view/src/ViewFactory.php:
--------------------------------------------------------------------------------
1 | engine);
29 | }
30 |
31 | public function render(string $template, array $arguments = []): string
32 | {
33 | return $this->getRenderer()->render($template, $arguments);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/watcher/README.md:
--------------------------------------------------------------------------------
1 | 开发中
2 |
--------------------------------------------------------------------------------
/src/watcher/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "max/watcher",
3 | "license": "Apache-2.0",
4 | "autoload": {
5 | "psr-4": {
6 | "Next\\Watcher\\": "src/"
7 | }
8 | },
9 | "authors": [
10 | {
11 | "name": "chengyao",
12 | "email": "chengyao0320@foxmail.com"
13 | }
14 | ],
15 | "require": {
16 | "max/utils": "dev-master"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/watcher/src/Contract/DriverInterface.php:
--------------------------------------------------------------------------------
1 | findFiles() as $file) {
22 | $this->last[$file->getRealPath()] = $file->getMTime();
23 | }
24 | }
25 |
26 | public function watch(): void
27 | {
28 | while (true) {
29 | usleep($this->interval);
30 | $currentFiles = [];
31 | $modified = $added = [];
32 | foreach ($this->findFiles() as $file) {
33 | $realPath = $file->getRealPath();
34 | $fileMTime = $file->getMTime();
35 | if (!isset($this->last[$realPath])) {
36 | $added[] = $realPath;
37 | $this->last[$realPath] = $fileMTime;
38 | } else {
39 | if ($this->last[$realPath] != $fileMTime) {
40 | $modified[] = $realPath;
41 | $this->last[$realPath] = $fileMTime;
42 | }
43 | }
44 |
45 | $currentFiles[$realPath] = $fileMTime;
46 | }
47 | $deleted = array_diff_key($this->last, $currentFiles);
48 | $this->last = array_diff_key($this->last, $deleted);
49 | $deleted = array_keys($deleted);
50 | clearstatcache();
51 | if (!empty($modified) || !empty($added) || !empty($deleted)) {
52 | ($this->callback)($added, $modified, $deleted);
53 | }
54 | }
55 | }
56 |
57 | protected function findFiles(): Finder
58 | {
59 | return Finder::create()->in($this->dirs)->name($this->pattern)->files();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/watcher/src/Watcher.php:
--------------------------------------------------------------------------------
1 | writeLine('Watching filesystem');
27 |
28 | $this->driver->watch();
29 | }
30 |
31 | public function writeLine(string $message): void
32 | {
33 | printf("\033[33m [INFO] \033[0m %s\n", $message);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |