├── .editorconfig
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .kodiak.toml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── bin
└── generate-facades.php
├── book.json
├── composer.json
├── composer.lock
├── docs
├── .gitbook
│ └── assets
│ │ └── siler.png
├── README.md
├── SUMMARY.md
├── concepts-and-philosophy.md
├── functional.md
├── graphql.md
├── graphql
│ ├── README.md
│ └── annotations.md
├── psrs-and-middlewares-pipelines.md
├── routing.md
├── swoole.md
├── twig-templating.md
└── web-servers.md
├── examples
├── README.md
├── functional
│ └── functional.php
├── graphql-annotations
│ ├── .gitignore
│ ├── .graphqlconfig
│ ├── composer.json
│ ├── composer.lock
│ ├── index.php
│ ├── schema.php
│ ├── src
│ │ ├── Bar.php
│ │ ├── Foo.php
│ │ ├── FooBar.php
│ │ ├── HelloWorld.php
│ │ ├── ITodo.php
│ │ ├── Mutation.php
│ │ ├── Query.php
│ │ ├── Subscription.php
│ │ ├── Todo.php
│ │ ├── TodoStatus.php
│ │ ├── TupleInput.php
│ │ └── Upper.php
│ └── subscriptions.php
├── graphql
│ ├── .gitignore
│ ├── .graphqlconfig
│ ├── README.md
│ ├── directives.php
│ ├── docker-compose.yml
│ ├── filters.php
│ ├── resolvers.php
│ ├── sapi.php
│ ├── schema.graphql
│ ├── swoole.php
│ └── uploads
│ │ └── .gitignore
├── grpc
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── README.md
│ ├── clients
│ │ ├── dart
│ │ │ ├── .gitignore
│ │ │ ├── bin
│ │ │ │ └── client.dart
│ │ │ ├── lib
│ │ │ │ └── src
│ │ │ │ │ └── generated
│ │ │ │ │ ├── helloworld.pb.dart
│ │ │ │ │ ├── helloworld.pbenum.dart
│ │ │ │ │ ├── helloworld.pbgrpc.dart
│ │ │ │ │ └── helloworld.pbjson.dart
│ │ │ ├── pubspec.lock
│ │ │ └── pubspec.yaml
│ │ ├── go
│ │ │ ├── .gitignore
│ │ │ ├── helloworld.pb.go
│ │ │ └── main.go
│ │ ├── node
│ │ │ ├── .gitignore
│ │ │ ├── client.js
│ │ │ ├── helloworld_grpc_pb.js
│ │ │ ├── helloworld_pb.js
│ │ │ ├── package-lock.json
│ │ │ └── package.json
│ │ └── php
│ │ │ ├── .gitignore
│ │ │ ├── bin
│ │ │ └── client.php
│ │ │ ├── composer.json
│ │ │ └── src
│ │ │ ├── GPBMetadata
│ │ │ └── Helloworld.php
│ │ │ └── Helloworld
│ │ │ ├── GreeterClient.php
│ │ │ ├── HelloReply.php
│ │ │ └── HelloRequest.php
│ ├── proto_gen.sh
│ ├── protos
│ │ └── helloworld.proto
│ └── server.php
├── hello-world
│ ├── README.md
│ ├── hello-world.phtml
│ └── index.php
├── laminas
│ ├── hello-world
│ │ └── index.php
│ ├── index.php
│ └── routes
│ │ └── index.php
├── mail
│ ├── .env.example
│ ├── .gitignore
│ └── swiftmailer.php
├── monolog
│ ├── index.php
│ └── siler.log
├── psr7-diactoros
│ ├── index.php
│ └── template.twig
├── psr7-route
│ └── index.php
├── route-any
│ └── index.php
├── route-files
│ ├── .models
│ ├── README.md
│ ├── controllers
│ │ ├── about.get.php
│ │ ├── contact.get.php
│ │ ├── contact.post.php
│ │ └── index.get.php
│ ├── index.php
│ └── views
│ │ ├── about.twig
│ │ ├── contact.twig
│ │ ├── home.twig
│ │ └── layout.twig
├── route-not-found
│ ├── books
│ │ └── index.php
│ └── index.php
├── swoole-chat
│ ├── docker-compose.yml
│ ├── index.html
│ └── server.php
├── swoole
│ ├── README.md
│ ├── api
│ │ └── todos.php
│ ├── docker-compose.yml
│ ├── index.php
│ ├── pages
│ │ ├── _layout.twig
│ │ ├── home.php
│ │ └── home.twig
│ └── public
│ │ └── assets
│ │ ├── scripts.js
│ │ └── styles.css
└── twig
│ ├── home.twig
│ └── index.php
├── media
├── README.md
├── logo.psd
├── siler.png
└── siler_avatar.jpg
├── phpcs.xml.dist
├── phpunit.xml.dist
├── psalm.xml.dist
├── siler.png
├── src
├── Config
│ └── Config.php
├── Container
│ └── Container.php
├── Diactoros
│ └── Diactoros.php
├── Dotenv
│ └── Dotenv.php
├── Encoder
│ └── Json.php
├── Env
│ └── Env.php
├── File
│ └── File.php
├── Functional
│ ├── Functional.php
│ └── Monad
│ │ ├── Identity.php
│ │ ├── Maybe.php
│ │ └── Monad.php
├── GraphQL
│ ├── Annotation
│ │ ├── Args.php
│ │ ├── Directive.php
│ │ ├── EnumType.php
│ │ ├── EnumVal.php
│ │ ├── Field.php
│ │ ├── InputType.php
│ │ ├── InterfaceType.php
│ │ ├── ObjectType.php
│ │ └── UnionType.php
│ ├── BuildSchema.php
│ ├── DateScalar.php
│ ├── DateTimeScalar.php
│ ├── Deannotator.php
│ ├── GraphQL.php
│ ├── GraphiQL.php
│ ├── Request.php
│ ├── SubscriptionsConnection.php
│ └── SubscriptionsManager.php
├── Grpc
│ ├── Grpc.php
│ └── Parser.php
├── Http
│ ├── Http.php
│ ├── Request.php
│ └── Response.php
├── HttpHandlerRunner
│ └── HttpHandlerRunner.php
├── Mail
│ └── SwiftMailer.php
├── Monolog
│ ├── Loggers.php
│ └── Monolog.php
├── Prelude
│ ├── Arr.php
│ ├── Collection.php
│ ├── Dispatcher.php
│ ├── Enum.php
│ ├── FromArray.php
│ ├── FromArrayInterface.php
│ ├── IO.php
│ ├── Klass.php
│ ├── Obj.php
│ ├── Patch.php
│ ├── PatchInterface.php
│ ├── Prelude.php
│ ├── Str.php
│ ├── ToArray.php
│ ├── ToArrayInterface.php
│ └── Tuple.php
├── Ratchet
│ ├── GraphQLSubscriptionsConnection.php
│ ├── GraphQLSubscriptionsServer.php
│ └── Ratchet.php
├── Route
│ └── Route.php
├── Siler.php
├── Stratigility
│ ├── RequestHandlerDecorator.php
│ └── Stratigility.php
├── Swoole
│ ├── GraphQLSubscriptionsConnection.php
│ └── Swoole.php
├── Twig
│ └── Twig.php
└── facades.php
└── tests
├── Integration
├── ComposabilityTest.php
├── FizzbuzzTest.php
└── RoutingTest.php
├── Unit
├── Config
│ └── ConfigTest.php
├── Container
│ └── ContainerTest.php
├── Diactoros
│ └── DiactorosTest.php
├── Dotenv
│ └── DotenvTest.php
├── Encoder
│ └── JsonTest.php
├── Env
│ └── EnvTest.php
├── File
│ └── FileTest.php
├── Functional
│ ├── FunctionalTest.php
│ └── MonadTest.php
├── GraphQL
│ ├── Annotated
│ │ ├── Bar.php
│ │ ├── Enum.php
│ │ ├── Foo.php
│ │ ├── FooBar.php
│ │ ├── IFoo.php
│ │ ├── Input.php
│ │ ├── ListOfException.php
│ │ ├── Mutation.php
│ │ ├── MyDirective.php
│ │ └── Query.php
│ ├── AnnotatedTest.php
│ ├── BuildSchemaTest.php
│ ├── DateTimeScalarTest.php
│ ├── GraphQLResolverTest.php
│ ├── GraphQLTest.php
│ └── SubscriptionsManagerTest.php
├── Http
│ ├── HttpTest.php
│ ├── RequestTest.php
│ └── ResponseTest.php
├── HttpHandlerRunner
│ └── HttpHandlerRunnerTest.php
├── Mail
│ └── SwiftMailerTest.php
├── Monolog
│ └── MonologTest.php
├── Prelude
│ ├── ArrTest.php
│ ├── CollectionTest.php
│ ├── DispatcherTest.php
│ ├── EnumTest.php
│ ├── FromToArrayTest.php
│ ├── IOTest.php
│ ├── ObjTest.php
│ ├── PatchFromToFixture.php
│ ├── StrTest.php
│ ├── Test.php
│ ├── TestEnum.php
│ └── TupleTest.php
├── Ratchet
│ ├── GraphQLSubscriptionsConnectionTest.php
│ ├── GraphQLSubscriptionsServerTest.php
│ └── RatchetTest.php
├── Route
│ ├── RouteClass.php
│ ├── RouteClassNameTest.php
│ ├── RouteFacadeTest.php
│ ├── RouteFileTest.php
│ ├── RouteFileWithPrefixTest.php
│ ├── RoutePsr7Test.php
│ ├── RouteResourceTest.php
│ ├── RouteStaticMethodTest.php
│ ├── RouteTest.php
│ ├── RouteUtf8Test.php
│ └── SwooleHttpRequestMock.php
├── SilerTest.php
├── Stratigility
│ ├── RequestHandlerDecoratorTest.php
│ └── StratigilityTest.php
├── Swoole
│ └── SwooleTest.php
└── Twig
│ └── TwigTest.php
└── fixtures
├── .env
├── TestEvent.php
├── callable_require.php
├── concat
├── bar.txt
└── foo.txt
├── config
├── test.ext
├── test.ini
├── test.php
└── yaml
│ └── test.yml
├── foo.php
├── graphql_error.json
├── graphql_input.json
├── php_input.json
├── php_input.txt
├── resources
├── create.php
├── destroy.php
├── edit.php
├── index.php
├── show.php
├── slug
│ └── edit.php
├── store.php
└── update.php
├── route_files
├── about
│ └── index.get.php
├── contact.get.php
├── contact.post.php
├── foo.$id.get.php
├── foo.@id.get.php
├── foo.{id}.get.php
└── index.get.php
├── schema.graphql
├── static.twig
├── template.twig
├── test.csv
└── to_be_required.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | indent_size = 2
10 |
11 | [*.php]
12 | indent_size = 4
13 |
14 | [{composer.json,*.neon}]
15 | indent_size = 4
16 |
17 | [{*.txt,*.json}]
18 | insert_final_newline = false
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [leocavalcante]
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "08:00"
8 | open-pull-requests-limit: 10
9 | target-branch: main
10 | labels:
11 | - automerge
12 | versioning-strategy: increase
13 | allow:
14 | - dependency-type: direct
15 | - dependency-type: indirect
16 | ignore:
17 | - dependency-name: vimeo/psalm
18 | versions:
19 | - 4.7.0
20 | - dependency-name: symfony/polyfill-intl-grapheme
21 | versions:
22 | - 1.22.1
23 | - dependency-name: symfony/polyfill-php80
24 | versions:
25 | - 1.22.1
26 | - dependency-name: symfony/polyfill-ctype
27 | versions:
28 | - 1.22.1
29 | - dependency-name: symfony/polyfill-php73
30 | versions:
31 | - 1.22.1
32 | - dependency-name: symfony/polyfill-intl-normalizer
33 | versions:
34 | - 1.22.1
35 | - dependency-name: symfony/string
36 | versions:
37 | - 5.2.3
38 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | php-versions: ['7.4', '8.0']
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v1
15 |
16 | - name: Setup
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: ${{ matrix.php-versions }}
20 | coverage: xdebug
21 |
22 | - name: Validate
23 | run: composer validate
24 |
25 | - name: Cache dir
26 | id: composer-cache
27 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
28 |
29 | - name: Cache
30 | uses: actions/cache@v2
31 | with:
32 | path: ${{ steps.composer-cache.outputs.dir }}
33 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
34 | restore-keys: ${{ runner.os }}-composer-
35 |
36 | - name: Install
37 | run: composer install --prefer-dist --no-progress --no-suggest
38 |
39 | - name: Test
40 | run: composer ci
41 |
42 | - name: Coverage
43 | uses: codecov/codecov-action@v1
44 | with:
45 | token: ${{ secrets.CODECOV_TOKEN }}
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.cache
2 | *.log
3 | /*.phar
4 | /.idea/
5 | /coverage-*
6 | /issues/
7 | /phpcs.xml
8 | /phpunit.xml
9 | /psalm.xml
10 | /vendor/
11 |
--------------------------------------------------------------------------------
/.kodiak.toml:
--------------------------------------------------------------------------------
1 | # .kodiak.toml
2 | # Minimal config. version is the only required field.
3 | version = 1
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2021 Leo Cavalcante
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | 1.5.3 | :white_check_mark: |
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Mail lc at leocavalcante.com. 24 hours SLA.
12 |
--------------------------------------------------------------------------------
/bin/generate-facades.php:
--------------------------------------------------------------------------------
1 | -
3 | Siler is a set of general purpose high-level abstractions aiming an API for
4 | declarative programming in PHP.
5 | ---
6 |
7 | # Siler
8 |
9 | > Simplicity is the ultimate sophistication. – Leonardo Da Vinci
10 |
11 | You can use it within any framework or standalone, as a micro-framework:
12 |
13 | ```bash
14 | composer require leocavalcante/siler
15 | ```
16 |
17 | ## A Hello World example
18 |
19 | ```php
20 | use Siler\Functional as λ; // Just to be cool, don't use non-ASCII identifiers ;)
21 | use Siler\Route;
22 |
23 | Route\get('/', λ\puts('Hello World'));
24 | ```
25 |
26 | This outputs "Hello World" when the file is reached via HTTP using the GET method and an URI path that matches "/". **Got the idea, right?**
27 |
28 |
--------------------------------------------------------------------------------
/docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [Siler](README.md)
4 | * [Routing](routing.md)
5 | * [PSRs & Middleware Pipeline](psrs-and-middlewares-pipelines.md)
6 | * [Twig Templating](twig-templating.md)
7 | * [GraphQL](graphql/README.md)
8 | * [@Annotations](graphql/annotations.md)
9 | * [Web Servers](web-servers.md)
10 | * [Siler ❤️ Swoole](swoole.md)
11 | * [λ Functional](functional.md)
12 | * [Concepts & Philosophy](concepts-and-philosophy.md)
13 |
14 |
--------------------------------------------------------------------------------
/docs/twig-templating.md:
--------------------------------------------------------------------------------
1 | # Twig Templating
2 |
3 | ```
4 | composer require twig/twig
5 | ```
6 |
7 | {% hint style="info" %}
8 | Siler doesn't have direct dependencies, to stay fit, it favors peer dependencies, which means you have to explicitly declare a `twig` dependency in your project in order to use it.
9 | {% endhint %}
10 |
11 | Siler will internally handle the `Twig_Environment` instance.
12 |
13 | ```php
14 | use Siler\Twig;
15 |
16 | Twig\init('path/to/templates');
17 | ```
18 |
19 | Actually it is also returned at `init` function call, so you call add Twig plugins, filters and functions, for example, adding `Siler\Http\url` into Twig's Environment to later reference static assets on the public folder:
20 |
21 | ```php
22 | Twig\init('path/to/templates')
23 | ->addFunction(new Twig_SimpleFunction('url', 'Siler\Http\url'));
24 | ```
25 |
26 | At initialization, you can also provide a path to templates cache as second argument and if you want to let Twig debug as third argument \(defaults to `false`\):
27 |
28 | ```php
29 | $shouldTwigDebug = true;
30 | Twig\init('path/to/templates', 'path/to/templates/cache', $shouldTwigDebug);
31 | ```
32 |
33 | To render a template, simply call `render` at Twig namespace:
34 |
35 | ```php
36 | echo Twig\render('pages/home.twig');
37 | ```
38 |
39 | An passing parameters can be done by the second argument:
40 |
41 | ```php
42 | $data = ['message' => 'Hello World'];
43 | echo Twig\render('pages/home.twig', $data);
44 | ```
45 |
46 | Something that can be confusing using Siler is that some function does outputs and other doesn't, like `Twig\render`. So remember that `Twig\render` will only return the rendered template within its given data and you should explicit output or let `Response` do it:
47 |
48 | ```php
49 | $html = Twig\render('pages/home.twig');
50 | Response\html($html);
51 | ```
52 |
53 | Also, remember that **you can always bring you own template engine to the playground without any bridging stuff** or use PHP itself on your views.
54 |
55 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Siler Examples
2 |
3 | See https://github.com/siler-examples for more.
--------------------------------------------------------------------------------
/examples/functional/functional.php:
--------------------------------------------------------------------------------
1 | trim($s)),
9 | λ\lfilter(λ\not(λ\equal('baz'))),
10 | λ\non_empty,
11 | λ\ljoin(',')
12 | ]);
13 |
14 | echo $pipeline(['foo', ' ', 'bar', 'baz']); // foo,bar
15 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/.graphqlconfig:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Annotated GraphQL Schema",
3 | "extensions": {
4 | "endpoints": {
5 | "Default GraphQL Endpoint": {
6 | "url": "http://localhost:8080/graphql"
7 | }
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "php": ">=7.4"
4 | },
5 | "autoload": {
6 | "psr-4": {
7 | "Siler\\Example\\GraphQL\\Annotation\\": "src/"
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/examples/graphql-annotations/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "03b47b7576525da6cbb4b64a32d9eaf0",
8 | "packages": [],
9 | "packages-dev": [],
10 | "aliases": [],
11 | "minimum-stability": "stable",
12 | "stability-flags": [],
13 | "prefer-stable": false,
14 | "prefer-lowest": false,
15 | "platform": {
16 | "php": ">=7.4"
17 | },
18 | "platform-dev": [],
19 | "plugin-api-version": "1.1.0"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/index.php:
--------------------------------------------------------------------------------
1 | init($schema));
17 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/schema.php:
--------------------------------------------------------------------------------
1 | x + $input->y;
26 | }
27 |
28 | /**
29 | * @Field(description="Sums of tuples", listOf="int")
30 | * @Args({
31 | * @Field(name="inputs", listOf=TupleInput::class, nullableList=true)
32 | * })
33 | * @return array
34 | */
35 | public static function sums($_, array $args): array
36 | {
37 | return array_map(static function (array $input) {
38 | $input = TupleInput::fromArray($input);
39 | return $input->x + $input->y;
40 | }, array_get_arr($args, 'inputs'));
41 | }
42 |
43 | /**
44 | * @Field()
45 | * @Args({@Field(name="message", type="string")})
46 | */
47 | public static function ekko($_, array $args): string
48 | {
49 | publish('ekko', $args['message']);
50 | return $args['message'];
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/src/Query.php:
--------------------------------------------------------------------------------
1 | 50 ? new Foo() : new Bar();
37 | }
38 |
39 | /**
40 | * @Field(listOf=Todo::class)
41 | * @return Todo[]
42 | */
43 | public static function todos(): array
44 | {
45 | $parent = new Todo('Parent todo');
46 | $todo = new Todo('Something to do');
47 | $todo->parent = $parent;
48 | return [$todo];
49 | }
50 |
51 | public static function dynamicFields(): array
52 | {
53 | return map(range(1, 10), fn(int $i) => (new Field())->name("index$i")->type('Int')->resolve(always($i)));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/src/Subscription.php:
--------------------------------------------------------------------------------
1 | title = $title;
36 | $this->status = TodoStatus::TODO;
37 | }
38 |
39 | /**
40 | * @Field(type = TodoStatus::class)
41 | * @param Todo $todo
42 | * @return int
43 | */
44 | public static function status(Todo $todo): int
45 | {
46 | return $todo->status;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/graphql-annotations/src/TodoStatus.php:
--------------------------------------------------------------------------------
1 | start();
17 |
--------------------------------------------------------------------------------
/examples/graphql/.gitignore:
--------------------------------------------------------------------------------
1 | /db.sqlite
2 | /.env
--------------------------------------------------------------------------------
/examples/graphql/.graphqlconfig:
--------------------------------------------------------------------------------
1 | {
2 | "schemaPath": "schema.graphql",
3 | "extensions": {
4 | "endpoints": {
5 | "siler_graphql": {
6 | "url": "http://localhost:8000",
7 | "subscription": {
8 | "url": "ws://localhost:8001"
9 | }
10 | }
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/graphql/README.md:
--------------------------------------------------------------------------------
1 | # Siler GraphQL Example
2 |
3 | This showcases:
4 | - Queries
5 | - Mutations
6 | - Subscriptions (+filters)
7 | - Directives
8 | - File uploads
9 |
10 | If you miss something you would like to see how is done with Siler, feel free to request.
11 |
12 | - Use the `swoole.php` file to see it working on top of Swoole runtime (Docker compose provided: `docker-compose up`).
13 |
14 | - Use `sapi.php` to see how it could be done with regular PHP over (Fast)CGI (`php -S localhost:8000 sapi.php`) *(subscriptions not supported)*.
15 |
--------------------------------------------------------------------------------
/examples/graphql/directives.php:
--------------------------------------------------------------------------------
1 | function (callable $resolver): Closure {
9 | return function ($root, $args, $context, $info) use ($resolver): string {
10 | $value = $resolver($root, $args, $context, $info);
11 | return mb_strtoupper($value, 'UTF-8');
12 | };
13 | },
14 | ];
15 |
--------------------------------------------------------------------------------
/examples/graphql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | api:
4 | container_name: siler_example_graphql
5 | image: phpswoole/swoole
6 | volumes:
7 | - ../../:/var/www
8 | ports:
9 | - 8000:8000
10 | - 8001:8001
11 | environment:
12 | AUTORELOAD_PROGRAMS: "swoole"
13 | AUTORELOAD_ANY_FILES: 0
14 | entrypoint: php /var/www/examples/graphql/swoole.php
15 |
--------------------------------------------------------------------------------
/examples/graphql/filters.php:
--------------------------------------------------------------------------------
1 | function (array $payload, array $vars) {
7 | return $payload['room_name'] == $vars['roomName'];
8 | },
9 | ];
10 |
--------------------------------------------------------------------------------
/examples/graphql/resolvers.php:
--------------------------------------------------------------------------------
1 | function ($room) {
18 | return R::findAll('message', 'room_id = ?', [$room['id']]);
19 | }
20 | ];
21 |
22 | $queries = [
23 | 'rooms' => function () {
24 | return R::findAll('room');
25 | },
26 | 'messages' => function ($_, $args) use ($room_by_name) {
27 | $room_name = array_get_str($args, 'roomName');
28 | $room = $room_by_name($room_name);
29 | return R::find('message', 'room_id = ?', [$room['id']]);
30 | }
31 | ];
32 |
33 | $mutations = [
34 | 'start' => function ($_, $args) {
35 | $room_name = array_get_str($args, 'roomName');
36 |
37 | $room = R::dispense('room');
38 | $room['name'] = $room_name;
39 |
40 | R::store($room);
41 |
42 | return $room;
43 | },
44 | 'chat' => function ($_, $args) use ($room_by_name) {
45 | $room_name = array_get_str($args, 'roomName');
46 | $body = array_get_str($args, 'body');
47 |
48 | $room = $room_by_name($room_name);
49 |
50 | $message = R::dispense('message');
51 | $message['roomId'] = $room['id'];
52 | $message['body'] = $body;
53 | $message['timestamp'] = new DateTime();
54 |
55 | R::store($message);
56 |
57 | $message['roomName'] = $room_name;
58 | // For the inbox filter
59 | GraphQL\publish('inbox', $message);
60 | // <- Exactly what "inbox" will receive
61 | return $message;
62 | },
63 | 'close' => function ($_, array $args) use ($room_by_name): bool {
64 | $room_name = array_get_str($args, 'roomName');
65 | $room = $room_by_name($room_name);
66 |
67 | if ($room === null) {
68 | return false;
69 | }
70 |
71 | R::trash($room);
72 | return true;
73 | },
74 | 'upload' => function ($_, $args) {
75 | $file = array_get_arr($args, 'file');
76 | move_uploaded_file($file['tmp_name'], __DIR__ . "/uploads/{$file['name']}");
77 | return ['name' => $file['name']];
78 | },
79 | ];
80 |
81 | $subscriptions = [
82 | 'inbox' => function ($message) {
83 | return $message;
84 | }
85 | ];
86 |
87 | return [
88 | 'Room' => $room,
89 | 'Query' => $queries,
90 | 'Mutation' => $mutations,
91 | 'Subscription' => $subscriptions,
92 | ];
93 |
--------------------------------------------------------------------------------
/examples/graphql/sapi.php:
--------------------------------------------------------------------------------
1 | ['roles' => ['inbox']]];
30 | });
31 |
32 | listen(ON_OPERATION, function (array $subscription, $_, $context) {
33 | if ($subscription['name'] === 'inbox') {
34 | if (empty($context['user'])) {
35 | throw new UserError('Unauthenticated', 401);
36 | }
37 |
38 | if (!in_array('inbox', $context['user']['roles'])) {
39 | throw new UserError('Unauthorized', 403);
40 | }
41 | }
42 | });
43 |
44 | $resolvers = require_once __DIR__ . '/resolvers.php';
45 | $directives = require_once __DIR__ . '/directives.php';
46 | $root_value = [];
47 | $context = [];
48 |
49 | $type_defs = file_get_contents(__DIR__ . '/schema.graphql');
50 | $schema = schema($type_defs, $resolvers);
51 | directives($directives);
52 |
53 | Route\post('/graphql', fn() => init($schema, $root_value, $context));
54 | Route\get('/graphiql', puts(graphiql()));
55 |
--------------------------------------------------------------------------------
/examples/graphql/schema.graphql:
--------------------------------------------------------------------------------
1 | directive @upper on FIELD
2 |
3 | scalar Upload
4 |
5 | type Message {
6 | id: ID!
7 | roomId: Int!
8 | body: String!
9 | timestamp: String!
10 | }
11 |
12 | type Room {
13 | id: ID!
14 | name: String!
15 | messages: [Message!]!
16 | }
17 |
18 | type File {
19 | name: String!
20 | }
21 |
22 | type Query {
23 | messages(roomName: String!): [Message!]!
24 | rooms: [Room!]!
25 | }
26 |
27 | type Mutation {
28 | start(roomName: String!): Room!
29 | chat(roomName: String!, body: String!): Message!
30 | close(roomName: String!): Boolean!
31 | upload(file: Upload): File!
32 | }
33 |
34 | type Subscription {
35 | inbox(roomName: String!): Message!
36 | }
37 |
--------------------------------------------------------------------------------
/examples/graphql/swoole.php:
--------------------------------------------------------------------------------
1 | ['roles' => ['inbox']]];
32 | });
33 |
34 | GraphQL\listen(GraphQL\ON_OPERATION, function (array $subscription, $_, $context) {
35 | if ($subscription['name'] === 'inbox') {
36 | if (empty($context['user'])) {
37 | throw new UserError('Unauthenticated', 401);
38 | }
39 |
40 | if (!in_array('inbox', $context['user']['roles'])) {
41 | throw new UserError('Unauthorized', 403);
42 | }
43 | }
44 | });
45 |
46 | $resolvers = require_once __DIR__ . '/resolvers.php';
47 | $directives = require_once __DIR__ . '/directives.php';
48 | $filters = require_once __DIR__ . '/filters.php';
49 | $root_value = [];
50 | $context = [];
51 |
52 | $type_defs = file_get_contents(__DIR__ . '/schema.graphql');
53 | $schema = GraphQL\schema($type_defs, $resolvers);
54 | GraphQL\directives($directives);
55 |
56 | $manager = GraphQL\subscriptions_manager($schema, $filters, $root_value, $context);
57 |
58 | $server = Swoole\graphql_subscriptions($manager, 8001);
59 | $server->set(['upload_tmp_dir' => __DIR__ . '/uploads']);
60 |
61 | Swoole\http_server_port($server, function () use ($schema, $root_value, $context) {
62 | Route\post('/graphql', fn() => Response\json(GraphQL\execute($schema, GraphQL\request()->toArray(), $root_value, $context)));
63 | Route\get('/graphiql', fn() => Response\html(GraphQL\graphiql()));
64 | }, 8000);
65 |
66 | $server->start();
67 |
--------------------------------------------------------------------------------
/examples/graphql/uploads/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/examples/grpc/.dockerignore:
--------------------------------------------------------------------------------
1 | **
2 | !Dockerfile
3 |
--------------------------------------------------------------------------------
/examples/grpc/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:buster-slim
2 |
3 | RUN apt-get update && apt-get install -y \
4 | zlib1g-dev git build-essential autoconf libtool pkg-config apt-transport-https wget gnupg2 golang-go
5 |
6 | RUN go get -u google.golang.org/grpc \
7 | && go get -u github.com/golang/protobuf/protoc-gen-go
8 |
9 | RUN sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' \
10 | && sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' \
11 | && apt-get update && apt-get install -y dart \
12 | && /usr/lib/dart/bin/pub global activate protoc_plugin
13 |
14 | RUN git clone -b v1.25.0 https://github.com/grpc/grpc.git \
15 | && cd grpc && git submodule update --init && make
16 |
17 | RUN touch ~/.bashrc \
18 | && echo "export GOPATH=\$HOME/go" >> ~/.bashrc \
19 | && echo "export PATH=/grpc/bins/opt:/grpc/bins/opt/protobuf:\$GOPATH/bin:\$HOME/.pub-cache/bin:\$PATH" >> ~/.bashrc
20 |
--------------------------------------------------------------------------------
/examples/grpc/README.md:
--------------------------------------------------------------------------------
1 | Docker-powered `protoc` and gRPC plugins.
2 |
3 | ```powershell
4 | docker build -t proto_gen .
5 | ```
6 |
7 | ```powershell
8 | docker run --rm -v "$(pwd):/gen" -w /gen proto_gen bash -l ./proto_gen.sh
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/.gitignore:
--------------------------------------------------------------------------------
1 | .packages
2 | .dart_tool/
3 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/bin/client.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:grpc/grpc.dart';
4 | import 'package:helloworld/src/generated/helloworld.pb.dart';
5 | import 'package:helloworld/src/generated/helloworld.pbgrpc.dart';
6 |
7 | Future main(List args) async {
8 | final channel = ClientChannel('localhost',
9 | port: 9090,
10 | options:
11 | const ChannelOptions(credentials: ChannelCredentials.insecure()));
12 | final stub = GreeterClient(channel);
13 |
14 | try {
15 | final response = await stub.sayHello(HelloRequest()..name = 'Siler');
16 | print('Greeter client received: ${response.message}');
17 | } catch (e) {
18 | print('Caught error: $e');
19 | }
20 | await channel.shutdown();
21 | }
22 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/lib/src/generated/helloworld.pbenum.dart:
--------------------------------------------------------------------------------
1 | ///
2 | // Generated code. Do not modify.
3 | // source: helloworld.proto
4 | //
5 | // @dart = 2.3
6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
7 |
8 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/lib/src/generated/helloworld.pbgrpc.dart:
--------------------------------------------------------------------------------
1 | ///
2 | // Generated code. Do not modify.
3 | // source: helloworld.proto
4 | //
5 | // @dart = 2.3
6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
7 |
8 | import 'dart:async' as $async;
9 | import 'dart:core' as $core;
10 |
11 | import 'package:grpc/service_api.dart' as $grpc;
12 |
13 | import 'helloworld.pb.dart' as $0;
14 |
15 | export 'helloworld.pb.dart';
16 |
17 | class GreeterClient extends $grpc.Client {
18 | static final _$sayHello = $grpc.ClientMethod<$0.HelloRequest, $0.HelloReply>(
19 | '/helloworld.Greeter/sayHello',
20 | ($0.HelloRequest value) => value.writeToBuffer(),
21 | ($core.List<$core.int> value) => $0.HelloReply.fromBuffer(value));
22 |
23 | GreeterClient($grpc.ClientChannel channel, {$grpc.CallOptions options})
24 | : super(channel, options: options);
25 |
26 | $grpc.ResponseFuture<$0.HelloReply> sayHello($0.HelloRequest request,
27 | {$grpc.CallOptions options}) {
28 | final call = $createCall(_$sayHello, $async.Stream.fromIterable([request]),
29 | options: options);
30 | return $grpc.ResponseFuture(call);
31 | }
32 | }
33 |
34 | abstract class GreeterServiceBase extends $grpc.Service {
35 | $core.String get $name => 'helloworld.Greeter';
36 |
37 | GreeterServiceBase() {
38 | $addMethod($grpc.ServiceMethod<$0.HelloRequest, $0.HelloReply>(
39 | 'sayHello',
40 | sayHello_Pre,
41 | false,
42 | false,
43 | ($core.List<$core.int> value) => $0.HelloRequest.fromBuffer(value),
44 | ($0.HelloReply value) => value.writeToBuffer()));
45 | }
46 |
47 | $async.Future<$0.HelloReply> sayHello_Pre(
48 | $grpc.ServiceCall call, $async.Future<$0.HelloRequest> request) async {
49 | return sayHello(call, await request);
50 | }
51 |
52 | $async.Future<$0.HelloReply> sayHello(
53 | $grpc.ServiceCall call, $0.HelloRequest request);
54 | }
55 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/lib/src/generated/helloworld.pbjson.dart:
--------------------------------------------------------------------------------
1 | ///
2 | // Generated code. Do not modify.
3 | // source: helloworld.proto
4 | //
5 | // @dart = 2.3
6 | // ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
7 |
8 | const HelloRequest$json = const {
9 | '1': 'HelloRequest',
10 | '2': const [
11 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
12 | ],
13 | };
14 |
15 | const HelloReply$json = const {
16 | '1': 'HelloReply',
17 | '2': const [
18 | const {'1': 'message', '3': 1, '4': 1, '5': 9, '10': 'message'},
19 | ],
20 | };
21 |
22 |
--------------------------------------------------------------------------------
/examples/grpc/clients/dart/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: helloworld
2 | description: Dart gRPC sample client.
3 |
4 | environment:
5 | sdk: '>=2.2.0 <3.0.0'
6 |
7 | dependencies:
8 | async:
9 | grpc:
10 | protobuf:
11 |
--------------------------------------------------------------------------------
/examples/grpc/clients/go/.gitignore:
--------------------------------------------------------------------------------
1 | main.exe
2 |
--------------------------------------------------------------------------------
/examples/grpc/clients/go/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "os"
7 | "time"
8 |
9 | "google.golang.org/grpc"
10 | pb "google.golang.org/grpc/examples/helloworld/helloworld"
11 | )
12 |
13 | const (
14 | address = "localhost:9090"
15 | defaultName = "Siler"
16 | )
17 |
18 | func main() {
19 | // Set up a connection to the server.
20 | conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
21 | if err != nil {
22 | log.Fatalf("did not connect: %v", err)
23 | }
24 | defer conn.Close()
25 | c := pb.NewGreeterClient(conn)
26 |
27 | // Contact the server and print out its response.
28 | name := defaultName
29 | if len(os.Args) > 1 {
30 | name = os.Args[1]
31 | }
32 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
33 | defer cancel()
34 | r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
35 | if err != nil {
36 | log.Fatalf("could not greet: %v", err)
37 | }
38 | log.Printf("Greeting: %s", r.GetMessage())
39 | }
40 |
--------------------------------------------------------------------------------
/examples/grpc/clients/node/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/examples/grpc/clients/node/client.js:
--------------------------------------------------------------------------------
1 | const grpc = require('grpc');
2 |
3 | const messages = require('./helloworld_pb');
4 | const services = require('./helloworld_grpc_pb');
5 |
6 | const client = new services.GreeterClient('localhost:9090',
7 | grpc.credentials.createInsecure());
8 |
9 | const request = new messages.HelloRequest();
10 | request.setName('Siler');
11 |
12 | client.sayHello(request, function (err, response) {
13 | console.log('Greeting:', response.getMessage());
14 | });
15 |
--------------------------------------------------------------------------------
/examples/grpc/clients/node/helloworld_grpc_pb.js:
--------------------------------------------------------------------------------
1 | // GENERATED CODE -- DO NOT EDIT!
2 |
3 | 'use strict';
4 | var grpc = require('grpc');
5 | var helloworld_pb = require('./helloworld_pb.js');
6 |
7 | function serialize_helloworld_HelloReply(arg) {
8 | if (!(arg instanceof helloworld_pb.HelloReply)) {
9 | throw new Error('Expected argument of type helloworld.HelloReply');
10 | }
11 | return new Buffer(arg.serializeBinary());
12 | }
13 |
14 | function deserialize_helloworld_HelloReply(buffer_arg) {
15 | return helloworld_pb.HelloReply.deserializeBinary(new Uint8Array(buffer_arg));
16 | }
17 |
18 | function serialize_helloworld_HelloRequest(arg) {
19 | if (!(arg instanceof helloworld_pb.HelloRequest)) {
20 | throw new Error('Expected argument of type helloworld.HelloRequest');
21 | }
22 | return new Buffer(arg.serializeBinary());
23 | }
24 |
25 | function deserialize_helloworld_HelloRequest(buffer_arg) {
26 | return helloworld_pb.HelloRequest.deserializeBinary(new Uint8Array(buffer_arg));
27 | }
28 |
29 |
30 | var GreeterService = exports.GreeterService = {
31 | sayHello: {
32 | path: '/helloworld.Greeter/sayHello',
33 | requestStream: false,
34 | responseStream: false,
35 | requestType: helloworld_pb.HelloRequest,
36 | responseType: helloworld_pb.HelloReply,
37 | requestSerialize: serialize_helloworld_HelloRequest,
38 | requestDeserialize: deserialize_helloworld_HelloRequest,
39 | responseSerialize: serialize_helloworld_HelloReply,
40 | responseDeserialize: deserialize_helloworld_HelloReply,
41 | },
42 | };
43 |
44 | exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService);
45 |
--------------------------------------------------------------------------------
/examples/grpc/clients/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@siler/grpc-node-client",
3 | "version": "1.0.0",
4 | "description": "Siler gRPC Node.js client example.",
5 | "main": "client.js",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/leocavalcante/siler.git"
12 | },
13 | "keywords": [
14 | "siler",
15 | "grpc",
16 | "node",
17 | "protobuf"
18 | ],
19 | "author": "leocavalcante ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/leocavalcante/siler/issues"
23 | },
24 | "homepage": "https://github.com/leocavalcante/siler#readme",
25 | "dependencies": {
26 | "google-protobuf": "^3.11.2",
27 | "grpc": "^1.24.9"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/bin/client.php:
--------------------------------------------------------------------------------
1 | ChannelCredentials::createInsecure(),
17 | ]);
18 |
19 | $request = new HelloRequest();
20 | $request->setName('Siler');
21 |
22 | /** @var HelloReply|null $reply */
23 | list($reply, $status) = $greeter->sayHello($request)->wait();
24 |
25 | if ($reply === null) {
26 | echo $status->details, PHP_EOL;
27 | return;
28 | }
29 |
30 | echo $reply->getMessage(), PHP_EOL;
31 | });
32 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "siler/grpc",
3 | "description": "Siler gRPC PHP client example",
4 | "type": "project",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "leocavalcante",
9 | "email": "lc@leocavalcante.com"
10 | }
11 | ],
12 | "minimum-stability": "stable",
13 | "require": {},
14 | "autoload": {
15 | "psr-4": {
16 | "": "src/"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/src/GPBMetadata/Helloworld.php:
--------------------------------------------------------------------------------
1 | internalAddGeneratedFile(hex2bin(
21 | "0aae010a1068656c6c6f776f726c642e70726f746f120a68656c6c6f776f" .
22 | "726c64221c0a0c48656c6c6f52657175657374120c0a046e616d65180120" .
23 | "012809221d0a0a48656c6c6f5265706c79120f0a076d6573736167651801" .
24 | "2001280932490a0747726565746572123e0a0873617948656c6c6f12182e" .
25 | "68656c6c6f776f726c642e48656c6c6f526571756573741a162e68656c6c" .
26 | "6f776f726c642e48656c6c6f5265706c792200620670726f746f33"
27 | ), true);
28 |
29 | static::$is_initialized = true;
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/src/Helloworld/GreeterClient.php:
--------------------------------------------------------------------------------
1 | _simpleRequest('/helloworld.Greeter/sayHello',
33 | $argument,
34 | ['\Helloworld\HelloReply', 'decode'],
35 | $metadata, $options);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/src/Helloworld/HelloReply.php:
--------------------------------------------------------------------------------
1 | helloworld.HelloReply
13 | */
14 | class HelloReply extends Message
15 | {
16 | /**
17 | * Generated from protobuf field string message = 1;
18 | */
19 | private $message = '';
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param array $data {
25 | * Optional. Data for populating the Message object.
26 | *
27 | * @type string $message
28 | * }
29 | */
30 | public function __construct($data = NULL)
31 | {
32 | Helloworld::initOnce();
33 | parent::__construct($data);
34 | }
35 |
36 | /**
37 | * Generated from protobuf field string message = 1;
38 | * @return string
39 | */
40 | public function getMessage()
41 | {
42 | return $this->message;
43 | }
44 |
45 | /**
46 | * Generated from protobuf field string message = 1;
47 | * @param string $var
48 | * @return $this
49 | */
50 | public function setMessage($var)
51 | {
52 | GPBUtil::checkString($var, True);
53 | $this->message = $var;
54 |
55 | return $this;
56 | }
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/examples/grpc/clients/php/src/Helloworld/HelloRequest.php:
--------------------------------------------------------------------------------
1 | helloworld.HelloRequest
13 | */
14 | class HelloRequest extends Message
15 | {
16 | /**
17 | * Generated from protobuf field string name = 1;
18 | */
19 | private $name = '';
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param array $data {
25 | * Optional. Data for populating the Message object.
26 | *
27 | * @type string $name
28 | * }
29 | */
30 | public function __construct($data = NULL)
31 | {
32 | Helloworld::initOnce();
33 | parent::__construct($data);
34 | }
35 |
36 | /**
37 | * Generated from protobuf field string name = 1;
38 | * @return string
39 | */
40 | public function getName()
41 | {
42 | return $this->name;
43 | }
44 |
45 | /**
46 | * Generated from protobuf field string name = 1;
47 | * @param string $var
48 | * @return $this
49 | */
50 | public function setName($var)
51 | {
52 | GPBUtil::checkString($var, True);
53 | $this->name = $var;
54 |
55 | return $this;
56 | }
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/examples/grpc/proto_gen.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | protoc \
3 | --php_out=clients/php/src --grpc_out=clients/php/src \
4 | --plugin=protoc-gen-grpc=/grpc/bins/opt/grpc_php_plugin \
5 | -Iprotos helloworld.proto
6 |
7 | protoc \
8 | --go_out=plugins=grpc:clients/go \
9 | -Iprotos helloworld.proto
10 |
11 | protoc \
12 | --dart_out=grpc:clients/dart/lib/src/generated \
13 | -Iprotos helloworld.proto
14 |
15 | protoc \
16 | --js_out=import_style=commonjs,binary:clients/node --grpc_out=clients/node \
17 | --plugin=protoc-gen-grpc=/grpc/bins/opt/grpc_node_plugin \
18 | -Iprotos helloworld.proto
19 |
--------------------------------------------------------------------------------
/examples/grpc/protos/helloworld.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package helloworld;
4 |
5 | service Greeter {
6 | rpc sayHello (HelloRequest) returns (HelloReply) {
7 | }
8 | }
9 |
10 | message HelloRequest {
11 | string name = 1;
12 | }
13 |
14 | message HelloReply {
15 | string message = 1;
16 | }
17 |
--------------------------------------------------------------------------------
/examples/grpc/server.php:
--------------------------------------------------------------------------------
1 | setMessage('Hello ' . $request->getName());
19 | return $reply;
20 | }
21 | }
22 |
23 | $services = ['helloworld.Greeter' => new Greeter()];
24 | $server = Grpc\server($services);
25 |
26 | echo "Listening at localhost:9090\n";
27 | $server->start();
28 |
--------------------------------------------------------------------------------
/examples/hello-world/README.md:
--------------------------------------------------------------------------------
1 | # Hello World
2 | 4 ways to Hello World in [Siler](https://github.com/leocavalcante/siler). My *f*avorite is:
3 |
4 | ```php
5 | use Siler\Functional as λ;
6 | use Siler\Route;
7 |
8 | Route\get('/functional-hello', λ\puts('Hello Functional World'));
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Siler's Hello World
6 |
7 |
8 | Hello World!
9 | Name = $params['name'] ?>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/hello-world/index.php:
--------------------------------------------------------------------------------
1 | handle($req->withAttribute('name', $name));
19 | };
20 |
21 | // A PSR-15 Middleware
22 | $attach = function ($req, $handler) {
23 | return json(['hello' => $req->getAttribute('name')]);
24 | };
25 |
26 | // Stratigility default pipeline
27 | pipe(middleware($greet));
28 | pipe(middleware($attach));
29 |
30 | // A PSR-7 Server Request from Globals
31 | $req = request();
32 |
33 | // A PSR-7 Response Message after pipline marshaling
34 | $res = handle($req);
35 |
36 | // Standard API emitter (header, echo, http_status_code)
37 | sapi_emit($res);
38 |
39 | /*
40 | * > curl http://localhost:8080
41 | * < {"hello":"world"}.
42 | */
43 |
44 | /*
45 | * > curl http://localhost:8080?name=leo
46 | * < {"hello":"leo"}.
47 | */
48 |
--------------------------------------------------------------------------------
/examples/laminas/routes/index.php:
--------------------------------------------------------------------------------
1 | withAttribute('user', $user);
21 |
22 | return $handler->handle($request);
23 | };
24 |
25 | $homeHandler = function () {
26 | return Diactoros\json('welcome');
27 | };
28 |
29 | $adminHandler = function ($request) {
30 | return Diactoros\json(['user' => $request->getAttribute('user')]);
31 | };
32 |
33 | $secretHandler = function ($request) {
34 | return Diactoros\json(['user' => $request->getAttribute('user')]);
35 | };
36 |
37 | Stratigility\pipe($userMiddleware, 'auth');
38 |
39 | $request = Diactoros\request();
40 | $response = Route\matching([
41 | Route\get('/', $homeHandler, $request),
42 | Route\get('/admin', Stratigility\process($request, 'auth')($adminHandler), $request),
43 | Route\get('/secret', Stratigility\process($request, 'auth')($secretHandler), $request),
44 | Diactoros\json('not found', 404)
45 | ]);
46 |
47 | HttpHandlerRunner\sapi_emit($response);
48 |
--------------------------------------------------------------------------------
/examples/mail/.env.example:
--------------------------------------------------------------------------------
1 | SMTP_HOST=smtp.sendgrid.net
2 | SMTP_PORT=587
3 | SMTP_USERNAME=apikey
4 | SMTP_PASSWORD=
5 |
6 | FROM=from@email.com
7 | TO=to@email.com
--------------------------------------------------------------------------------
/examples/mail/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/examples/mail/swiftmailer.php:
--------------------------------------------------------------------------------
1 | 'debug']);
11 | Log\info('info', ['level' => 'info']);
12 | Log\notice('notice', ['level' => 'notice']);
13 | Log\warning('warning', ['level' => 'warning']);
14 | Log\error('error', ['level' => 'error']);
15 | Log\critical('critical', ['level' => 'critical']);
16 | Log\alert('alert', ['level' => 'alert']);
17 | Log\emergency('emergency', ['level' => 'emergency']);
18 |
--------------------------------------------------------------------------------
/examples/monolog/siler.log:
--------------------------------------------------------------------------------
1 | [2019-01-27 15:27:26] monolog_default_channel.DEBUG: Debug {"level":"debug"} []
2 | [2019-01-27 15:27:58] log.DEBUG: Debug {"level":"debug"} []
3 | [2019-01-27 15:29:36] log.DEBUG: debug {"level":"debug"} []
4 | [2019-01-27 15:29:36] log.INFO: info {"level":"info"} []
5 | [2019-01-27 15:29:36] log.NOTICE: notice {"level":"notice"} []
6 | [2019-01-27 15:29:36] log.WARNING: warning {"level":"warning"} []
7 | [2019-01-27 15:29:36] log.ERROR: error {"level":"error"} []
8 | [2019-01-27 15:29:36] log.CRITICAL: critical {"level":"critical"} []
9 | [2019-01-27 15:29:36] log.ALERT: alert {"level":"alert"} []
10 | [2019-01-27 15:29:36] log.EMERGENCY: emergency {"level":"emergency"} []
11 |
--------------------------------------------------------------------------------
/examples/psr7-diactoros/index.php:
--------------------------------------------------------------------------------
1 | getQueryParams()));
21 | });
22 |
23 | twig('examples/psr7-diactoros');
24 |
25 | // call like: /to-html?foo=bar
26 | get('/to-html', function () {
27 | // outputs a table with foo bar, see: template.twig
28 | sapi_emit(html(render('template.twig', ['query' => request()->getQueryParams()])));
29 | });
30 |
31 | // call like: /to-text?foo=bar
32 | get('/to-text', function () {
33 | // output: foo=bar
34 | sapi_emit(text(http_build_query(request()->getQueryParams())));
35 | });
36 |
--------------------------------------------------------------------------------
/examples/psr7-diactoros/template.twig:
--------------------------------------------------------------------------------
1 |
2 | {% for key, val in query %}
3 |
4 | {{ key }} |
5 | {{ val }} |
6 |
7 | {% endfor %}
8 |
9 |
--------------------------------------------------------------------------------
/examples/psr7-route/index.php:
--------------------------------------------------------------------------------
1 | getQueryParams(), 'salute', 'Olá');
20 |
21 | return Diactoros\text("{$salute} {$params['name']}");
22 | },
23 | $request
24 | ),
25 |
26 | Route\get(
27 | '/',
28 | function () {
29 | return Diactoros\text('hello world');
30 | },
31 | $request
32 | ),
33 |
34 | Diactoros\text('not found', 404)
35 | ]);
36 |
37 | HttpHandlerRunner\sapi_emit($response);
38 |
--------------------------------------------------------------------------------
/examples/route-any/index.php:
--------------------------------------------------------------------------------
1 |
6 | But can you still call it MVC, right?
7 |
--------------------------------------------------------------------------------
/examples/route-files/controllers/about.get.php:
--------------------------------------------------------------------------------
1 | name = $name;
12 | $contact->message = $message;
13 | $contact->date = new DateTime();
14 |
15 | R::store($contact);
16 |
17 | redirect('/about');
18 |
--------------------------------------------------------------------------------
/examples/route-files/controllers/index.get.php:
--------------------------------------------------------------------------------
1 | About
5 |
6 | Contacts
7 |
8 |
9 | {% for contact in contacts %}
10 | - {{ contact.name }}
11 | -
12 |
{{ contact.message }}
13 | {{ contact.date|date }}
14 |
15 | {% endfor %}
16 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/examples/route-files/views/contact.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.twig' %}
2 |
3 | {% block content %}
4 | Contact
5 |
6 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/examples/route-files/views/home.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.twig' %}
2 |
3 | {% block content %}
4 | Home
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/examples/route-files/views/layout.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Route Files
6 |
7 |
8 |
15 |
16 |
17 | {% block content %}{% endblock %}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/route-not-found/books/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Swoole Chat
6 |
7 |
8 |
9 |
10 |
14 |
15 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/swoole-chat/server.php:
--------------------------------------------------------------------------------
1 | data);
9 | };
10 |
11 | Swoole\websocket_hooks([
12 | 'open' => puts("New connection\n"),
13 | 'close' => puts("Someone leaves\n")
14 | ]);
15 |
16 | Swoole\websocket($echo)->start();
17 |
--------------------------------------------------------------------------------
/examples/swoole/README.md:
--------------------------------------------------------------------------------
1 | # Siler ❤️ Swoole
2 |
3 | * [Siler is](https://github.com/leocavalcante/siler)
4 | * [Swoole is](https://www.swoole.co.uk/)
5 |
6 | Flat files and plain-old PHP functions rocking on a production-grade, high-performance, scalable, concurrent and non-blocking HTTP server.
7 |
8 | It uses [Dwoole](https://github.com/leocavalcante/dwoole) for a cross-platform and auto-restartable development environment.
--------------------------------------------------------------------------------
/examples/swoole/api/todos.php:
--------------------------------------------------------------------------------
1 | 1,
13 | 'text' => 'foo'
14 | ],
15 | [
16 | 'id' => 2,
17 | 'text' => 'bar'
18 | ],
19 | [
20 | 'id' => 3,
21 | 'text' => 'baz'
22 | ]
23 | ];
24 |
25 | return function () use ($todos) {
26 | Swoole\json($todos);
27 | };
28 |
--------------------------------------------------------------------------------
/examples/swoole/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | swoole:
4 | container_name: siler_swoole
5 | image: leocavalcante/dwoole:dev
6 | ports:
7 | - '9501:9501'
8 | volumes:
9 | - ../../:/app
10 | environment:
11 | ENTRY_POINT_FILE: /app/examples/swoole/index.php
12 |
--------------------------------------------------------------------------------
/examples/swoole/index.php:
--------------------------------------------------------------------------------
1 | set([
21 | 'enable_static_handler' => true,
22 | 'document_root' => __DIR__ . '/public',
23 | ]);
24 | $server->start();
25 |
--------------------------------------------------------------------------------
/examples/swoole/pages/_layout.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Siler + Swoole
8 |
9 |
10 |
11 |
12 | {% block page %}{% endblock %}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/swoole/pages/home.php:
--------------------------------------------------------------------------------
1 | get['name'] ?? 'World';
10 | Swoole\emit(Twig\render('home.twig', ['name' => $name]));
11 | };
12 |
--------------------------------------------------------------------------------
/examples/swoole/pages/home.twig:
--------------------------------------------------------------------------------
1 | {% extends "_layout.twig" %}
2 |
3 | {% block page %}
4 | Hello {{ name }}
5 | {% endblock %}
--------------------------------------------------------------------------------
/examples/swoole/public/assets/scripts.js:
--------------------------------------------------------------------------------
1 | console.log('Siler ❤️ Swoole')
2 |
--------------------------------------------------------------------------------
/examples/swoole/public/assets/styles.css:
--------------------------------------------------------------------------------
1 | p {
2 | color: pink;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/twig/home.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Siler\Twig
6 |
7 |
8 | Hello {{ name }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/twig/index.php:
--------------------------------------------------------------------------------
1 |
2 |
4 | Siler's Code Style
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | tests/Swoole/Swoole.php
13 |
14 |
15 |
16 | src
17 | tests
18 | src/facades.php
19 |
20 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | src
12 |
13 |
14 | src/Grpc
15 | src/Swoole
16 | src/facades.php
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | tests/Unit
27 |
28 |
29 | tests/Integration
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/psalm.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/siler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leocavalcante/siler/a948888c52dfa5eeee9fda8ce13ded1c422fcbaa/siler.png
--------------------------------------------------------------------------------
/src/Diactoros/Diactoros.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | function init(string $path): array
19 | {
20 | $dot_env = Dotenv::createImmutable($path);
21 | return $dot_env->load();
22 | }
23 |
24 | /**
25 | * Get an environment value or fallback to the given default.
26 | *
27 | * @param string|null $key
28 | * @param string|null $default A default when the key do not exists
29 | * @return string|null|array
30 | */
31 | function env(?string $key = null, ?string $default = null)
32 | {
33 | /** @var array $_SERVER */
34 | return array_get($_SERVER, $key, $default);
35 | }
36 |
37 | /**
38 | * Returns an environment variable as an integer.
39 | *
40 | * @param string $key
41 | * @param int|null $default
42 | * @return int|null
43 | */
44 | function int_val(string $key, ?int $default = null): ?int
45 | {
46 | $val = env($key);
47 |
48 | if ($val === null) {
49 | return $default;
50 | }
51 |
52 | if (is_numeric($val)) {
53 | return (int) $val;
54 | }
55 |
56 | return $default;
57 | }
58 |
59 | /**
60 | * Returns an environment variable as an boolean.
61 | *
62 | * @param string $key
63 | * @param bool|null $default
64 | * @return bool|null
65 | */
66 | function bool_val(string $key, ?bool $default = null): ?bool
67 | {
68 | $val = env($key);
69 |
70 | if ($val === null) {
71 | return $default;
72 | }
73 |
74 | if ($val === 'false') {
75 | return false;
76 | }
77 |
78 | if ($val === '[]') {
79 | return false;
80 | }
81 |
82 | if ($val === '{}') {
83 | return false;
84 | }
85 |
86 | return (bool) $val;
87 | }
88 |
89 | /**
90 | * Checks for the presence of an environment variable.
91 | *
92 | * @param string $key
93 | * @return true
94 | */
95 | function requires(string $key): bool
96 | {
97 | if (\array_key_exists($key, $_ENV)) {
98 | return true;
99 | }
100 |
101 | throw new UnexpectedValueException("$key is not set in the environment variables");
102 | }
103 |
--------------------------------------------------------------------------------
/src/Encoder/Json.php:
--------------------------------------------------------------------------------
1 |
18 | * @return SplFileInfo[]
19 | */
20 | function recursively_iterated_directory(string $dirname, string $regex = '/.*/', int $mode = RegexIterator::MATCH): array
21 | {
22 | $dir_iterator = new RecursiveDirectoryIterator($dirname);
23 | $iterator = new RecursiveIteratorIterator($dir_iterator);
24 | $regexp_iterator = new RegexIterator($iterator, $regex, $mode);
25 |
26 | $list = [];
27 |
28 | /** @var SplFileInfo $spl_file_info */
29 | foreach ($regexp_iterator as $spl_file_info) {
30 | $list[] = $spl_file_info;
31 | }
32 |
33 | return $list;
34 | }
35 |
36 | /**
37 | * Alias for recursively_iterated_directory().
38 | *
39 | * @param string $dirname
40 | * @param string $regex
41 | * @param RegexIterator::MATCH|RegexIterator::GET_MATCH|RegexIterator::ALL_MATCHES|RegexIterator::SPLIT|RegexIterator::REPLACE $mode
42 | * @psalm-return list
43 | * @return SplFileInfo[]
44 | */
45 | function recur_iter_dir(string $dirname, string $regex = '/.*/', int $mode = RegexIterator::MATCH): array
46 | {
47 | return recursively_iterated_directory($dirname, $regex, $mode);
48 | }
49 |
50 | /**
51 | * Loads and concatenates file contents.
52 | *
53 | * @param string[]|SplFileInfo[] $files
54 | * @param string $separator
55 | *
56 | * @return string
57 | */
58 | function concat_files(array $files, string $separator = "\n"): string
59 | {
60 | $files = array_filter(
61 | $files,
62 | /**
63 | * @param string|SplFileInfo $file
64 | * @return bool
65 | */
66 | static function ($file): bool {
67 | if ($file instanceof SplFileInfo) {
68 | $file->isFile();
69 | }
70 |
71 | return is_file((string)$file);
72 | }
73 | );
74 |
75 | $files = array_map(
76 | /**
77 | * @param string|SplFileInfo $file
78 | */
79 | static function ($file): string {
80 | if ($file instanceof SplFileInfo) {
81 | return file_get_contents($file->getPathname());
82 | }
83 |
84 | return file_get_contents($file);
85 | },
86 | $files
87 | );
88 |
89 | $contents = array_reduce($files, concat($separator), '');
90 |
91 | return trim($contents);
92 | }
93 |
94 | /**
95 | * @param array ...$segments
96 | *
97 | * @return string
98 | */
99 | function join_dir(...$segments): string
100 | {
101 | return implode(DIRECTORY_SEPARATOR, $segments);
102 | }
103 |
--------------------------------------------------------------------------------
/src/Functional/Monad/Identity.php:
--------------------------------------------------------------------------------
1 | value = $value;
23 | }
24 |
25 | /**
26 | * @param callable|null $function
27 | * @return self|mixed
28 | * @psalm-param callable(T):(T|null)|null $function
29 | * @psalm-return self|T
30 | */
31 | public function __invoke(callable $function = null)
32 | {
33 | if ($function === null) {
34 | return $this->value;
35 | }
36 |
37 | return new self($function($this->value));
38 | }
39 |
40 | /**
41 | * @return mixed
42 | * @psalm-return T
43 | */
44 | public function return()
45 | {
46 | return $this->value;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Functional/Monad/Maybe.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class Maybe extends Identity
10 | {
11 | /**
12 | * @param callable|null $function
13 | * @return self|mixed
14 | * @psalm-param callable(T):(T|null)|null $function
15 | * @psalm-return self|T
16 | */
17 | public function __invoke(callable $function = null)
18 | {
19 | if ($function === null) {
20 | return $this->return();
21 | }
22 |
23 | return $this->bind($function);
24 | }
25 |
26 | /**
27 | * @param callable(T):(T|null) $function
28 | * @return self
29 | */
30 | public function bind(callable $function): self
31 | {
32 | if ($this->value === null) {
33 | return new self(null);
34 | }
35 |
36 | return new self($function($this->value));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Functional/Monad/Monad.php:
--------------------------------------------------------------------------------
1 | fields = $values['value'];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/GraphQL/Annotation/Directive.php:
--------------------------------------------------------------------------------
1 | type = $type;
52 | return $this;
53 | }
54 |
55 | public function name(string $name): self
56 | {
57 | $this->name = $name;
58 | return $this;
59 | }
60 |
61 | public function description(string $description): self
62 | {
63 | $this->description = $description;
64 | return $this;
65 | }
66 |
67 | public function listOf(string $listOf): self
68 | {
69 | $this->listOf = $listOf;
70 | return $this;
71 | }
72 |
73 | public function nullable(bool $nullable): self
74 | {
75 | $this->nullable = $nullable;
76 | return $this;
77 | }
78 |
79 | public function nullableList(bool $nullableList): self
80 | {
81 | $this->nullableList = $nullableList;
82 | return $this;
83 | }
84 |
85 | public function resolve(?callable $resolve): self
86 | {
87 | $this->resolve = $resolve;
88 | return $this;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/GraphQL/Annotation/InputType.php:
--------------------------------------------------------------------------------
1 | format((string) static::FORMAT);
28 | }
29 |
30 | throw new Error('Don\'t know how to serialize non-DateTime');
31 | }
32 |
33 | /**
34 | * @param Node $valueNode
35 | * @param array|null $variables
36 | * @return mixed
37 | * @throws Error
38 | */
39 | public function parseLiteral(Node $valueNode, ?array $variables = null)
40 | {
41 | if ($valueNode instanceof StringValueNode) {
42 | return $this->parseValue($valueNode->value);
43 | }
44 |
45 | throw new Error(sprintf('Unable to parse non string literal as %s', $this->name));
46 | }
47 |
48 | /**
49 | * @param mixed $value
50 | * @return mixed
51 | * @throws Error
52 | */
53 | public function parseValue($value)
54 | {
55 | $date_time = false;
56 |
57 | if (\is_string($value)) {
58 | $date_time = DateTime::createFromFormat((string) static::FORMAT, $value);
59 | }
60 |
61 | if ($date_time === false) {
62 | throw new Error(sprintf("Error parsing $value as %s. Is it in %s format?", $this->name, (string)static::FORMAT));
63 | }
64 |
65 | return $date_time;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/GraphQL/DateTimeScalar.php:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
36 |
37 |
44 |
48 |
52 |
53 |
58 |
59 |
60 |
61 |
62 | Loading...
63 |
67 |
95 |
96 |
97 | HTML;
98 | }
99 |
--------------------------------------------------------------------------------
/src/GraphQL/Request.php:
--------------------------------------------------------------------------------
1 | query = $query;
25 | $this->variables = $variables;
26 | $this->operationName = $operationName;
27 | }
28 |
29 | /**
30 | * @return array
31 | * @internal For legacy purposes, may be removed.
32 | */
33 | public function toArray(): array
34 | {
35 | return [
36 | 'query' => $this->query,
37 | 'variables' => $this->variables,
38 | 'operationName' => $this->operationName,
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/GraphQL/SubscriptionsConnection.php:
--------------------------------------------------------------------------------
1 | serializeToString());
20 | }
21 |
22 | /**
23 | * @param string $data
24 | * @return string
25 | */
26 | public static function pack(string $data): string
27 | {
28 | return pack('CN', 0, \strlen($data)) . $data;
29 | }
30 |
31 | /**
32 | * @param \ReflectionClass $message_class
33 | * @param string $value
34 | * @return Message|null
35 | * @throws \Exception
36 | */
37 | public static function deserialize(\ReflectionClass $message_class, string $value): ?Message
38 | {
39 | if (empty($value)) {
40 | return null;
41 | }
42 |
43 | $value = self::unpack($value);
44 |
45 | /** @var Message $object */
46 | $object = $message_class->newInstance();
47 | $object->mergeFromString($value);
48 |
49 | return $object;
50 | }
51 |
52 | /**
53 | * @param string $data
54 | * @return string
55 | */
56 | public static function unpack(string $data): string
57 | {
58 | // it's the way to verify the package length
59 | // 1 + 4 + data
60 | // $len = unpack('N', substr($data, 1, 4))[1];
61 | // assert(strlen($data) - 5 === $len);
62 | return substr($data, 5);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/HttpHandlerRunner/HttpHandlerRunner.php:
--------------------------------------------------------------------------------
1 | emit($response);
22 | }
23 |
--------------------------------------------------------------------------------
/src/Mail/SwiftMailer.php:
--------------------------------------------------------------------------------
1 | send($message);
37 | }
38 |
39 | /**
40 | * Sugar to create a new SwiftMailer Message.
41 | *
42 | * @param string $subject
43 | * @param array $from
44 | * @param array $to
45 | * @param string $body
46 | * @param string $contentType
47 | *
48 | * @return Swift_Message
49 | */
50 | function message(
51 | string $subject,
52 | array $from,
53 | array $to,
54 | string $body,
55 | string $contentType = 'text/plain'
56 | ): Swift_Message {
57 | return (new Swift_Message())
58 | ->setSubject($subject)
59 | ->setFrom($from)
60 | ->setTo($to)
61 | ->setBody($body, $contentType);
62 | }
63 |
64 | /**
65 | * Sugar to create a new SwiftMailer SMTP transport.
66 | *
67 | * @param string $host
68 | * @param int $port
69 | * @param string|null $username
70 | * @param string|null $password
71 | *
72 | * @return Swift_SmtpTransport
73 | */
74 | function smtp(string $host, int $port, ?string $username = null, ?string $password = null): Swift_SmtpTransport
75 | {
76 | $transport = new Swift_SmtpTransport($host, $port);
77 |
78 | if ($username !== null) {
79 | $transport->setUsername($username);
80 | }
81 |
82 | if ($password !== null) {
83 | $transport->setPassword($password);
84 | }
85 |
86 | return $transport;
87 | }
88 |
89 | /**
90 | * Setup a Swift Mailer in the Siler Container.
91 | *
92 | * @param Swift_Transport $transport
93 | *
94 | * @return Swift_Mailer
95 | */
96 | function mailer(Swift_Transport $transport): Swift_Mailer
97 | {
98 | $mailer = new Swift_Mailer($transport);
99 | Container\set(SWIFT_MAILER, $mailer);
100 |
101 | return $mailer;
102 | }
103 |
--------------------------------------------------------------------------------
/src/Monolog/Loggers.php:
--------------------------------------------------------------------------------
1 | */
18 | public static $predicates = [];
19 |
20 | /**
21 | * Returns the Logger identified by the channel.
22 | *
23 | * @param string $channel The log channel.
24 | *
25 | * @return Logger
26 | */
27 | public static function getLogger(string $channel): Logger
28 | {
29 | if (empty(static::$loggers[$channel])) {
30 | static::$loggers[$channel] = new Logger($channel);
31 | }
32 |
33 | return static::$loggers[$channel];
34 | }
35 |
36 | /**
37 | * @param int $level
38 | * @param bool|callable():bool $predicate
39 | */
40 | public static function logIf(int $level, $predicate): void
41 | {
42 | static::$predicates[$level] = $predicate;
43 | }
44 |
45 | public static function gate(int $level): bool
46 | {
47 | if (!array_key_exists($level, self::$predicates)) {
48 | return true;
49 | }
50 |
51 | $pred = self::$predicates[$level];
52 |
53 | if (is_callable($pred)) {
54 | return $pred();
55 | }
56 |
57 | return $pred;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Prelude/Arr.php:
--------------------------------------------------------------------------------
1 | 1) {
20 | $key = array_shift($keys);
21 |
22 | // If the key doesn't exist at this depth, we will just create an empty array
23 | // to hold the next value, allowing us to create the arrays to hold final
24 | // values at the correct depth. Then we'll keep digging into the array.
25 | if (!isset($array[$key]) || !is_array($array[$key])) {
26 | $array[$key] = [];
27 | }
28 |
29 | $array = &$array[$key];
30 | }
31 |
32 | /** @psalm-suppress MixedAssignment */
33 | $array[array_shift($keys)] = $value;
34 | return $array;
35 | }
36 |
37 | /**
38 | * Creates an array of associative arrays using the first element as keys.
39 | *
40 | * @param array $arr
41 | * @return array
42 | */
43 | function assoc(array $arr): array
44 | {
45 | /** @var array $head */
46 | $head = $arr[0];
47 |
48 | return array_map(static function (array $row) use ($head): array {
49 | return array_combine($head, $row);
50 | }, array_slice($arr, 1));
51 | }
52 |
--------------------------------------------------------------------------------
/src/Prelude/Dispatcher.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | private $listeners = [];
18 |
19 | /**
20 | * Listen for an event using its class name as pattern.
21 | *
22 | * @template E
23 | * @param string $eventClass
24 | * @psalm-param class-string $eventClass
25 | * @param callable(E):void $callback
26 | * @return $this
27 | */
28 | public function listen(string $eventClass, callable $callback): self
29 | {
30 | if (empty($this->listeners[$eventClass])) {
31 | $this->listeners[$eventClass] = [];
32 | }
33 |
34 | $this->listeners[$eventClass][] = $callback;
35 | return $this;
36 | }
37 |
38 | /**
39 | * Dispatch the given event.
40 | *
41 | * @param object $event
42 | * @return object
43 | */
44 | public function dispatch(object $event): object
45 | {
46 | $event_class = get_class($event);
47 |
48 | foreach ($this->listeners[$event_class] as $callback) {
49 | $callback($event);
50 | }
51 |
52 | return $event;
53 | }
54 |
55 | /**
56 | * Returns the listeners for the given event.
57 | *
58 | * @param object $event
59 | * @return iterable
60 | */
61 | public function getListenersForEvent(object $event): iterable
62 | {
63 | $event_class = get_class($event);
64 | return $this->listeners[$event_class];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Prelude/Enum.php:
--------------------------------------------------------------------------------
1 | */
15 | private static $constsMemo = [];
16 |
17 | /**
18 | * @param mixed $value
19 | * @return static
20 | * @throws ReflectionException
21 | */
22 | public static function of($value): self
23 | {
24 | /** @psalm-suppress UnsafeInstantiation */
25 | return new static($value);
26 | }
27 |
28 | /**
29 | * @return array
30 | * @throws ReflectionException
31 | */
32 | protected static function consts(): array
33 | {
34 | $class_name = static::class;
35 |
36 | if (!\array_key_exists($class_name, self::$constsMemo)) {
37 | $reflection = new ReflectionClass($class_name);
38 | self::$constsMemo[$class_name] = $reflection->getConstants();
39 | }
40 |
41 | return self::$constsMemo[$class_name];
42 | }
43 |
44 | /**
45 | * @param mixed $value
46 | * @return bool
47 | * @throws ReflectionException
48 | */
49 | protected static function valid($value): bool
50 | {
51 | return \in_array($value, array_values(static::consts()), true);
52 | }
53 |
54 | /** @var mixed */
55 | private $value;
56 |
57 | /**
58 | * @param mixed $value
59 | * @throws ReflectionException
60 | */
61 | public function __construct($value)
62 | {
63 | if (!static::valid($value)) {
64 | $class_name = static::class;
65 | throw new UnexpectedValueException("Invalid value ($value) for enum ($class_name)");
66 | }
67 |
68 | $this->value = $value;
69 | }
70 |
71 | /**
72 | * @return mixed
73 | */
74 | public function valueOf()
75 | {
76 | return $this->value;
77 | }
78 |
79 | /**
80 | * @param Enum $other
81 | * @return bool
82 | */
83 | public function sameAs(Enum $other): bool
84 | {
85 | return $this->value === $other->value;
86 | }
87 |
88 | /**
89 | * @return string
90 | */
91 | public function __toString(): string
92 | {
93 | return (string) $this->value;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Prelude/FromArray.php:
--------------------------------------------------------------------------------
1 | getProperties() as $prop) {
24 | $prop_name = $prop->getName();
25 | $key = snake_case($prop_name);
26 |
27 | if (array_key_exists($prop_name, $arr)) {
28 | $obj->{$prop_name} = $arr[$prop_name];
29 | } elseif (array_key_exists($key, $arr)) {
30 | $obj->{$prop_name} = $arr[$key];
31 | }
32 | }
33 |
34 | return $obj;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Prelude/FromArrayInterface.php:
--------------------------------------------------------------------------------
1 | $source
14 | * @return mixed
15 | * @psalm-return T
16 | */
17 | function patch($target, array $source)
18 | {
19 | /** @psalm-suppress MixedAssignment */
20 | foreach ($source as $key => $value) {
21 | $prop = lcfirst(camel_case($key));
22 |
23 | if (property_exists($target, $prop)) {
24 | $target->{$prop} = $value;
25 | }
26 | }
27 |
28 | return $target;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Prelude/Patch.php:
--------------------------------------------------------------------------------
1 | slugify($input, $opts);
29 | }
30 |
31 | /**
32 | * Breaks a string into lines.
33 | *
34 | * @param string $input
35 | *
36 | * @return array
37 | */
38 | function lines(string $input): array
39 | {
40 | return array_map(function (string $row): string {
41 | return trim($row);
42 | }, preg_split('/\n/', $input));
43 | }
44 |
45 | /**
46 | * Checks if a string starts with another string.
47 | *
48 | * @param string $haystack
49 | * @param string $needle
50 | * @return bool
51 | */
52 | function starts_with(string $haystack, string $needle): bool
53 | {
54 | return strpos($haystack, $needle) === 0;
55 | }
56 |
57 | /**
58 | * Checks if a string ends by another string.
59 | *
60 | * @param string $haystack
61 | * @param string $needle
62 | * @return bool
63 | */
64 | function ends_with(string $haystack, string $needle): bool
65 | {
66 | return substr_compare($haystack, $needle, -strlen($needle)) === 0;
67 | }
68 |
69 | /**
70 | * Checks if a string contains another string.
71 | *
72 | * @param string $haystack
73 | * @param string $needle
74 | * @return bool
75 | */
76 | function contains(string $haystack, string $needle): bool
77 | {
78 | return strpos($haystack, $needle) !== false;
79 | }
80 |
81 | /**
82 | * Converts a CamelCase string to snake_case.
83 | *
84 | * @param string $input
85 | * @return string
86 | */
87 | function snake_case(string $input): string
88 | {
89 | return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $input));
90 | }
91 |
92 | /**
93 | * Converts a snake_case string to CamelCase.
94 | *
95 | * @param string $input
96 | * @return string
97 | */
98 | function camel_case(string $input): string
99 | {
100 | return str_replace('_', '', ucwords($input, '_'));
101 | }
102 |
103 | /**
104 | * Multi-byte alternative for ucfirst().
105 | *
106 | * @param string $input
107 | * @return string
108 | */
109 | function mb_ucfirst(string $input): string
110 | {
111 | return mb_strtoupper(mb_substr($input, 0, 1)) . mb_substr($input, 1);
112 | }
113 |
114 | /**
115 | * Multi-byte alternative for lcfirst().
116 | *
117 | * @param string $input
118 | * @return string
119 | */
120 | function mb_lcfirst(string $input): string
121 | {
122 | return mb_strtolower(mb_substr($input, 0, 1)) . mb_substr($input, 1);
123 | }
124 |
--------------------------------------------------------------------------------
/src/Prelude/ToArray.php:
--------------------------------------------------------------------------------
1 | $value) {
23 | if ($convertCase) {
24 | $key = snake_case($key);
25 | }
26 |
27 | $arr[$key] = $value;
28 | }
29 |
30 | return $arr;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Prelude/ToArrayInterface.php:
--------------------------------------------------------------------------------
1 | values = $values;
43 | }
44 |
45 | /**
46 | * @override
47 | *
48 | * @param string|int $offset
49 | *
50 | * @return bool
51 | */
52 | public function offsetExists($offset)
53 | {
54 | return isset($this->values[$offset]);
55 | }
56 |
57 | /**
58 | * @override
59 | *
60 | * @param string|int $offset
61 | *
62 | * @return mixed
63 | * @throws OutOfBoundsException
64 | *
65 | */
66 | public function offsetGet($offset)
67 | {
68 | if (isset($this->values[$offset])) {
69 | return $this->values[$offset];
70 | }
71 |
72 | throw new OutOfRangeException('Invalid tuple position');
73 | }
74 |
75 | /**
76 | * @override
77 | *
78 | * @param mixed $offset
79 | * @param mixed $value
80 | *
81 | * @throws RuntimeException
82 | */
83 | public function offsetSet($offset, $value)
84 | {
85 | throw new RuntimeException('Tuples are immutable!');
86 | }
87 |
88 | /**
89 | * @override
90 | *
91 | * @param mixed $offset
92 | *
93 | * @throws RuntimeException
94 | */
95 | public function offsetUnset($offset)
96 | {
97 | throw new RuntimeException('Tuples are immutable!');
98 | }
99 |
100 | /**
101 | * @return int
102 | */
103 | public function count()
104 | {
105 | return count($this->values);
106 | }
107 |
108 | /**
109 | * @return array
110 | */
111 | public function values(): array
112 | {
113 | return $this->values;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Ratchet/GraphQLSubscriptionsConnection.php:
--------------------------------------------------------------------------------
1 | conn = $conn;
29 | $this->key = $key;
30 | }
31 |
32 | /**
33 | * @param string $data
34 | */
35 | public function send(string $data): void
36 | {
37 | $this->conn->send($data);
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public function key(): string
44 | {
45 | return $this->key;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Ratchet/GraphQLSubscriptionsServer.php:
--------------------------------------------------------------------------------
1 | */
24 | private $connections;
25 |
26 | /**
27 | * SubscriptionsServer constructor.
28 | *
29 | * @param SubscriptionsManager $manager
30 | */
31 | public function __construct(SubscriptionsManager $manager)
32 | {
33 | $this->manager = $manager;
34 | /** @var SplObjectStorage connections */
35 | $this->connections = new SplObjectStorage();
36 | }
37 |
38 | /**
39 | * @override
40 | * @param ConnectionInterface $conn
41 | * @return void
42 | */
43 | public function onOpen(ConnectionInterface $conn): void
44 | {
45 | $this->connections->offsetSet($conn, new GraphQLSubscriptionsConnection($conn, uniqid()));
46 | }
47 |
48 | /**
49 | * @override
50 | * @param ConnectionInterface $from
51 | * @param string $msg
52 | * @return void
53 | * @throws Exception
54 | */
55 | public function onMessage(ConnectionInterface $from, $msg): void
56 | {
57 | $from = $this->connections->offsetGet($from);
58 | /** @var array $msg */
59 | $msg = Json\decode(strval($msg));
60 | $this->manager->handle($from, $msg);
61 | }
62 |
63 | /**
64 | * @override
65 | * @param ConnectionInterface $conn *
66 | * @return void
67 | */
68 | public function onClose(ConnectionInterface $conn): void
69 | {
70 | $this->connections->offsetUnset($conn);
71 | }
72 |
73 | /**
74 | * @override
75 | * @param ConnectionInterface $conn
76 | * @param Exception $e
77 | * @return void
78 | */
79 | public function onError(ConnectionInterface $conn, Exception $e): void
80 | {
81 | }
82 |
83 | /**
84 | * @return array
85 | */
86 | public function getSubProtocols(): array
87 | {
88 | return [WEBSOCKET_SUB_PROTOCOL];
89 | }
90 |
91 | /**
92 | * @param ConnectionInterface $conn
93 | * @return SubscriptionsConnection
94 | */
95 | public function getSubscriptionsConnection(ConnectionInterface $conn): SubscriptionsConnection
96 | {
97 | return $this->connections->offsetGet($conn);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Ratchet/Ratchet.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
29 | $this->pathParams = $pathParams;
30 | }
31 |
32 | /**
33 | * {@inheritdoc}.
34 | */
35 | public function handle(ServerRequestInterface $request): ResponseInterface
36 | {
37 | $handler = $this->handler;
38 | return $handler($request, $this->pathParams);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Swoole/GraphQLSubscriptionsConnection.php:
--------------------------------------------------------------------------------
1 | fd = $fd;
34 | }
35 |
36 | /**
37 | * @param string $data
38 | */
39 | public function send(string $data): void
40 | {
41 | push($data, $this->fd);
42 | }
43 |
44 | /**
45 | * @return int
46 | */
47 | public function key(): int
48 | {
49 | return $this->fd;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Twig/Twig.php:
--------------------------------------------------------------------------------
1 | $debug,
31 | 'cache' => $templatesCachePath
32 | ]
33 | );
34 |
35 | Container\set('twig', $twig);
36 |
37 | return $twig;
38 | }
39 |
40 | /**
41 | * Renders the given template within the given data.
42 | *
43 | * @param string $name The template name in the templates path
44 | * @param array $data The array of data to used within the template
45 | *
46 | * @return string
47 | *
48 | * @throws LoaderError
49 | * @throws RuntimeError
50 | * @throws SyntaxError
51 | */
52 | function render(string $name, array $data = []): string
53 | {
54 | /** @var Environment|null $twig */
55 | $twig = Container\get('twig');
56 |
57 | if ($twig === null) {
58 | throw new UnexpectedValueException('Twig should be initialized first');
59 | }
60 |
61 | return $twig->render($name, $data);
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Integration/ComposabilityTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($isDivisibleBy3(3));
21 | $this->assertFalse($isDivisibleBy3(2));
22 | }
23 |
24 | public function testAny()
25 | {
26 | $isDivisibleBy3 = compose([equal(0), mod(3)]);
27 | $isDivisibleBy5 = compose([equal(0), mod(5)]);
28 | $isDivisibleBy3Or5 = any([$isDivisibleBy3, $isDivisibleBy5]);
29 |
30 | $this->assertFalse($isDivisibleBy3Or5(2));
31 | $this->assertTrue($isDivisibleBy3Or5(3));
32 | $this->assertFalse($isDivisibleBy3Or5(4));
33 | $this->assertTrue($isDivisibleBy3Or5(5));
34 | }
35 |
36 | public function testAll()
37 | {
38 | $isDivisibleBy3 = compose([equal(0), mod(3)]);
39 | $isDivisibleBy5 = compose([equal(0), mod(5)]);
40 | $isDivisibleBy3And5 = all([$isDivisibleBy3, $isDivisibleBy5]);
41 |
42 | $this->assertFalse($isDivisibleBy3And5(2));
43 | $this->assertFalse($isDivisibleBy3And5(3));
44 | $this->assertFalse($isDivisibleBy3And5(4));
45 | $this->assertFalse($isDivisibleBy3And5(5));
46 | $this->assertTrue($isDivisibleBy3And5(15));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Integration/FizzbuzzTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Integration/RoutingTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString('Hello World');
24 | Route\get('/', F\puts('Hello World'));
25 | }
26 |
27 | public function testStaticPages()
28 | {
29 | $this->expectOutputString('Hello World
');
30 |
31 | Twig\init(__DIR__ . '/../fixtures');
32 |
33 | Route\get('/', F\puts(Twig\render('static.twig')));
34 | }
35 |
36 | public function testDynamicPages()
37 | {
38 | $this->expectOutputString('hello-world
');
39 |
40 | Twig\init(__DIR__ . '/../fixtures');
41 |
42 | $_SERVER['REQUEST_URI'] = '/hello-world';
43 |
44 | Route\get('/{foo}', function ($params) {
45 | echo Twig\render('template.twig', $params);
46 | });
47 | }
48 |
49 | public function testFiles()
50 | {
51 | $this->expectOutputString('index.get');
52 |
53 | $_SERVER['REQUEST_URI'] = '/';
54 |
55 | Route\files('tests/fixtures/route_files/');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Config/ConfigTest.php:
--------------------------------------------------------------------------------
1 | 'bar']);
21 | $processors = [$token];
22 |
23 | $readers = ['ext' => new Json()];
24 |
25 | processors($processors);
26 | readers($readers);
27 |
28 | $this->config = load(__DIR__ . '/../../fixtures/config');
29 | }
30 |
31 | public function testLoad(): void
32 | {
33 | self::assertSame($this->config, Container\get(CONFIG));
34 | }
35 |
36 | public function testConfig(): void
37 | {
38 | self::assertSame('value', config('test.config'));
39 | self::assertSame('another_value', config('test.another_config'));
40 | }
41 |
42 | public function testConfigDefault(): void
43 | {
44 | self::assertNull(config('invalid_key'));
45 | self::assertSame(123, config('invalid.key', 123));
46 | }
47 |
48 | public function testHas(): void
49 | {
50 | self::assertTrue(has('test.config'));
51 | self::assertFalse(has('invalid.key'));
52 | }
53 |
54 | public function testAll(): void
55 | {
56 | self::assertSame([
57 | 'test' => [
58 | 'json' => 'custom',
59 | 'another_config' => 'another_value',
60 | 'token_processing' => 'bar',
61 | 'config' => 'value'
62 | ]
63 | ], all());
64 | }
65 |
66 | public function testNoConfig(): void
67 | {
68 | Container\set(CONFIG, null);
69 | self::assertNull(config('test.config'));
70 | self::assertFalse(has('test.config'));
71 | self::assertNull(all());
72 | }
73 |
74 | public function testYaml(): void
75 | {
76 | yaml(always(['yaml', 'reader', 'test']));
77 | load(__DIR__ . '/../../fixtures/config/yaml');
78 | self::assertSame(['yaml', 'reader', 'test'], all());
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Unit/Diactoros/DiactorosTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(RequestInterface::class, Diactoros\request());
22 | }
23 |
24 | public function testResponse()
25 | {
26 | $this->assertInstanceOf(ResponseInterface::class, Diactoros\response());
27 | }
28 |
29 | public function testHtml()
30 | {
31 | $this->assertInstanceOf(HtmlResponse::class, Diactoros\html('test'));
32 | }
33 |
34 | public function testJson()
35 | {
36 | $this->assertInstanceOf(JsonResponse::class, Diactoros\json('test'));
37 | }
38 |
39 | public function testText()
40 | {
41 | $this->assertInstanceOf(TextResponse::class, Diactoros\text('test'));
42 | }
43 |
44 | public function testNone()
45 | {
46 | $this->assertInstanceOf(EmptyResponse::class, Diactoros\none());
47 | }
48 |
49 | public function testRedirect()
50 | {
51 | $this->assertInstanceOf(RedirectResponse::class, Diactoros\redirect('test'));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Unit/Dotenv/DotenvTest.php:
--------------------------------------------------------------------------------
1 | assertCount(11, $entries);
22 | $this->assertArrayHasKey('FOO', $entries);
23 | $this->assertSame('bar', $entries['FOO']);
24 | $this->assertSame($_SERVER, env());
25 | $this->assertSame('bar', env('FOO'));
26 | $this->assertSame('baz', env('BAR', 'baz'));
27 | $this->assertNull(env('BAR'));
28 | }
29 |
30 | public function testEvnInt()
31 | {
32 | init(__DIR__ . '/../../fixtures');
33 |
34 | $this->assertSame(8, int_val('ENV_INT'));
35 | $this->assertNull(int_val('ENV_INT_NULL'));
36 | $this->assertSame(0, int_val('ENV_INT_DEFAULT', 0));
37 | $this->assertNull(int_val('ENV_INT_NOT_NUMERIC'));
38 | }
39 |
40 | public function testEnvBool()
41 | {
42 | init(__DIR__ . '/../../fixtures');
43 |
44 | $this->assertNull(bool_val('ENV_BOOL_NULL'));
45 |
46 | foreach (range(0, 5) as $index) {
47 | $this->assertFalse(bool_val("ENV_BOOL_FV$index"), "$index isn't falsy");
48 | }
49 |
50 | $this->assertTrue(bool_val('ENV_BOOL_TV'));
51 | }
52 |
53 | public function testEnvRequires()
54 | {
55 | $this->expectException(UnexpectedValueException::class);
56 | $this->expectExceptionMessage('BAR is not set in the environment variables');
57 |
58 | init(__DIR__ . '/../../fixtures');
59 |
60 | requires('FOO');
61 | requires('BAR');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Unit/Encoder/JsonTest.php:
--------------------------------------------------------------------------------
1 | assertSame('{"foo":"bar","baz":8}', Json\encode(['foo' => 'bar', 'baz' => 8]));
16 | }
17 |
18 | public function testDecode()
19 | {
20 | $this->assertSame(['foo' => 'bar', 'baz' => 8], Json\decode('{"foo":"bar","baz":8}'));
21 | }
22 |
23 | public function testDecodeException()
24 | {
25 | // TODO: Use JsonException when PHP 7.2 support drops
26 | $this->expectException(Exception::class);
27 | Json\decode('invalid json');
28 | }
29 |
30 | public function testEncodeException()
31 | {
32 | // TODO: Use JsonException when PHP 7.2 support drops
33 | $this->expectException(Exception::class);
34 | Json\encode(fopen('php://input', 'r'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Env/EnvTest.php:
--------------------------------------------------------------------------------
1 | assertSame('bar', env_var('FOO'));
18 | }
19 |
20 | public function testEnvVarDefault()
21 | {
22 | $this->assertSame('foo', env_var('BAR', 'foo'));
23 | }
24 |
25 | public function testEnvVarThrows()
26 | {
27 | $this->expectException(UnexpectedValueException::class);
28 | env_var('BAR');
29 | }
30 |
31 | public function testEnvInt()
32 | {
33 | $_ENV['FOO'] = '42';
34 | $this->assertSame(42, env_int('FOO'));
35 | }
36 |
37 | public function testEnvIntDefault()
38 | {
39 | $this->assertSame(43, env_int('BAR', 43));
40 | }
41 |
42 | public function testEnvBool()
43 | {
44 | $_ENV['FOO'] = 'true';
45 | $this->assertSame(true, env_bool('FOO'));
46 |
47 | $_ENV['FOO'] = 'false';
48 | $this->assertSame(false, env_bool('FOO'));
49 | }
50 |
51 | public function testEnvHas()
52 | {
53 | $_ENV['FOO'] = 'bar';
54 | $this->assertTrue(env_has('FOO'));
55 | $this->assertFalse(env_has('BAR'));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/File/FileTest.php:
--------------------------------------------------------------------------------
1 | getPathname();
21 | }, $dir);
22 |
23 | $this->assertContains(join_dir($basedir, 'fixtures', 'foo.php'), $dir);
24 | }
25 |
26 | public function testRecursivelyIteratedDirectoryWithPattern()
27 | {
28 | $basedir = dirname(__DIR__, 2);
29 | $dir = recur_iter_dir(join_dir($basedir, 'fixtures'), '/\.txt$/');
30 |
31 | $dir = array_map(function (SplFileInfo $info): string {
32 | return $info->getPathname();
33 | }, $dir);
34 |
35 | $this->assertContains(join_dir($basedir, 'fixtures', 'php_input.txt'), $dir);
36 | }
37 |
38 | public function testConcatFilesWithRecur()
39 | {
40 | $basedir = dirname(__DIR__, 2);
41 | $result = concat_files(recur_iter_dir(join_dir($basedir, 'fixtures', 'concat')), '');
42 |
43 | // Note: file order is arbitrary
44 | $this->assertTrue($result === 'foobar' || $result === 'barfoo');
45 | }
46 |
47 | public function testConcatFiles()
48 | {
49 | $result = concat_files([__DIR__ . '/../../fixtures/concat/foo.txt', __DIR__ . '/../../fixtures/concat/bar.txt']);
50 | $this->assertSame("foo\nbar", $result);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Unit/Functional/MonadTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Monad\Identity::class, $monad);
17 | $this->assertSame(1, $monad());
18 |
19 | $monad = $monad(f\add(1));
20 | $this->assertInstanceOf(Monad\Identity::class, $monad);
21 | $this->assertSame(2, $monad());
22 | }
23 |
24 | public function testMaybe()
25 | {
26 | $maybe = Monad\maybe(1);
27 | $this->assertInstanceOf(Monad\Maybe::class, $maybe);
28 | $this->assertSame(1, $maybe());
29 |
30 | $maybe = $maybe(f\add(1));
31 | $this->assertInstanceOf(Monad\Maybe::class, $maybe);
32 | $this->assertSame(2, $maybe());
33 |
34 | $maybe = $maybe(f\always(null));
35 | $this->assertInstanceOf(Monad\Maybe::class, $maybe);
36 |
37 | $maybe = $maybe(f\mul(2));
38 | $this->assertNull($maybe());
39 | }
40 |
41 | public function testMaybeTree()
42 | {
43 | $foo = [
44 | 'name' => 'foo',
45 | 'parent' => null
46 | ];
47 | $bar = [
48 | 'name' => 'bar',
49 | 'parent' => $foo
50 | ];
51 | $baz = [
52 | 'name' => 'baz',
53 | 'parent' => $bar
54 | ];
55 |
56 | $parent = function ($value) {
57 | return $value['parent'];
58 | };
59 |
60 | $grandparent = Monad\maybe($baz)($parent)($parent);
61 | $this->assertSame($foo, $grandparent());
62 |
63 | $grandparent = Monad\maybe($foo)($parent)($parent);
64 | $this->assertNull($grandparent());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Unit/GraphQL/Annotated/Bar.php:
--------------------------------------------------------------------------------
1 | x + $input->y;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Unit/GraphQL/Annotated/MyDirective.php:
--------------------------------------------------------------------------------
1 | name('baz')
39 | ->type('string')
40 | ->resolve(always('Baz'))
41 | ];
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Unit/GraphQL/AnnotatedTest.php:
--------------------------------------------------------------------------------
1 | assertValid();
30 |
31 | $this->assertTrue($schema->hasType('Query'));
32 | $this->assertTrue($schema->hasType('Mutation'));
33 |
34 | $result = execute($schema, ['query' => 'query { hello }']);
35 | $this->assertSame(['data' => ['hello' => 'world']], $result);
36 |
37 | $result = execute($schema, ['query' => 'mutation { sum(input: {x: 2, y: 2}) }']);
38 | $this->assertSame(['data' => ['sum' => 4]], $result);
39 |
40 | $result = execute($schema, ['query' => 'query { foo { enum foo } }']);
41 | $this->assertSame(['data' => ['foo' => ['enum' => Enum::YES, 'foo' => 'foo']]], $result);
42 |
43 | $result = execute($schema, ['query' => 'query { bar { myBool myFloat } }']);
44 | $this->assertSame(['data' => ['bar' => ['myBool' => true, 'myFloat' => 4.2]]], $result);
45 |
46 | $this->assertNotNull($schema->getDirective('myDirective'));
47 |
48 | $result = execute($schema, ['query' => 'query { baz }']);
49 | $this->assertSame(['data' => ['baz' => 'Baz']], $result);
50 | }
51 |
52 | public function testListOfException()
53 | {
54 | $this->expectException(TypeError::class);
55 | annotated(ListOfException::class);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/GraphQL/DateTimeScalarTest.php:
--------------------------------------------------------------------------------
1 | serialize(DateTime::createFromFormat(DateScalar::FORMAT, $expected));
21 | self::assertSame($expected, $actual);
22 |
23 | $this->expectException(Error::class);
24 | $ds->serialize($expected);
25 | }
26 |
27 | public function testDateParse(): void
28 | {
29 | $value = '2020-07-18';
30 | $literal = new StringValueNode(['value' => $value]);
31 |
32 | $expected = DateTime::createFromFormat(DateScalar::FORMAT, $value);
33 | $ds = new DateScalar();
34 |
35 | $actual = $ds->parseLiteral($literal);
36 | self::assertEquals($expected, $actual);
37 |
38 | $this->expectException(Error::class);
39 | $ds->parseLiteral(new IntValueNode(['value' => 0]));
40 |
41 | $actual = $ds->parseValue($value);
42 | self::assertEquals($expected, $actual);
43 |
44 | $this->expectException(Error::class);
45 | $ds->parseValue('2020-07-18');
46 | }
47 |
48 | public function testDateTimeSerialize(): void
49 | {
50 | $expected = '2020-07-18 13:40:00';
51 | $dts = new DateTimeScalar();
52 |
53 | $actual = $dts->serialize(DateTime::createFromFormat(DateTimeScalar::FORMAT, $expected));
54 | self::assertSame($expected, $actual);
55 |
56 | $this->expectException(Error::class);
57 | $dts->serialize($expected);
58 | }
59 |
60 | public function testDateTimeParse(): void
61 | {
62 | $value = '2020-07-18 13:40:00';
63 | $literal = new StringValueNode(['value' => $value]);
64 |
65 | $expected = DateTime::createFromFormat(DateTimeScalar::FORMAT, $value);
66 | $dts = new DateTimeScalar();
67 |
68 | $actual = $dts->parseLiteral($literal);
69 | self::assertEquals($expected, $actual);
70 |
71 | $this->expectException(Error::class);
72 | $dts->parseLiteral(new IntValueNode(['value' => 0]));
73 |
74 | $actual = $dts->parseValue($value);
75 | self::assertEquals($expected, $actual);
76 |
77 | $this->expectException(Error::class);
78 | $dts->parseValue('2020-07-18');
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Unit/HttpHandlerRunner/HttpHandlerRunnerTest.php:
--------------------------------------------------------------------------------
1 | 'bar']);
19 |
20 | $this->expectOutputString('{"foo":"bar"}');
21 |
22 | $result = HttpHandlerRunner\sapi_emit($response);
23 |
24 | $this->assertTrue($result);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Unit/Mail/SwiftMailerTest.php:
--------------------------------------------------------------------------------
1 | expectException(UnderflowException::class);
21 | Mail\send(new Swift_Message());
22 | }
23 |
24 | public function testMailer()
25 | {
26 | $mailer = Mail\mailer(new Swift_NullTransport());
27 | $this->assertInstanceOf(Swift_Mailer::class, $mailer);
28 | $this->assertSame(Container\get(Mail\SWIFT_MAILER), $mailer);
29 |
30 | Mail\send(new Swift_Message());
31 | }
32 |
33 | public function testSugar()
34 | {
35 | $message = Mail\message('subject', ['from@from.from'], ['to@to.to'], 'body');
36 | $this->assertInstanceOf(Swift_Message::class, $message);
37 | $this->assertSame('subject', $message->getSubject());
38 | $this->assertSame('body', $message->getBody());
39 | $this->assertSame('text/plain', $message->getBodyContentType());
40 | $this->assertSame(['from@from.from' => null], $message->getFrom());
41 | $this->assertSame(['to@to.to' => null], $message->getTo());
42 |
43 | $smtp = Mail\smtp('host', 0, 'username', 'password');
44 | $this->assertInstanceOf(Swift_SmtpTransport::class, $smtp);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Unit/Monolog/MonologTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(StreamHandler::class, $handler);
20 | }
21 |
22 | public function testLog()
23 | {
24 | $handler = new TestHandler();
25 |
26 | Log\handler($handler);
27 | Log\log(Logger::WARNING, 'test');
28 |
29 | list($record) = $handler->getRecords();
30 |
31 | $this->assertSame(Log\MONOLOG_DEFAULT_CHANNEL, $record['channel']);
32 | }
33 |
34 | public function testSugar()
35 | {
36 | $handler = new TestHandler();
37 | Log\handler($handler, 'test');
38 |
39 | $levels = array_values(Logger::getLevels());
40 |
41 | foreach ($levels as $level) {
42 | $levelName = strtolower(Logger::getLevelName($level));
43 | call_user_func("Siler\\Monolog\\$levelName", $levelName, ['context' => $levelName], 'test');
44 | }
45 |
46 | $records = $handler->getRecords();
47 |
48 | foreach ($levels as $i => $level) {
49 | $levelName = Logger::getLevelName($level);
50 | $record = $records[$i];
51 |
52 | $this->assertSame(strtolower($levelName), $record['message']);
53 | $this->assertSame($level, $record['level']);
54 | $this->assertSame($levelName, $record['level_name']);
55 | $this->assertArrayHasKey('context', $record['context']);
56 | $this->assertSame(strtolower($levelName), $record['context']['context']);
57 | $this->assertSame('test', $record['channel']);
58 | }
59 | }
60 |
61 | public function testLogIf()
62 | {
63 | $handler = new TestHandler();
64 | $channel = 'log_if';
65 |
66 | Log\handler($handler, $channel);
67 | Log\debug('foo', [], $channel);
68 | Log\debug_if(always(false));
69 | Log\debug('bar', [], $channel);
70 |
71 | $records = $handler->getRecords();
72 |
73 | $this->assertCount(1, $records);
74 | $this->assertSame('foo', $records[0]['message']);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/ArrTest.php:
--------------------------------------------------------------------------------
1 | ['bar' => 'baz']];
14 | $expected = ['foo' => ['bar' => 'qux']];
15 |
16 | set($fixture, 'foo.bar', 'qux');
17 |
18 | $this->assertSame($expected, $fixture);
19 | }
20 |
21 | public function testSetDeeplyCreates()
22 | {
23 | $fixture = ['foo' => []];
24 | $expected = ['foo' => ['bar' => 'qux']];
25 |
26 | set($fixture, 'foo.bar', 'qux');
27 |
28 | $this->assertSame($expected, $fixture);
29 | }
30 |
31 | public function testAssoc()
32 | {
33 | $fixture = [
34 | ['foo', 'bar'],
35 | ['baz', 'qux'],
36 | [4, 2],
37 | ];
38 |
39 | $expected = [
40 | ['foo' => 'baz', 'bar' => 'qux'],
41 | ['foo' => 4, 'bar' => 2],
42 | ];
43 |
44 | $actual = assoc($fixture);
45 | $this->assertSame($expected, $actual);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/CollectionTest.php:
--------------------------------------------------------------------------------
1 | filter(even)->equals(collect([2, 4])));
18 | }
19 |
20 | public function testToArray(): void
21 | {
22 | self::assertSame([1, 2, 3], collect([1, 2, 3])->toArray());
23 | }
24 |
25 | public function testFold(): void
26 | {
27 | self::assertSame(6, collect([1, 2, 3])->fold(0, sum));
28 | }
29 |
30 | public function testMap(): void
31 | {
32 | self::assertTrue(collect([1, 2, 3])->map(mul(2))->equals(collect([2, 4, 6])));
33 | }
34 |
35 | public function testMerge(): void
36 | {
37 | self::assertTrue(collect([1, 2, 3, 4, 5, 6])->equals(collect([1, 2, 3])->merge(collect([4, 5, 6]))));
38 | }
39 |
40 | public function testJoin(): void
41 | {
42 | self::assertSame('123', collect([1, 2, 3])->join());
43 | self::assertSame('1,2,3', collect([1, 2, 3])->join(','));
44 | }
45 |
46 | public function testFirst(): void
47 | {
48 | self::assertSame(1, collect([1, 2 ,3])->first());
49 | self::assertNull(collect([])->first());
50 | }
51 |
52 | public function testLast(): void
53 | {
54 | self::assertSame(3, collect([1, 2 ,3])->last());
55 | self::assertNull(collect([])->last());
56 | }
57 |
58 | public function testIsEmpty(): void
59 | {
60 | self::assertTrue(collect([])->isEmpty());
61 | self::assertFalse(collect([1, 2, 3])->isEmpty());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/DispatcherTest.php:
--------------------------------------------------------------------------------
1 | listen(TestEvent::class, function (TestEvent $event) {
16 | $this->assertSame('test', $event->payload);
17 | });
18 |
19 | $dispatcher->dispatch(new TestEvent('test'));
20 | }
21 |
22 | public function testGetListenersForEvent()
23 | {
24 | $dispatcher = new Dispatcher();
25 |
26 | $callback = function (TestEvent $event) {
27 | };
28 |
29 | $dispatcher->listen(TestEvent::class, $callback);
30 |
31 | $this->assertSame([$callback], $dispatcher->getListenersForEvent(new TestEvent('test')));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/EnumTest.php:
--------------------------------------------------------------------------------
1 | assertSame(0, $foo->valueOf());
13 |
14 | $another_foo = TestEnum::of(TestEnum::FOO);
15 | $this->assertTrue($foo->sameAs($another_foo));
16 |
17 | $bar = TestEnum::of(TestEnum::BAR);
18 | $this->assertFalse($foo->sameAs($bar));
19 |
20 | $this->expectException(\UnexpectedValueException::class);
21 | $this->expectExceptionMessage(sprintf('Invalid value (foo) for enum (%s)', TestEnum::class));
22 | TestEnum::of('foo');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/FromToArrayTest.php:
--------------------------------------------------------------------------------
1 | 'foo',
17 | 'fooBar' => 'bar',
18 | 'foo_bar_baz' => 'baz',
19 | ]);
20 |
21 | $this->assertSame('foo', $fixture->foo);
22 | $this->assertSame('bar', $fixture->fooBar);
23 | $this->assertSame('baz', $fixture->fooBarBaz);
24 | }
25 |
26 | public function testToArray()
27 | {
28 | $fixture = new PatchFromToFixture();
29 | $fixture->foo = 'foo';
30 | $fixture->fooBar = 'bar';
31 |
32 | $arr = $fixture->toArray();
33 | $this->assertSame('foo', $arr['foo']);
34 | $this->assertSame('bar', $arr['foo_bar']);
35 | $this->assertNull($arr['foo_bar_baz'] ?? null);
36 | $this->assertNull($arr['fooBarBaz'] ?? null);
37 |
38 | $arr = $fixture->toArray(false);
39 | $this->assertSame('foo', $arr['foo']);
40 | $this->assertSame('bar', $arr['fooBar']);
41 | $this->assertNull($arr['foo_bar'] ?? null);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/IOTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString('foo' . PHP_EOL);
15 | println('foo');
16 | }
17 |
18 | public function testCsvToArray()
19 | {
20 | $arr = csv_to_array(__DIR__ . '/../../fixtures/test.csv');
21 | $this->assertSame([['1', 'foo'], ['2', 'bar'], ['3', 'baz']], $arr);
22 | }
23 |
24 | public function testFetch()
25 | {
26 | $args = ['foo' => 'bar'];
27 | ['response' => $response, 'error' => $err, 'status' => $code] = fetch('https://postman-echo.com/get', ['query' => $args]);
28 | $this->assertNull($err);
29 | $this->assertSame(200, $code);
30 | $this->assertSame($args, $response['args']);
31 |
32 | ['status' => $code, 'error' => $err] = fetch('https://postman-echo.com/status/418');
33 | $this->assertNull($err);
34 | $this->assertSame(418, $code);
35 |
36 | ['response' => $response, 'error' => $err, 'status' => $code] = fetch('https://postman-echo.com/post', [
37 | 'method' => 'post',
38 | 'body' => 'foobar',
39 | 'headers' => ['content-type' => 'text/plain'],
40 | ]);
41 | $this->assertNull($err);
42 | $this->assertSame(200, $code);
43 | $this->assertSame('foobar', $response['data']);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/ObjTest.php:
--------------------------------------------------------------------------------
1 | foo = 'bar';
13 |
14 | $return_ref = $target->patch(['foo' => 'baz']);
15 |
16 | $this->assertSame($return_ref, $target);
17 | $this->assertSame('baz', $target->foo);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/PatchFromToFixture.php:
--------------------------------------------------------------------------------
1 |
14 | * @implements PatchInterface
15 | */
16 | class PatchFromToFixture implements FromArrayInterface, ToArrayInterface, PatchInterface
17 | {
18 | /** @use FromArray */
19 | use FromArray;
20 | use ToArray;
21 |
22 | /** @use Patch */
23 | use Patch;
24 |
25 | public $foo;
26 | public $fooBar;
27 | public $fooBarBaz;
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/Test.php:
--------------------------------------------------------------------------------
1 | assertSame('KlassTest', unqualified_name(KlassTest::class));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Unit/Prelude/TestEnum.php:
--------------------------------------------------------------------------------
1 | assertFalse(is_array($tuple));
19 | $this->assertSame(1, $tuple[0]);
20 | $this->assertSame('a', $tuple[1]);
21 | $this->assertSame(true, $tuple[2]);
22 | $this->assertTrue(isset($tuple[1]));
23 | $this->assertIsArray($tuple->values());
24 | $this->assertSame([1, 'a', true], $tuple->values());
25 | }
26 |
27 | public function testOutOfRangeGet()
28 | {
29 | $this->expectException(OutOfRangeException::class);
30 | $tuple = tuple(1);
31 | $tuple[1];
32 | }
33 |
34 | public function testImmutableSet()
35 | {
36 | $this->expectException(RuntimeException::class);
37 | $tuple = tuple(1);
38 | $tuple[1] = 2;
39 | }
40 |
41 | public function testImmutableUnset()
42 | {
43 | $this->expectException(RuntimeException::class);
44 | $tuple = tuple(1);
45 | unset($tuple[0]);
46 | }
47 |
48 | public function testCount()
49 | {
50 | $tuple = tuple(1, 2, 3);
51 | $this->assertCount(3, $tuple);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Unit/Ratchet/GraphQLSubscriptionsConnectionTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(ConnectionInterface::class)->getMock();
16 | $conn = new GraphQLSubscriptionsConnection($interface, 'test_key');
17 | $this->assertSame('test_key', $conn->key());
18 | }
19 |
20 | public function testSend()
21 | {
22 | $interface = $this->getMockBuilder(ConnectionInterface::class)->getMock();
23 | $interface->expects($this->once())
24 | ->method('send')
25 | ->with('test_data');
26 |
27 | $conn = new GraphQLSubscriptionsConnection($interface, 'test_key');
28 | $conn->send('test_data');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Unit/Ratchet/RatchetTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(SubscriptionsManager::class)
15 | ->disableOriginalConstructor()
16 | ->getMock();
17 |
18 | $server = graphql_subscriptions($manager);
19 | $this->assertInstanceOf(IoServer::class, $server);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteClass.php:
--------------------------------------------------------------------------------
1 | expectOutputString('className.index');
13 | $_SERVER['REQUEST_METHOD'] = 'GET';
14 | $_SERVER['REQUEST_URI'] = '/class-name';
15 | Route\class_name('/class-name', 'Siler\Test\Unit\Route\RouteClass');
16 | }
17 |
18 | /**
19 | * @runInSeparateProcess
20 | * @preserveGlobalState disabled
21 | */
22 | public function testPostFoo()
23 | {
24 | $this->expectOutputString('className.postFoo');
25 | $_SERVER['REQUEST_METHOD'] = 'POST';
26 | $_SERVER['REQUEST_URI'] = '/class-name/foo';
27 | Route\class_name('/class-name', 'Siler\Test\Unit\Route\RouteClass');
28 | }
29 |
30 | /**
31 | * @runInSeparateProcess
32 | * @preserveGlobalState disabled
33 | */
34 | public function testPutFooBar()
35 | {
36 | $this->expectOutputString('className.putFooBar');
37 | $_SERVER['REQUEST_METHOD'] = 'PUT';
38 | $_SERVER['REQUEST_URI'] = '/class-name/foo/bar';
39 | Route\class_name('/class-name', 'Siler\Test\Unit\Route\RouteClass');
40 | }
41 |
42 | public function testAnyParams()
43 | {
44 | $this->expectOutputString('className.baz.qux');
45 | $_SERVER['REQUEST_METHOD'] = 'ANYTHING';
46 | $_SERVER['REQUEST_URI'] = '/class-name/baz/qux';
47 | Route\class_name('/class-name', 'Siler\Test\Unit\Route\RouteClass');
48 | }
49 |
50 | public function tearDown(): void
51 | {
52 | Route\resume();
53 | }
54 |
55 | protected function setUp(): void
56 | {
57 | Route\purge_match();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteFacadeTest.php:
--------------------------------------------------------------------------------
1 | expectException(Exception::class);
16 | $this->expectExceptionMessage('Route /bar/baz should match');
17 |
18 | $_SERVER['REQUEST_METHOD'] = 'GET';
19 |
20 | Route\get('/bar/baz', function ($params) {
21 | throw new Exception('Route /bar/baz should match');
22 | });
23 | }
24 |
25 | public function testPost()
26 | {
27 | $this->expectException(Exception::class);
28 | $this->expectExceptionMessage('Route /bar/baz should match');
29 |
30 | $_SERVER['REQUEST_METHOD'] = 'POST';
31 |
32 | Route\post('/bar/baz', function ($params) {
33 | throw new Exception('Route /bar/baz should match');
34 | });
35 | }
36 |
37 | public function testPut()
38 | {
39 | $this->expectException(Exception::class);
40 | $this->expectExceptionMessage('Route /bar/baz should match');
41 |
42 | $_SERVER['REQUEST_METHOD'] = 'PUT';
43 |
44 | Route\put('/bar/baz', function ($params) {
45 | throw new Exception('Route /bar/baz should match');
46 | });
47 | }
48 |
49 | public function testDelete()
50 | {
51 | $this->expectException(Exception::class);
52 | $this->expectExceptionMessage('Route /bar/baz should match');
53 |
54 | $_SERVER['REQUEST_METHOD'] = 'DELETE';
55 |
56 | Route\delete('/bar/baz', function ($params) {
57 | throw new Exception('Route /bar/baz should match');
58 | });
59 | }
60 |
61 | public function testOptions()
62 | {
63 | $this->expectException(Exception::class);
64 | $this->expectExceptionMessage('Route /bar/baz should match');
65 |
66 | $_SERVER['REQUEST_METHOD'] = 'OPTIONS';
67 |
68 | Route\options('/bar/baz', function ($params) {
69 | throw new Exception('Route /bar/baz should match');
70 | });
71 | }
72 |
73 | public function testAny()
74 | {
75 | $this->expectException(Exception::class);
76 | $this->expectExceptionMessage('Route /bar/baz should match');
77 |
78 | $_SERVER['REQUEST_METHOD'] = 'ANYTHING';
79 |
80 | Route\any('/bar/baz', function ($params) {
81 | throw new Exception('Route /bar/baz should match');
82 | });
83 | }
84 |
85 | protected function setUp(): void
86 | {
87 | $_GET = $_POST = $_REQUEST = ['foo' => 'bar'];
88 |
89 | $_SERVER['HTTP_HOST'] = 'test:8000';
90 | $_SERVER['SCRIPT_NAME'] = '/foo/test.php';
91 | $_SERVER['PATH_INFO'] = '/bar/baz';
92 | $_SERVER['REQUEST_URI'] = '/bar/baz';
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteFileTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString('index.get');
20 |
21 | $_SERVER['REQUEST_METHOD'] = 'GET';
22 | $_SERVER['REQUEST_URI'] = '/';
23 |
24 | Route\files(__DIR__ . '/../../fixtures/route_files/');
25 | }
26 |
27 | /**
28 | * @runInSeparateProcess
29 | * @preserveGlobalState disabled
30 | */
31 | public function testGetContact()
32 | {
33 | $this->expectOutputString('contact.get');
34 |
35 | $_SERVER['REQUEST_METHOD'] = 'GET';
36 | $_SERVER['REQUEST_URI'] = '/contact';
37 |
38 | Route\files(__DIR__ . '/../../fixtures/route_files/');
39 | }
40 |
41 | /**
42 | * @runInSeparateProcess
43 | * @preserveGlobalState disabled
44 | */
45 | public function testPostContact()
46 | {
47 | $this->expectOutputString('contact.post');
48 |
49 | $_SERVER['REQUEST_METHOD'] = 'POST';
50 | $_SERVER['REQUEST_URI'] = '/contact';
51 |
52 | Route\files(__DIR__ . '/../../fixtures/route_files/');
53 | }
54 |
55 | /**
56 | * @runInSeparateProcess
57 | * @preserveGlobalState disabled
58 | */
59 | public function testGetAbout()
60 | {
61 | $this->expectOutputString('about.index.get');
62 |
63 | $_SERVER['REQUEST_METHOD'] = 'GET';
64 | $_SERVER['REQUEST_URI'] = '/about';
65 |
66 | Route\files(__DIR__ . '/../../fixtures/route_files/');
67 | }
68 |
69 | /**
70 | * @runInSeparateProcess
71 | * @preserveGlobalState disabled
72 | */
73 | public function testGetWithParam()
74 | {
75 | $this->expectOutputString('foo.$8.get');
76 |
77 | $_SERVER['SCRIPT_NAME'] = '/index.php';
78 | $_SERVER['REQUEST_METHOD'] = 'GET';
79 | $_SERVER['REQUEST_URI'] = '/foo/8';
80 |
81 | Route\files(__DIR__ . '/../../fixtures/route_files/');
82 | }
83 |
84 | /**
85 | * @runInSeparateProcess
86 | * @preserveGlobalState disabled
87 | */
88 | public function testNotExists()
89 | {
90 | $this->expectException(InvalidArgumentException::class);
91 | Route\files('path/does/not/exists');
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteFileWithPrefixTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString('index.get');
19 |
20 | $_SERVER['REQUEST_METHOD'] = 'GET';
21 | $_SERVER['REQUEST_URI'] = '/foo/';
22 |
23 | Route\files(__DIR__ . '/../../fixtures/route_files/', '/foo');
24 | }
25 |
26 | /**
27 | * @runInSeparateProcess
28 | * @preserveGlobalState disabled
29 | */
30 | public function testGetContact()
31 | {
32 | $this->expectOutputString('contact.get');
33 |
34 | $_SERVER['REQUEST_METHOD'] = 'GET';
35 | $_SERVER['REQUEST_URI'] = '/foo/contact';
36 |
37 | Route\files(__DIR__ . '/../../fixtures/route_files/', '/foo');
38 | }
39 |
40 | /**
41 | * @runInSeparateProcess
42 | * @preserveGlobalState disabled
43 | */
44 | public function testPostContact()
45 | {
46 | $this->expectOutputString('contact.post');
47 |
48 | $_SERVER['REQUEST_METHOD'] = 'POST';
49 | $_SERVER['REQUEST_URI'] = '/foo/contact';
50 |
51 | Route\files(__DIR__ . '/../../fixtures/route_files/', '/foo');
52 | }
53 |
54 | /**
55 | * @runInSeparateProcess
56 | * @preserveGlobalState disabled
57 | */
58 | public function testGetAbout()
59 | {
60 | $this->expectOutputString('about.index.get');
61 |
62 | $_SERVER['REQUEST_METHOD'] = 'GET';
63 | $_SERVER['REQUEST_URI'] = '/foo/about';
64 |
65 | Route\files(__DIR__ . '/../../fixtures/route_files/', '/foo');
66 | }
67 |
68 | /**
69 | * @runInSeparateProcess
70 | * @preserveGlobalState disabled
71 | */
72 | public function testGetWithParam()
73 | {
74 | $this->expectOutputString('foo.$8.get');
75 |
76 | $_SERVER['SCRIPT_NAME'] = '/index.php';
77 | $_SERVER['REQUEST_METHOD'] = 'GET';
78 | $_SERVER['REQUEST_URI'] = '/foo/foo/8';
79 |
80 | Route\files(__DIR__ . '/../../fixtures/route_files/', '/foo');
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RoutePsr7Test.php:
--------------------------------------------------------------------------------
1 | '/test'];
20 | $request = ServerRequestFactory::fromGlobals($server);
21 |
22 | $actual = Route\get(
23 | '/test',
24 | function () {
25 | return 'foo';
26 | },
27 | $request
28 | );
29 |
30 | $this->assertSame('foo', $actual);
31 | }
32 |
33 | /**
34 | * @runInSeparateProcess
35 | * @preserveGlobalState disabled
36 | */
37 | public function testNullRoute()
38 | {
39 | $server = ['REQUEST_URI' => '/foo'];
40 | $request = ServerRequestFactory::fromGlobals($server);
41 |
42 | $actual = Route\get(
43 | '/bar',
44 | function () {
45 | return 'baz';
46 | },
47 | $request
48 | );
49 |
50 | $this->assertNull($actual);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteResourceTest.php:
--------------------------------------------------------------------------------
1 | expectOutputString('resources.index');
15 |
16 | $_SERVER['REQUEST_METHOD'] = 'GET';
17 | $_SERVER['REQUEST_URI'] = '/resources';
18 |
19 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
20 | }
21 |
22 | /**
23 | * @runInSeparateProcess
24 | */
25 | public function testCreateResource()
26 | {
27 | $this->expectOutputString('resources.create');
28 |
29 | $_SERVER['REQUEST_METHOD'] = 'GET';
30 | $_SERVER['REQUEST_URI'] = '/resources/create';
31 |
32 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
33 | }
34 |
35 | public function testStoreResource()
36 | {
37 | $this->expectOutputString('resources.store bar');
38 |
39 | $_SERVER['REQUEST_METHOD'] = 'POST';
40 | $_SERVER['REQUEST_URI'] = '/resources';
41 | $_POST['foo'] = 'bar';
42 |
43 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
44 | }
45 |
46 | public function testShowResource()
47 | {
48 | $this->expectOutputString('resources.show 8');
49 |
50 | $_SERVER['REQUEST_METHOD'] = 'GET';
51 | $_SERVER['REQUEST_URI'] = '/resources/8';
52 |
53 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
54 | }
55 |
56 | public function testEditResource()
57 | {
58 | $this->expectOutputString('resources.edit 8');
59 |
60 | $_SERVER['REQUEST_METHOD'] = 'GET';
61 | $_SERVER['REQUEST_URI'] = '/resources/8/edit';
62 |
63 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
64 | }
65 |
66 | public function testUpdateResource()
67 | {
68 | $this->expectOutputString('resources.update 8');
69 |
70 | $_SERVER['REQUEST_METHOD'] = 'PUT';
71 | $_SERVER['REQUEST_URI'] = '/resources/8';
72 |
73 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
74 | }
75 |
76 | public function testDestroyResource()
77 | {
78 | $this->expectOutputString('resources.destroy 8');
79 |
80 | $_SERVER['REQUEST_METHOD'] = 'DELETE';
81 | $_SERVER['REQUEST_URI'] = '/resources/8';
82 |
83 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources');
84 | }
85 |
86 | /**
87 | * @runInSeparateProcess
88 | */
89 | public function testOverrideIdentity()
90 | {
91 | $this->expectOutputString('resources.edit foo');
92 |
93 | $_SERVER['REQUEST_METHOD'] = 'GET';
94 | $_SERVER['REQUEST_URI'] = '/resources/foo/edit';
95 | $_SERVER['SCRIPT_NAME'] = '/test/index.php';
96 |
97 | Route\resource('/resources', __DIR__ . '/../../fixtures/resources/slug', 'slug');
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteStaticMethodTest.php:
--------------------------------------------------------------------------------
1 | assertSame('static_method', $result);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Unit/Route/RouteUtf8Test.php:
--------------------------------------------------------------------------------
1 | expectOutputString('foo');
17 | $_SERVER['REQUEST_URI'] = rawurlencode('/жолжаксынов');
18 | Route\get('/жолжаксынов', function () {
19 | echo 'foo';
20 | });
21 | }
22 |
23 | public function testUtf8B()
24 | {
25 | $this->expectOutputString('victon-빅톤-mayday');
26 | $_SERVER['REQUEST_URI'] = rawurlencode('/test/victon-빅톤-mayday');
27 | Route\get('/test/{test:.*}', function (array $params) {
28 | echo $params['test'];
29 | });
30 | }
31 |
32 | public function testUtf8C()
33 | {
34 | $this->expectOutputString('আড়-ইহ-জ-র-দ-ড়-বছর-র-শ-শ-র-গল-য়-ছ-র');
35 | $_SERVER['REQUEST_URI'] = rawurlencode('/foo/আড়-ইহ-জ-র-দ-ড়-বছর-র-শ-শ-র-গল-য়-ছ-র/baz');
36 | Route\get('/foo/{bar:.*}/baz', function (array $params) {
37 | echo $params['bar'];
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Unit/Route/SwooleHttpRequestMock.php:
--------------------------------------------------------------------------------
1 | server = [
15 | 'request_method' => $method,
16 | 'request_uri' => $uri
17 | ];
18 |
19 | $this->header = $header;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Unit/Stratigility/RequestHandlerDecoratorTest.php:
--------------------------------------------------------------------------------
1 | 'bar'];
22 |
23 | $handler = function (
24 | ServerRequestInterface $_request,
25 | array $_pathParams
26 | ) use (
27 | $request,
28 | $response,
29 | $params
30 | ): ResponseInterface {
31 | $this->assertSame($request, $_request);
32 | $this->assertSame($params, $_pathParams);
33 |
34 | return $response;
35 | };
36 |
37 | $decorator = new RequestHandlerDecorator($handler, $params);
38 | $decorator_response = $decorator->handle($request);
39 |
40 | $this->assertSame($response, $decorator_response);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Unit/Swoole/SwooleTest.php:
--------------------------------------------------------------------------------
1 | assertSame(3, $middleware(new Request(), new Response()));
31 |
32 | $early_return = [
33 | function (Request $request, Response $response, $value) {
34 | return $request->get['foo'] ?? 'foobar';
35 | },
36 | function (Request $request, Response $response, $value) {
37 | if ($value === 'bar') {
38 | return 'baz';
39 | }
40 |
41 | $response->header = 'quz';
42 | return null;
43 | },
44 | function (Request $request, Response $response, $value) {
45 | $response->header = $value;
46 | },
47 | ];
48 |
49 | $request = new Request();
50 | $request->get = ['foo' => 'bar'];
51 | $response = new Response();
52 | middleware($early_return)($request, $response);
53 | $this->assertSame('baz', $response->header);
54 |
55 | $request = new Request();
56 | $request->get = [];
57 | $response = new Response();
58 | middleware($early_return)($request, $response);
59 | $this->assertSame('quz', $response->header);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/TwigTest.php:
--------------------------------------------------------------------------------
1 | expectException(RuntimeException::class);
18 | $this->expectExceptionMessage('Twig should be initialized first');
19 |
20 | Container\set('twig', null);
21 | Twig\render('template.twig');
22 | }
23 |
24 | public function testCreateTwigEnv()
25 | {
26 | $twigEnv = Twig\init(__DIR__ . '/../../fixtures');
27 | $this->assertInstanceOf(Environment::class, $twigEnv);
28 | }
29 |
30 | public function testRender()
31 | {
32 | Twig\init(__DIR__ . '/../../fixtures');
33 | $this->assertSame('bar
', Twig\render('template.twig', ['foo' => 'bar']));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/fixtures/.env:
--------------------------------------------------------------------------------
1 | FOO=bar
2 | APP_KEY=test
3 |
4 | ENV_INT=8
5 | ENV_INT_NOT_NUMERIC=eight
6 |
7 | ENV_BOOL_FV0=false
8 | ENV_BOOL_FV1='0'
9 | ENV_BOOL_FV2=0
10 | ENV_BOOL_FV3=''
11 | ENV_BOOL_FV4=[]
12 | ENV_BOOL_FV5={}
13 |
14 | ENV_BOOL_TV=true
--------------------------------------------------------------------------------
/tests/fixtures/TestEvent.php:
--------------------------------------------------------------------------------
1 | payload = $payload;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/fixtures/callable_require.php:
--------------------------------------------------------------------------------
1 | [
5 | 'config' => 'value'
6 | ]
7 | ];
8 |
--------------------------------------------------------------------------------
/tests/fixtures/config/yaml/test.yml:
--------------------------------------------------------------------------------
1 | foo:
2 | bar: baz
3 |
--------------------------------------------------------------------------------
/tests/fixtures/foo.php:
--------------------------------------------------------------------------------
1 | Hello World
--------------------------------------------------------------------------------
/tests/fixtures/template.twig:
--------------------------------------------------------------------------------
1 | {{ foo }}
--------------------------------------------------------------------------------
/tests/fixtures/test.csv:
--------------------------------------------------------------------------------
1 | 1,foo
2 | 2,bar
3 | 3,baz
4 |
--------------------------------------------------------------------------------
/tests/fixtures/to_be_required.php:
--------------------------------------------------------------------------------
1 |