Compare commits

..

2 Commits

Author SHA1 Message Date
Kilian db5f81c294 Lighthouse 5.16 2021-07-26 01:35:16 +02:00
Kilian 5f6f382ace Pusher 2021-07-25 18:20:49 +02:00
322 changed files with 2114 additions and 52190 deletions
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
FILES=$(git diff --cached --name-only --diff-filter=ACMR -- '*.php' '*.html' '*.yaml' | sed 's| |\\ |g')
[ -z "$FILES" ] && exit 0
# Prettify all selected files
echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write
# Add back the modified/prettified files to staging
echo "$FILES" | xargs git add
exit 0
-15
View File
@@ -4,8 +4,6 @@ use System\Classes\PluginBase;
use App; use App;
use Config; use Config;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Foundation\AliasLoader; use Illuminate\Foundation\AliasLoader;
use GermanAirlinesVa\Graphql\Classes\GraphqlServiceProvider; use GermanAirlinesVa\Graphql\Classes\GraphqlServiceProvider;
use Nuwave\Lighthouse\Subscriptions\SubscriptionServiceProvider; use Nuwave\Lighthouse\Subscriptions\SubscriptionServiceProvider;
@@ -35,22 +33,9 @@ class Plugin extends PluginBase
public function boot() public function boot()
{ {
App::make('October\Rain\Support\ClassLoader')->addDirectories('graphql'); App::make('October\Rain\Support\ClassLoader')->addDirectories('graphql');
$this->bootPackages(); $this->bootPackages();
App::register(GraphqlServiceProvider::class); App::register(GraphqlServiceProvider::class);
App::register(SubscriptionServiceProvider::class); App::register(SubscriptionServiceProvider::class);
$this->app->singleton(DatabaseManager::class, function ($app) {
return $app->make('db');
});
Broadcast::routes([
'prefix' => '',
'middleware' => 'GermanAirlinesVA\\Graphql\\Classes\\Authentication',
]);
Config::set('database.connections.germanairlinesva_graphql', Config::get('germanairlinesva.graphql::connection'));
Config::push('system.unencrypt_cookies', 'graphql-session-id');
} }
public function bootPackages() public function bootPackages()
-5
View File
@@ -1,5 +0,0 @@
# Relations
## GraphQLKeys
- BelongsTo Member (External DB, creation in Social Plugin)
+1 -1
View File
@@ -32920,7 +32920,7 @@ var _validUrl = __webpack_require__(429);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var options = { method: 'post', headers: { 'Content-Type': 'application/json' } }; var options = { method: 'post', headers: { 'Content-Type': 'application/json' } };
var endpoint = 'http://' + window.location.host + '/graphql/'; // Initial var endpoint = 'http://localhost/graphql/'; // Initial
var defaultQuery = '\n# Welcome to GraphiQL\n#\n# GraphiQL is an in-browser tool for writing, validating, and\n# testing GraphQL queries.\n#\n# Type queries into this side of the screen, and you will see intelligent\n# typeaheads aware of the current GraphQL type schema and live syntax and\n# validation errors highlighted within the text.\n#\n# GraphQL queries typically start with a "{" character. Lines that starts\n# with a # are ignored.\n#\n# An example GraphQL query might look like:\n#\n# {\n# field(arg: "value") {\n# subField\n# }\n# }\n#\n# Keyboard shortcuts:\n#\n# Run Query: Ctrl-Enter (or press the play button above)\n#\n# Auto Complete: Ctrl-Space (or just start typing)\n#\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# Default endpoint is an instance of https://www.graph.cool/\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n\nquery {\n countries {\n name\n }\n}\n'; var defaultQuery = '\n# Welcome to GraphiQL\n#\n# GraphiQL is an in-browser tool for writing, validating, and\n# testing GraphQL queries.\n#\n# Type queries into this side of the screen, and you will see intelligent\n# typeaheads aware of the current GraphQL type schema and live syntax and\n# validation errors highlighted within the text.\n#\n# GraphQL queries typically start with a "{" character. Lines that starts\n# with a # are ignored.\n#\n# An example GraphQL query might look like:\n#\n# {\n# field(arg: "value") {\n# subField\n# }\n# }\n#\n# Keyboard shortcuts:\n#\n# Run Query: Ctrl-Enter (or press the play button above)\n#\n# Auto Complete: Ctrl-Space (or just start typing)\n#\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# Default endpoint is an instance of https://www.graph.cool/\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n\nquery {\n countries {\n name\n }\n}\n';
-25
View File
@@ -1,25 +0,0 @@
<?php namespace GermanAirlinesVa\Graphql\Classes;
use Closure;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class Authentication
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->header('Gql-Session') === 'SUPER_SECRET_KEY_HEADER') {
return '';
} else {
throw new AccessDeniedHttpException();
}
return $next($request);
}
}
-74
View File
@@ -1,74 +0,0 @@
<?php namespace GermanAirlinesVa\Graphql\Classes;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Subscriptions\Subscriber;
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class Dummy extends GraphQLSubscription
{
/**
* Check if subscriber is allowed to listen to the subscription.
*
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* @param \Illuminate\Http\Request $request
* @return bool
*/
public function authorize(Subscriber $subscriber, Request $request): bool
{
return true;
}
/**
* Filter which subscribers should receive the subscription.
*
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* @param mixed $root
* @return bool
*/
public function filter(Subscriber $subscriber, $root): bool
{
return true;
}
/**
* Encode topic name.
*
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* @param string $fieldName
* @return string
*/
public function encodeTopic(Subscriber $subscriber, string $fieldName): string
{
// Create a unique topic name based on the `author` argument
return Str::snake($fieldName);
}
/**
* Decode topic name.
*
* @param string $fieldName
* @param \App\Post $root
* @return string
*/
public function decodeTopic(string $fieldName, $root): string
{
return Str::snake($fieldName);
}
/**
* Resolve the subscription.
*
* @param \App\Post $root
* @param array<string, mixed> $args
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
* @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo
* @return mixed
*/
public function resolve($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): array
{
return [$root];
}
}
+2 -6
View File
@@ -68,7 +68,7 @@ class SchemaSourceProvider implements LighthouseSchemaSourceProvider
public function getSchemaString(): string public function getSchemaString(): string
{ {
// root types // root types
$schema = " $schema = '
type Query { type Query {
dummy: Boolean dummy: Boolean
} }
@@ -76,11 +76,7 @@ class SchemaSourceProvider implements LighthouseSchemaSourceProvider
type Mutation { type Mutation {
dummy: Boolean dummy: Boolean
} }
';
type Subscription {
dummy: Boolean @subscription(class: \"GermanAirlinesVa\\\\Graphql\\\\Classes\\\\Dummy\")
}
";
// schema // schema
$schema .= collect($this->getGraphMap())->implode('schema', ''); $schema .= collect($this->getGraphMap())->implode('schema', '');
return $schema; return $schema;
+1
View File
@@ -3,6 +3,7 @@
"type": "october-plugin", "type": "october-plugin",
"description": "None", "description": "None",
"require": { "require": {
"nuwave/lighthouse": "^5.0",
"composer/installers": "~1.0" "composer/installers": "~1.0"
} }
} }
+1 -22
View File
@@ -4,27 +4,6 @@ use Cms\Classes\Theme;
use GermanAirlinesVa\Graphql\Models\Settings; use GermanAirlinesVa\Graphql\Models\Settings;
return [ return [
'connection' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => 'germanairlinesva_graphql',
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => 'InnoDB',
'options' => extension_loaded('pdo_mysql')
? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
])
: [],
],
'packages' => [ 'packages' => [
'nuwave/lighthouse' => [ 'nuwave/lighthouse' => [
'config_namespace' => 'lighthouse', 'config_namespace' => 'lighthouse',
@@ -168,7 +147,7 @@ return [
| |
*/ */
//'debug' => \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\Debug::INCLUDE_TRACE, 'debug' => \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\Debug::INCLUDE_TRACE,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
-17
View File
@@ -1,17 +0,0 @@
<?php namespace GermanAirlinesVa\Graphql\Controllers;
use Backend\Classes\Controller;
class GraphqlKey extends Controller
{
public $implement = ['Backend\Behaviors\ListController'];
public $listConfig = 'config_list.yaml';
public $requiredPermissions = ['germanairlinesva.graphql.master'];
public function __construct()
{
parent::__construct();
}
}
+3 -2
View File
@@ -1,15 +1,16 @@
<?php namespace GermanAirlinesVa\Graphql\Controllers; <?php namespace GermanAirlinesVa\Graphql\Controllers;
use Backend\Classes\Controller; use Backend\Classes\Controller;
use BackendMenu;
class Playground extends Controller class Playground extends Controller
{ {
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
}
public $requiredPermissions = ['germanairlinesva.graphql.master']; BackendMenu::setContext('GermanAirlinesVa.Graphql', 'menu', 'playground');
}
public function index() public function index()
{ {
-18
View File
@@ -1,18 +0,0 @@
<div data-control="toolbar">
<button
class="btn btn-default oc-icon-trash-o"
disabled="disabled"
onclick="$(this).data('request-data', {
checked: $('.control-list').listWidget('getChecked')
})"
data-request="onDelete"
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked"
data-request-success="$(this).prop('disabled', true)"
data-stripe-load-indicator
>
<?= e(trans('backend::lang.list.delete_selected')) ?>
</button>
</div>
-11
View File
@@ -1,11 +0,0 @@
list: $/germanairlinesva/graphql/models/graphqlkey/columns.yaml
modelClass: GermanAirlinesVa\Graphql\Models\GraphqlKey
title: GraphQL Keys
noRecordsMessage: 'backend::lang.list.no_records'
showSetup: true
showCheckboxes: true
recordsPerPage: 20
toolbar:
buttons: list_toolbar
search:
prompt: 'backend::lang.list.search_prompt'
-1
View File
@@ -1 +0,0 @@
<?= $this->listRender() ?>
+3 -11
View File
@@ -1,3 +1,4 @@
title = "CMS"
== ==
<?php <?php
use Cms\Classes\Theme; use Cms\Classes\Theme;
@@ -13,17 +14,8 @@ function resolveCmsContent($root, $args) {
?> ?>
== ==
# Content # Content
extend type Query { extend type Query { cmsContent(name: String!): CmsContent! }
cmsContent(name: String!): CmsContent! type CmsContent { fileName: String! content: String markup: String parsedMarkup: String }
}
type CmsContent {
fileName: String!
content: String
markup: String
parsedMarkup: String
}
# CMS-wide types # CMS-wide types
# Backend\Models\User # Backend\Models\User
type BackendUser { id: ID! } type BackendUser { id: ID! }
+6 -18
View File
@@ -1,3 +1,4 @@
title = "Fleet"
== ==
== ==
# Content # Content
@@ -5,23 +6,10 @@ extend type Query {
aircraft: [Aircraft] @all(model: "GermanAirlinesVa\\Fleet\\Models\\Aircraft") aircraft: [Aircraft] @all(model: "GermanAirlinesVa\\Fleet\\Models\\Aircraft")
aircraftType: [AircraftType] @all(model: "GermanAirlinesVa\\Fleet\\Models\\AircraftType") aircraftType: [AircraftType] @all(model: "GermanAirlinesVa\\Fleet\\Models\\AircraftType")
} }
extend type Subscription {
aircraftAdded: Aircraft @subscription(class: "GermanAirlinesVa\\Fleet\\Classes\\AircraftAdded") type Subscription {
} aircraft: [Aircraft] @subscription(class: "GermanAirlinesVa\\Fleet\\Classes\\Aircraft")
extend type Mutation {
addAircraft(aircraft_type_id: ID!, home_airport_id: ID!, name: String!, registration: String! ): Aircraft
@create(model:"GermanAirlinesVa\\Fleet\\Models\\Aircraft")
@broadcast(subscription: "aircraftAdded")
} }
type Aircraft { type Aircraft { name: String! registration: String! }
id: ID! type AircraftType { type: String! aircrafts: [Aircraft] @hasMany }
aircraft_type_id: ID!
home_airport_id: ID!
name: String!
registration: String!
}
type AircraftType {
type: String!
aircrafts: [Aircraft] @hasMany
}
-150
View File
@@ -1,150 +0,0 @@
==
<?php
function resolveExamMemberRanks($root, $args) {
$exams = \GermanAirlinesVa\Schooling\Models\ExamMemberRank::where('member_id', $args['memberID'])->get();
foreach($exams as $exam) {
$exam->load([
'exam_questions' => function ($query) {
$query->groupBy('id');
}
]);
}
return $exams;
}
function resolveExamTyperatings($root, $args) {
$exams = \GermanAirlinesVa\Schooling\Models\ExamTyperating::where('member_id', $args['memberID'])->get();
foreach($exams as $exam) {
$exam->load([
'exam_questions' => function ($query) {
$query->groupBy('id');
}
]);
}
return $exams;
}
function resolveTyperatings() {
$typeratings = GermanAirlinesVa\Schooling\Models\Typerating::all();
foreach($typeratings as $typerating) {
$typerating->load([
'exam_questions' => function($query) {
$query->groupBy('id');
}
]);
}
return $typeratings;
}
function resolveMemberRanks() {
$ranks = GermanAirlinesVa\Schooling\Models\MemberRank::all();
foreach($ranks as $rank) {
$rank->load([
'exam_questions' => function($query) {
$query->groupBy('id');
}
]);
}
return $rank;
}
function createExamMemberRank($root, $args) {
return GermanAirlinesVa\Schooling\Models\ExamMemberRank::createNew($args['memberID'], $args['memberRankID']);
}
function resolveCreateExamTyperating($root, $args) {
return GermanAirlinesVa\Schooling\Models\ExamTyperating::createNew($args['memberID'], $args['typeratingID']);
}
==
# Content
extend type Query {
memberRanks: [MemberRank]!
typeratings: [Typerating]!
examMemberRanks(memberID: ID!): [ExamMemberRank]!
examTyperatings(memberID: ID!): [ExamTyperating]!
}
extend type Mutation {
createExamMemberRank(memberID: ID!, memberRankID: ID!): ExamMemberRank!
createExamTyperating(memberID: ID!, typeratingID: ID!): ExamTyperating!
}
type MemberRank {
id: ID!
name: String!
description: String!
points: Int!
price: Int!
badge: String!
region: String!
exam_questions: [ExamMemberRankQuestion]! @hasMany
exams: [ExamMemberRank]! @hasMany
}
type Typerating {
id: ID!
name: String!
price: Int!
exam_questions: [ExamTyperatingQuestion]! @hasMany
exams: [ExamTyperating]! @hasMany
}
type ExamMemberRank {
id: ID!
member_id: ID!
member_rank: MemberRank! @belongsTo
start: DateTime!
status: ExamStatus!
exam_questions: [ExamMemberRankQuestion]! @belongsToMany
exam_answers: [ExamMemberRankAnswer]! @belongsToMany
}
type ExamTyperating {
id: ID!
member_id: ID!
typerating: Typerating! @belongsTo
start: DateTime!
status: ExamStatus!
exam_questions: [ExamTyperatingQuestion]! @belongsToMany
exam_answers: [ExamTyperatingAnswer]! @belongsToMany
}
type ExamMemberRankQuestion {
id: ID!
member_rank: MemberRank! @belongsTo
in_use: Boolean!
mandatory: Boolean!
text: String!
picture: String!
exam_answers: [ExamMemberRankAnswer]! @hasMany
exam_member_ranks: [ExamMemberRank]! @belongsToMany
}
type ExamTyperatingQuestion {
id: ID!
typerating: Typerating! @belongsTo
in_use: Boolean!
mandatory: Boolean!
text: String!
picture: String!
exam_answers: [ExamTyperatingAnswer]! @hasMany
exma_typeratings: [ExamTyperating]! @belongsToMany
}
type ExamMemberRankAnswer {
id: ID!
exam_question: ExamMemberRankQuestion! @belongsTo
text: String!
is_correct: Boolean!
exam_member_ranks: [ExamMemberRank]! @belongsToMany
}
type ExamTyperatingAnswer {
id: ID!
exam_question: ExamTyperatingQuestion! @belongsTo
text: String!
is_correct: Boolean!
exam_typeratings: [ExamTyperating]! @belongsToMany
}
enum ExamStatus {
open
pending
closed
validated
}
scalar DateTime
@scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
+1 -3
View File
@@ -4,9 +4,7 @@
'description' => '', 'description' => '',
], ],
'menu' => [ 'menu' => [
'main' => 'GA GraphQL', 'playground' => 'GA GraphiQL',
'keys' => 'GraphQL Keys',
'playground' => 'GraphiQL',
], ],
'permission' => [ 'permission' => [
'tab' => [ 'tab' => [
-32
View File
@@ -1,32 +0,0 @@
<?php namespace GermanAirlinesVa\Graphql\Models;
use Model;
class GraphqlKey extends Model
{
use \October\Rain\Database\Traits\Validation;
/*
* Disable timestamps by default.
* Remove this line if timestamps are defined in the database table.
*/
public $timestamps = false;
/**
* @var string The database table used by the model.
*/
public $table = 'graphql_keys';
protected $connection = 'germanairlinesva_graphql';
/**
* @var array Validation rules
*/
public $rules = [
'member_id' => 'required',
'key' => 'required',
];
public $belongsTo = [
'member' => 'GermanAirlinesVa\Social\Models\Member',
];
}
+1
View File
@@ -1,5 +1,6 @@
<?php namespace GermanAirlinesVa\Graphql\Models; <?php namespace GermanAirlinesVa\Graphql\Models;
use Cms\Classes\Theme;
use Model; use Model;
class Settings extends Model class Settings extends Model
-15
View File
@@ -1,15 +0,0 @@
columns:
id:
label: id
type: number
member:
label: member
type: text
relation: member
valueFrom: name
valid_from:
label: valid_from
type: datetime
key:
label: key
type: text
+3 -4
View File
@@ -6,11 +6,10 @@
"author": "German Airlines VA", "author": "German Airlines VA",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@prettier/plugin-php": "^0.17.3", "@prettier/plugin-php": "^0.16.3",
"prettier": "^2.3.2" "prettier": "^2.3.0"
}, },
"scripts": { "scripts": {
"preinstall": "git config core.hooksPath .hooks", "format": "prettier --write './**/*.{php,html,htm,yaml}'"
"format": "prettier --write './**/*.{php,html,yaml}'"
} }
} }
+4 -13
View File
@@ -5,22 +5,13 @@ plugin:
icon: oc-icon-database icon: oc-icon-database
homepage: '' homepage: ''
permissions: permissions:
germanairlinesva.graphql.master: schemas:
tab: 'germanairlinesva.graphql::lang.permission.tab.schemas' tab: 'germanairlinesva.graphql::lang.permission.tab.schemas'
label: 'germanairlinesva.graphql::lang.permission.label.schemas' label: 'germanairlinesva.graphql::lang.permission.label.schemas'
navigation: navigation:
menu: menu:
label: 'germanairlinesva.graphql::lang.menu.main' label: 'germanairlinesva.graphql::lang.menu.playground'
url: / url: germanairlinesva/graphql/playground
icon: icon-database icon: icon-database
permissions: permissions:
- germanairlinesva.graphql.master - schemas
sideMenu:
side-menu-item:
label: 'germanairlinesva.graphql::lang.menu.playground'
url: germanairlinesva/graphql/playground
icon: icon-database
side-menu-item2:
label: 'germanairlinesva.graphql::lang.menu.keys'
url: germanairlinesva/graphql/graphqlkey
icon: icon-key
@@ -1,30 +0,0 @@
<?php namespace GermanAirlinesVa\GraphQl\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableCreateGermanAirlinesVaGraphQlDeferredBindings extends Migration
{
public function up()
{
Schema::connection('germanairlinesva_graphql')->create('deferred_bindings', function ($table) {
$table->engine = 'InnoDB';
$table->increments('id')->unsigned();
$table->string('master_type');
$table->string('master_field');
$table->string('slave_type');
$table->integer('slave_id');
$table->mediumText('pivot_data')->nullable();
$table->string('session_key');
$table->boolean('is_bind')->default(true);
$table->timestamps();
});
}
public function down()
{
Schema::connection('germanairlinesva_graphql')->disableForeignKeyConstraints();
Schema::connection('germanairlinesva_graphql')->dropIfExists('deferred_bindings');
Schema::connection('germanairlinesva_graphql')->enableForeignKeyConstraints();
}
}
@@ -1,26 +0,0 @@
<?php namespace GermanAirlinesVa\Graphql\Updates;
use DB;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableCreateGermanAirlinesVaGraphqlGraphqlKeys extends Migration
{
public function up()
{
Schema::connection('germanairlinesva_graphql')->create('graphql_keys', function ($table) {
$table->engine = 'InnoDB';
$table->bigIncrements('id')->unsigned();
$table->bigInteger('member_id')->unsigned();
$table->datetime('valid_from')->default(DB::raw('NOW()'));
$table->string('key');
});
}
public function down()
{
Schema::connection('germanairlinesva_graphql')->disableForeignKeyConstraints();
Schema::connection('germanairlinesva_graphql')->dropIfExists('graphql_keys');
Schema::connection('germanairlinesva_graphql')->enableForeignKeyConstraints();
}
}
+1 -6
View File
@@ -1,7 +1,2 @@
1.0.1: 1.0.1:
- 'Initialize plugin.' - Initialize plugin.
- 'Create table deferred_bindings'
- builder_table_create_deferred_bindings.php
1.0.2:
- 'Create table graphql_keys'
- builder_table_create_graphql_keys.php
+4
View File
@@ -6,7 +6,11 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
); );
+15 -4
View File
@@ -6,6 +6,20 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'51fcf4e06c07cc00c920b44bcd900e7a' => $vendorDir . '/thecodingmachine/safe/deprecated/apc.php', '51fcf4e06c07cc00c920b44bcd900e7a' => $vendorDir . '/thecodingmachine/safe/deprecated/apc.php',
'47f619d9197b36cf5ab70738d7743fe2' => $vendorDir . '/thecodingmachine/safe/deprecated/libevent.php', '47f619d9197b36cf5ab70738d7743fe2' => $vendorDir . '/thecodingmachine/safe/deprecated/libevent.php',
'ea6bb8a12ef9b68f6ada99058e530760' => $vendorDir . '/thecodingmachine/safe/deprecated/mssql.php', 'ea6bb8a12ef9b68f6ada99058e530760' => $vendorDir . '/thecodingmachine/safe/deprecated/mssql.php',
@@ -95,9 +109,6 @@ return array(
'4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php', '4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php',
'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php', 'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php',
'356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php', '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
); );
+45
View File
@@ -6,19 +6,64 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\HttpClient\\' => array($vendorDir . '/symfony/http-client-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'),
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'), 'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'),
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'),
'Nuwave\\Lighthouse\\' => array($vendorDir . '/nuwave/lighthouse/src'), 'Nuwave\\Lighthouse\\' => array($vendorDir . '/nuwave/lighthouse/src'),
'Laragraph\\Utils\\' => array($vendorDir . '/laragraph/utils/src'), 'Laragraph\\Utils\\' => array($vendorDir . '/laragraph/utils/src'),
'Illuminate\\Validation\\' => array($vendorDir . '/illuminate/validation'),
'Illuminate\\Translation\\' => array($vendorDir . '/illuminate/translation'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/support'),
'Illuminate\\Session\\' => array($vendorDir . '/illuminate/session'),
'Illuminate\\Routing\\' => array($vendorDir . '/illuminate/routing'),
'Illuminate\\Queue\\' => array($vendorDir . '/illuminate/queue'),
'Illuminate\\Pipeline\\' => array($vendorDir . '/illuminate/pipeline'),
'Illuminate\\Pagination\\' => array($vendorDir . '/illuminate/pagination'),
'Illuminate\\Http\\' => array($vendorDir . '/illuminate/http'),
'Illuminate\\Filesystem\\' => array($vendorDir . '/illuminate/filesystem'),
'Illuminate\\Database\\' => array($vendorDir . '/illuminate/database'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
'Illuminate\\Console\\' => array($vendorDir . '/illuminate/console'),
'Illuminate\\Bus\\' => array($vendorDir . '/illuminate/bus'),
'Illuminate\\Auth\\' => array($vendorDir . '/illuminate/auth'),
'HaydenPierce\\ClassFinder\\UnitTest\\' => array($vendorDir . '/haydenpierce/class-finder/test/unit'), 'HaydenPierce\\ClassFinder\\UnitTest\\' => array($vendorDir . '/haydenpierce/class-finder/test/unit'),
'HaydenPierce\\ClassFinder\\' => array($vendorDir . '/haydenpierce/class-finder/src'), 'HaydenPierce\\ClassFinder\\' => array($vendorDir . '/haydenpierce/class-finder/src'),
'GraphQL\\' => array($vendorDir . '/webonyx/graphql-php/src'), 'GraphQL\\' => array($vendorDir . '/webonyx/graphql-php/src'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
); );
+270 -4
View File
@@ -7,6 +7,20 @@ namespace Composer\Autoload;
class ComposerStaticInit02b791b67b928853969b061eb6816088 class ComposerStaticInit02b791b67b928853969b061eb6816088
{ {
public static $files = array ( public static $files = array (
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php',
'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
'51fcf4e06c07cc00c920b44bcd900e7a' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/apc.php', '51fcf4e06c07cc00c920b44bcd900e7a' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/apc.php',
'47f619d9197b36cf5ab70738d7743fe2' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/libevent.php', '47f619d9197b36cf5ab70738d7743fe2' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/libevent.php',
'ea6bb8a12ef9b68f6ada99058e530760' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/mssql.php', 'ea6bb8a12ef9b68f6ada99058e530760' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/mssql.php',
@@ -96,23 +110,48 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
'4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php', '4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php',
'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php', 'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php',
'356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php', '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'v' =>
array (
'voku\\' => 5,
),
'S' => 'S' =>
array ( array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Php72\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26, 'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Contracts\\HttpClient\\' => 29,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Translation\\' => 30,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Routing\\' => 26,
'Symfony\\Component\\Process\\' => 26,
'Symfony\\Component\\Mime\\' => 23,
'Symfony\\Component\\HttpKernel\\' => 29,
'Symfony\\Component\\HttpFoundation\\' => 33,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\ErrorHandler\\' => 31,
'Symfony\\Component\\Console\\' => 26,
'Safe\\' => 5, 'Safe\\' => 5,
), ),
'R' =>
array (
'Ramsey\\Uuid\\' => 12,
'Ramsey\\Collection\\' => 18,
),
'P' => 'P' =>
array ( array (
'Psr\\SimpleCache\\' => 16, 'Psr\\SimpleCache\\' => 16,
@@ -120,6 +159,10 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
'Psr\\EventDispatcher\\' => 20, 'Psr\\EventDispatcher\\' => 20,
'Psr\\Container\\' => 14, 'Psr\\Container\\' => 14,
), ),
'O' =>
array (
'Opis\\Closure\\' => 13,
),
'N' => 'N' =>
array ( array (
'Nuwave\\Lighthouse\\' => 18, 'Nuwave\\Lighthouse\\' => 18,
@@ -128,6 +171,25 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
'Laragraph\\Utils\\' => 16, 'Laragraph\\Utils\\' => 16,
), ),
'I' =>
array (
'Illuminate\\Validation\\' => 22,
'Illuminate\\Translation\\' => 23,
'Illuminate\\Support\\' => 19,
'Illuminate\\Session\\' => 19,
'Illuminate\\Routing\\' => 19,
'Illuminate\\Queue\\' => 17,
'Illuminate\\Pipeline\\' => 20,
'Illuminate\\Pagination\\' => 22,
'Illuminate\\Http\\' => 16,
'Illuminate\\Filesystem\\' => 22,
'Illuminate\\Database\\' => 20,
'Illuminate\\Contracts\\' => 21,
'Illuminate\\Container\\' => 21,
'Illuminate\\Console\\' => 19,
'Illuminate\\Bus\\' => 15,
'Illuminate\\Auth\\' => 16,
),
'H' => 'H' =>
array ( array (
'HaydenPierce\\ClassFinder\\UnitTest\\' => 34, 'HaydenPierce\\ClassFinder\\UnitTest\\' => 34,
@@ -137,9 +199,35 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
'GraphQL\\' => 8, 'GraphQL\\' => 8,
), ),
'E' =>
array (
'Egulias\\EmailValidator\\' => 23,
),
'D' =>
array (
'Doctrine\\Inflector\\' => 19,
'Doctrine\\Common\\Lexer\\' => 22,
),
'C' =>
array (
'Composer\\Installers\\' => 20,
'Carbon\\' => 7,
),
'B' =>
array (
'Brick\\Math\\' => 11,
),
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'voku\\' =>
array (
0 => __DIR__ . '/..' . '/voku/portable-ascii/src/voku',
),
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Php73\\' => 'Symfony\\Polyfill\\Php73\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73', 0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
@@ -148,6 +236,10 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php72', 0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
), ),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
@@ -160,12 +252,88 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
), ),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Contracts\\HttpClient\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/http-client-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\VarDumper\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/var-dumper',
),
'Symfony\\Component\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation',
),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'Symfony\\Component\\Routing\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/routing',
),
'Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
'Symfony\\Component\\Mime\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/mime',
),
'Symfony\\Component\\HttpKernel\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/http-kernel',
),
'Symfony\\Component\\HttpFoundation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/http-foundation',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Symfony\\Component\\ErrorHandler\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/error-handler',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Safe\\' => 'Safe\\' =>
array ( array (
0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib', 0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib',
1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated', 1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated',
2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated', 2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated',
), ),
'Ramsey\\Uuid\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
),
'Ramsey\\Collection\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/collection/src',
),
'Psr\\SimpleCache\\' => 'Psr\\SimpleCache\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src', 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
@@ -182,6 +350,10 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
0 => __DIR__ . '/..' . '/psr/container/src', 0 => __DIR__ . '/..' . '/psr/container/src',
), ),
'Opis\\Closure\\' =>
array (
0 => __DIR__ . '/..' . '/opis/closure/src',
),
'Nuwave\\Lighthouse\\' => 'Nuwave\\Lighthouse\\' =>
array ( array (
0 => __DIR__ . '/..' . '/nuwave/lighthouse/src', 0 => __DIR__ . '/..' . '/nuwave/lighthouse/src',
@@ -190,6 +362,72 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
0 => __DIR__ . '/..' . '/laragraph/utils/src', 0 => __DIR__ . '/..' . '/laragraph/utils/src',
), ),
'Illuminate\\Validation\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/validation',
),
'Illuminate\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/translation',
),
'Illuminate\\Support\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/macroable',
1 => __DIR__ . '/..' . '/illuminate/collections',
2 => __DIR__ . '/..' . '/illuminate/support',
),
'Illuminate\\Session\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/session',
),
'Illuminate\\Routing\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/routing',
),
'Illuminate\\Queue\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/queue',
),
'Illuminate\\Pipeline\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/pipeline',
),
'Illuminate\\Pagination\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/pagination',
),
'Illuminate\\Http\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/http',
),
'Illuminate\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/filesystem',
),
'Illuminate\\Database\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/database',
),
'Illuminate\\Contracts\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/contracts',
),
'Illuminate\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/container',
),
'Illuminate\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/console',
),
'Illuminate\\Bus\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/bus',
),
'Illuminate\\Auth\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/auth',
),
'HaydenPierce\\ClassFinder\\UnitTest\\' => 'HaydenPierce\\ClassFinder\\UnitTest\\' =>
array ( array (
0 => __DIR__ . '/..' . '/haydenpierce/class-finder/test/unit', 0 => __DIR__ . '/..' . '/haydenpierce/class-finder/test/unit',
@@ -202,12 +440,40 @@ class ComposerStaticInit02b791b67b928853969b061eb6816088
array ( array (
0 => __DIR__ . '/..' . '/webonyx/graphql-php/src', 0 => __DIR__ . '/..' . '/webonyx/graphql-php/src',
), ),
'Egulias\\EmailValidator\\' =>
array (
0 => __DIR__ . '/..' . '/egulias/email-validator/src',
),
'Doctrine\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector',
),
'Doctrine\\Common\\Lexer\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
),
'Composer\\Installers\\' =>
array (
0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers',
),
'Carbon\\' =>
array (
0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
),
'Brick\\Math\\' =>
array (
0 => __DIR__ . '/..' . '/brick/math/src',
),
); );
public static $classMap = array ( public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
); );
public static function getInitializer(ClassLoader $loader) public static function getInitializer(ClassLoader $loader)
+18 -18
View File
@@ -2963,22 +2963,22 @@
}, },
{ {
"name": "symfony/http-kernel", "name": "symfony/http-kernel",
"version": "v5.3.4", "version": "v5.3.3",
"version_normalized": "5.3.4.0", "version_normalized": "5.3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-kernel.git", "url": "https://github.com/symfony/http-kernel.git",
"reference": "38a768f2946a7261dfc399873dda2c115dd6ac17" "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/38a768f2946a7261dfc399873dda2c115dd6ac17", "url": "https://api.github.com/repos/symfony/http-kernel/zipball/90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8",
"reference": "38a768f2946a7261dfc399873dda2c115dd6ac17", "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"psr/log": "^1|^2", "psr/log": "~1.0",
"symfony/deprecation-contracts": "^2.1", "symfony/deprecation-contracts": "^2.1",
"symfony/error-handler": "^4.4|^5.0", "symfony/error-handler": "^4.4|^5.0",
"symfony/event-dispatcher": "^5.0", "symfony/event-dispatcher": "^5.0",
@@ -2986,7 +2986,7 @@
"symfony/http-foundation": "^5.3", "symfony/http-foundation": "^5.3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php73": "^1.9", "symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16" "symfony/polyfill-php80": "^1.15"
}, },
"conflict": { "conflict": {
"symfony/browser-kit": "<4.4", "symfony/browser-kit": "<4.4",
@@ -3005,7 +3005,7 @@
"twig/twig": "<2.13" "twig/twig": "<2.13"
}, },
"provide": { "provide": {
"psr/log-implementation": "1.0|2.0" "psr/log-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"psr/cache": "^1.0|^2.0|^3.0", "psr/cache": "^1.0|^2.0|^3.0",
@@ -3030,7 +3030,7 @@
"symfony/console": "", "symfony/console": "",
"symfony/dependency-injection": "" "symfony/dependency-injection": ""
}, },
"time": "2021-07-26T17:36:49+00:00", "time": "2021-06-30T08:27:49+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -3058,7 +3058,7 @@
"description": "Provides a structured process for converting a Request into a Response", "description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-kernel/tree/v5.3.4" "source": "https://github.com/symfony/http-kernel/tree/v5.3.3"
}, },
"funding": [ "funding": [
{ {
@@ -3837,24 +3837,24 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v5.3.4", "version": "v5.3.2",
"version_normalized": "5.3.4.0", "version_normalized": "5.3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "d16634ee55b895bd85ec714dadc58e4428ecf030" "reference": "714b47f9196de61a196d86c4bad5f09201b307df"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d16634ee55b895bd85ec714dadc58e4428ecf030", "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df",
"reference": "d16634ee55b895bd85ec714dadc58e4428ecf030", "reference": "714b47f9196de61a196d86c4bad5f09201b307df",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"symfony/polyfill-php80": "^1.16" "symfony/polyfill-php80": "^1.15"
}, },
"time": "2021-07-23T15:54:19+00:00", "time": "2021-06-12T10:15:01+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@@ -3882,7 +3882,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v5.3.4" "source": "https://github.com/symfony/process/tree/v5.3.2"
}, },
"funding": [ "funding": [
{ {
-26
View File
@@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
@@ -1,7 +0,0 @@
/ci/ export-ignore
/docs/ export-ignore
/test/ export-ignore
/.gitignore export-ignore
/.gitlab-ci.yml export-ignore
/composer.lock export-ignore
/phpunit.xml export-ignore
-13
View File
@@ -1,13 +0,0 @@
Copyright (c) 2018 Hayden Pierce
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-126
View File
@@ -1,126 +0,0 @@
ClassFinder
===========
A dead simple utility to identify classes in a given namespace.
This package is an improved implementation of an [answer on Stack Overflow](https://stackoverflow.com/a/40229665/3000068)
and provides additional features with less configuration required.
Requirements
------------
* Application is using Composer.
* Classes can be autoloaded with Composer.
* PHP >= 5.3.0
Installing
----------
Installing is done by requiring it with Composer.
```
$ composer require haydenpierce/class-finder
```
No other installation methods are currently supported.
Supported Autoloading Methods
--------------------------------
| Method | Supported | with `ClassFinder::RECURSIVE_MODE` |
| ---------- | --------- | ---------------------------------- |
| PSR-4 | ✔️ | ✔️ |
| PSR-0 | ❌️* | ❌️* |
| Classmap | ✔️ | ✔️ |
| Files | ✔️^ | ❌️** |
\^ Experimental.
\* Planned.
\** Not planned. Open an issue if you need this feature.
Examples
--------
**Standard Mode**
```
<?php
require_once __DIR__ . '/vendor/autoload.php';
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
/**
* array(
* 'TestApp1\Foo\Bar',
* 'TestApp1\Foo\Baz',
* 'TestApp1\Foo\Foo'
* )
*/
var_dump($classes);
```
**Recursive Mode** *(in v0.3-beta)*
```
<?php
require_once __DIR__ . '/vendor/autoload.php';
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE);
/**
* array(
* 'TestApp1\Foo\Bar',
* 'TestApp1\Foo\Baz',
* 'TestApp1\Foo\Foo',
* 'TestApp1\Foo\Box\Bar',
* 'TestApp1\Foo\Box\Baz',
* 'TestApp1\Foo\Box\Foo',
* 'TestApp1\Foo\Box\Lon\Bar',
* 'TestApp1\Foo\Box\Lon\Baz',
* 'TestApp1\Foo\Box\Lon\Foo',
* )
*/
var_dump($classes);
```
Documentation
-------------
[Changelog](docs/changelog.md)
**Exceptions**:
* [Files could not locate PHP](docs/exceptions/filesCouldNotLocatePHP.md)
* [Files exec not available](docs/exceptions/filesExecNotAvailable.md)
* [Missing composer.json](docs/exceptions/missingComposerConfig.md)
**Internals**
* [How Testing Works](docs/testing.md)
* [Continuous Integration Notes](docs/ci.md)
Future Work
-----------
> **WARNING**: Before 1.0.0, expect that bug fixes _will not_ be backported to older versions. Backwards incompatible changes
may be introduced in minor 0.X.Y versions, where X changes.
* `psr0` support
* Additional features:
Various ideas:
* ~~`ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE)`.
Providing classes multiple namespaces deep.~~ (included v0.3-beta)
* `ClassFinder::getClassesImplementingInterface('TestApp1\Foo', 'TestApp1\FooInterface', ClassFinder::RECURSIVE_MODE)`.
Filtering classes to only classes that implement a namespace.
* `ClassFinder::debugRenderReport('TestApp1\Foo\Baz')`
Guidance for solving "class not found" errors resulting from typos in namespaces, missing directories, etc. Would print
an HTML report. Not intended for production use, but debugging.
-27
View File
@@ -1,27 +0,0 @@
{
"name": "haydenpierce/class-finder",
"description" : "A library that can provide of a list of classes in a given namespace",
"type": "library",
"license": "MIT",
"version": "0.4.3",
"authors": [
{
"name": "Hayden Pierce",
"email": "hayden@haydenpierce.com"
}
],
"require": {
"php": ">=5.3",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "~9.0",
"mikey179/vfsstream": "^1.6"
},
"autoload": {
"psr-4": {
"HaydenPierce\\ClassFinder\\": "src/",
"HaydenPierce\\ClassFinder\\UnitTest\\": "test/unit"
}
}
}
-82
View File
@@ -1,82 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder;
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
class AppConfig
{
/** @var string */
private $appRoot;
public function __construct()
{
$this->appRoot = $this->findAppRoot();
}
/**
* @return string
*/
private function findAppRoot()
{
if ($this->appRoot) {
$appRoot = $this->appRoot;
} else {
$workingDirectory = str_replace('\\', '/', __DIR__);
$workingDirectory = str_replace('/vendor/haydenpierce/class-finder/src', '', $workingDirectory);
$directoryPathPieces = explode('/', $workingDirectory);
$appRoot = null;
do {
$path = implode('/', $directoryPathPieces) . '/composer.json';
if (file_exists($path)) {
$appRoot = implode('/', $directoryPathPieces) . '/';
} else {
array_pop($directoryPathPieces);
}
} while (is_null($appRoot) && count($directoryPathPieces) > 0);
}
$this->throwIfInvalidAppRoot($appRoot);
$this->appRoot= $appRoot;
return $this->appRoot;
}
/**
* @param string $appRoot
* @return void
* @throws ClassFinderException
*/
private function throwIfInvalidAppRoot($appRoot)
{
if (!file_exists($appRoot . '/composer.json')) {
throw new ClassFinderException(sprintf("Could not locate composer.json. You can get around this by setting ClassFinder::\$appRoot manually. See '%s' for details.",
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/missingComposerConfig.md'
));
}
}
/**
* @return string
*/
public function getAppRoot()
{
if ($this->appRoot === null) {
$this->appRoot = $this->findAppRoot();
}
$this->throwIfInvalidAppRoot($this->appRoot);
return $this->appRoot;
}
/**
* @param string $appRoot
* @return void
*/
public function setAppRoot($appRoot)
{
$this->appRoot = $appRoot;
}
}
-208
View File
@@ -1,208 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder;
use HaydenPierce\ClassFinder\Classmap\ClassmapEntryFactory;
use HaydenPierce\ClassFinder\Classmap\ClassmapFinder;
use HaydenPierce\ClassFinder\Files\FilesEntryFactory;
use HaydenPierce\ClassFinder\Files\FilesFinder;
use HaydenPierce\ClassFinder\PSR4\PSR4Finder;
use HaydenPierce\ClassFinder\PSR4\PSR4NamespaceFactory;
class ClassFinder
{
const STANDARD_MODE = 1;
const RECURSIVE_MODE = 2;
/** @var AppConfig */
private static $config;
/** @var PSR4Finder */
private static $psr4;
/** @var ClassmapFinder */
private static $classmap;
/** @var FilesFinder */
private static $files;
/** @var boolean */
private static $useFilesSupport = false;
/** @var boolean */
private static $usePSR4Support = true;
/** @var boolean */
private static $useClassmapSupport = true;
/**
* @return void
*/
private static function initialize()
{
if (!(self::$config instanceof AppConfig)) {
self::$config = new AppConfig();
}
if (!(self::$psr4 instanceof PSR4Finder)) {
$PSR4Factory = new PSR4NamespaceFactory(self::$config);
self::$psr4 = new PSR4Finder($PSR4Factory);
}
if (!(self::$classmap instanceof ClassmapFinder)) {
$classmapFactory = new ClassmapEntryFactory(self::$config);
self::$classmap = new ClassmapFinder($classmapFactory);
}
if (!(self::$files instanceof FilesFinder) && self::$useFilesSupport) {
$filesFactory = new FilesEntryFactory(self::$config);
self::$files = new FilesFinder($filesFactory);
}
}
/**
* Identify classes in a given namespace.
*
* @param string $namespace
* @param int $options
* @return string[]
*
* @throws \Exception
*/
public static function getClassesInNamespace($namespace, $options = self::STANDARD_MODE)
{
self::initialize();
$findersWithNamespace = self::findersWithNamespace($namespace);
$classes = array_reduce($findersWithNamespace, function($carry, FinderInterface $finder) use ($namespace, $options){
return array_merge($carry, $finder->findClasses($namespace, $options));
}, array());
return array_unique($classes);
}
/**
* Check if a given namespace contains any classes.
*
* @param string $namespace
* @return bool
*/
public static function namespaceHasClasses($namespace)
{
self::initialize();
return count(self::findersWithNamespace($namespace)) > 0;
}
/**
* @param string $appRoot
* @return void
*/
public static function setAppRoot($appRoot)
{
self::initialize();
self::$config->setAppRoot($appRoot);
}
/**
* @return void
*/
public static function enableExperimentalFilesSupport()
{
self::$useFilesSupport = true;
}
/**
* @return void
*/
public static function disableExperimentalFilesSupport()
{
self::$useFilesSupport = false;
}
/**
* @return void
*/
public static function enablePSR4Support()
{
self::$usePSR4Support = true;
}
/**
* @return void
*/
public static function disablePSR4Support()
{
self::$usePSR4Support = false;
}
/**
* @return void
*/
public static function enableClassmapSupport()
{
self::$useClassmapSupport = true;
}
/**
* @return void
*/
public static function disableClassmapSupport()
{
self::$useClassmapSupport = false;
}
/**
* @return FinderInterface[]
*/
private static function getSupportedFinders()
{
$supportedFinders = array();
/*
* This is done for testing. For some tests, allowing PSR4 classes contaminates the test results. This could also be
* disabled for performance reasons (less finders in use means less work), but most people probably won't do that.
*/
if (self::$usePSR4Support) {
$supportedFinders[] = self::$psr4;
}
/*
* This is done for testing. For some tests, allowing classmap classes contaminates the test results. This could also be
* disabled for performance reasons (less finders in use means less work), but most people probably won't do that.
*/
if (self::$useClassmapSupport) {
$supportedFinders[] = self::$classmap;
}
/*
* Files support is tucked away behind a flag because it will need to use some kind of shell access via exec, or
* system.
*
* #1 Many environments (such as shared space hosts) may not allow these functions, and attempting to call
* these functions will blow up.
* #2 I've heard of performance issues with calling these functions.
* #3 Files support probably doesn't benefit most projects.
* #4 Using exec() or system() is against many PHP developers' religions.
*/
if (self::$useFilesSupport) {
$supportedFinders[] = self::$files;
}
return $supportedFinders;
}
/**
* @param string $namespace
* @return FinderInterface[]
*/
private static function findersWithNamespace($namespace)
{
$findersWithNamespace = array_filter(self::getSupportedFinders(), function (FinderInterface $finder) use ($namespace) {
return $finder->isNamespaceKnown($namespace);
});
return $findersWithNamespace;
}
}
@@ -1,77 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Classmap;
use HaydenPierce\ClassFinder\ClassFinder;
class ClassmapEntry
{
/** @var string */
private $className;
/**
* @param string $fullyQualifiedClassName
*/
public function __construct($fullyQualifiedClassName)
{
$this->className = $fullyQualifiedClassName;
}
/**
* @param string $namespace
* @return bool
*/
public function knowsNamespace($namespace)
{
return strpos($this->className, $namespace) !== false;
}
/**
* @param string $namespace
* @return bool
*/
public function matches($namespace, $options)
{
if ($options === ClassFinder::RECURSIVE_MODE) {
return $this->doesMatchAnyNamespace($namespace);
} else {
return $this->doesMatchDirectNamespace($namespace);
}
}
/**
* @return string
*/
public function getClassName()
{
return $this->className;
}
/**
* Checks if the class is a child or subchild of the given namespace.
*
* @param $namespace
* @return bool
*/
private function doesMatchAnyNamespace($namespace)
{
return strpos($this->getClassName(),$namespace) === 0;
}
/**
* Checks if the class is a DIRECT child of the given namespace.
*
* @param string $namespace
* @return bool
*/
private function doesMatchDirectNamespace($namespace)
{
$classNameFragments = explode('\\', $this->getClassName());
array_pop($classNameFragments);
$classNamespace = implode('\\', $classNameFragments);
$namespace = trim($namespace, '\\');
return $namespace === $classNamespace;
}
}
@@ -1,36 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Classmap;
use HaydenPierce\ClassFinder\AppConfig;
class ClassmapEntryFactory
{
/** @var AppConfig */
private $appConfig;
public function __construct(AppConfig $appConfig)
{
$this->appConfig = $appConfig;
}
/**
* @return ClassmapEntry[]
*/
public function getClassmapEntries()
{
// Composer will compile user declared mappings to autoload_classmap.php. So no additional work is needed
// to fetch user provided entries.
$classmap = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_classmap.php');
// if classmap has no entries return empty array
if(count($classmap) == 0) {
return array();
}
$classmapKeys = array_keys($classmap);
return array_map(function($index) use ($classmapKeys){
return new ClassmapEntry($classmapKeys[$index]);
}, range(0, count($classmap) - 1));
}
}
@@ -1,51 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Classmap;
use HaydenPierce\ClassFinder\FinderInterface;
class ClassmapFinder implements FinderInterface
{
/** @var ClassmapEntryFactory */
private $factory;
public function __construct(ClassmapEntryFactory $factory)
{
$this->factory = $factory;
}
/**
* @param string $namespace
* @return bool
*/
public function isNamespaceKnown($namespace)
{
$classmapEntries = $this->factory->getClassmapEntries();
foreach($classmapEntries as $classmapEntry) {
if ($classmapEntry->knowsNamespace($namespace)) {
return true;
}
}
return false;
}
/**
* @param string $namespace
* @param int $options
* @return string[]
*/
public function findClasses($namespace, $options)
{
$classmapEntries = $this->factory->getClassmapEntries();
$matchingEntries = array_filter($classmapEntries, function(ClassmapEntry $entry) use ($namespace, $options) {
return $entry->matches($namespace, $options);
});
return array_map(function(ClassmapEntry $entry) {
return $entry->getClassName();
}, $matchingEntries);
}
}
@@ -1,8 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Exception;
class ClassFinderException extends \Exception
{
}
@@ -1,101 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Files;
class FilesEntry
{
/** @var string */
private $file;
/** @var string */
private $php;
/**
* @param string $fileToInclude
* @param string $php
*/
public function __construct($fileToInclude, $php)
{
$this->file = $this->normalizePath($fileToInclude);
$this->php = $php;
}
/**
* @param string $namespace
* @return bool
*/
public function knowsNamespace($namespace)
{
$classes = $this->getClassesInFile();
foreach($classes as $class) {
if (strpos($class, $namespace) !== false) {
return true;
};
}
return false;
}
/**
* Gets a list of classes that belong to the given namespace.
*
* @param string $namespace
* @return string[]
*/
public function getClasses($namespace)
{
$classes = $this->getClassesInFile();
return array_values(array_filter($classes, function($class) use ($namespace) {
$classNameFragments = explode('\\', $class);
array_pop($classNameFragments);
$classNamespace = implode('\\', $classNameFragments);
$namespace = trim($namespace, '\\');
return $namespace === $classNamespace;
}));
}
/**
* Dynamically execute files and check for defined classes.
*
* This is where the real magic happens. Since classes in a randomly included file could contain classes in any namespace,
* (or even multiple namespaces!) we must execute the file and check for newly defined classes. This has a potential
* downside that files being executed will execute their side effects - which may be undesirable. However, Composer
* will require these files anyway - so hopefully causing those side effects isn't that big of a deal.
*
* @return array
*/
private function getClassesInFile()
{
// get_declared_classes() returns a bunch of classes that are built into PHP. So we need a control here.
$script = "var_export(get_declared_classes());";
exec($this->php . " -r \"$script\"", $output);
$classes = 'return ' . implode('', $output) . ';';
$initialClasses = eval($classes);
// clear the exec() buffer.
unset($output);
// This brings in the new classes. so $classes here will include the PHP defaults and the newly defined classes
$script = "require_once '{$this->file}'; var_export(get_declared_classes());";
exec($this->php . ' -r "' . $script . '"', $output);
$classes = 'return ' . implode('', $output) . ';';
$allClasses = eval($classes);
return array_diff($allClasses, $initialClasses);
}
/**
* TODO: Similar to PSR4Namespace::normalizePath. Maybe we refactor?
* @param string $path
* @return string
*/
private function normalizePath($path)
{
$path = str_replace('\\', '/', $path);
return $path;
}
}
@@ -1,66 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Files;
use HaydenPierce\ClassFinder\AppConfig;
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
class FilesEntryFactory
{
/** @var AppConfig */
private $appConfig;
public function __construct(AppConfig $appConfig)
{
$this->appConfig = $appConfig;
}
/**
* @return FilesEntry[]
*/
public function getFilesEntries()
{
$files = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_files.php');
$phpPath = $this->findPHP();
$filesKeys = array_values($files);
return array_map(function($index) use ($filesKeys, $phpPath){
return new FilesEntry($filesKeys[$index], $phpPath);
}, range(0, count($files) - 1));
}
/**
* Locates the PHP interrupter.
*
* If PHP 5.4 or newer is used, the PHP_BINARY value is used.
* Otherwise we attempt to find it from shell commands.
*
* @return string
* @throws ClassFinderException
*/
private function findPHP()
{
if (defined("PHP_BINARY")) {
// PHP_BINARY was made available in PHP 5.4
$php = PHP_BINARY;
} else {
$isHostWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
if ($isHostWindows) {
exec('where php', $output);
$php = $output[0];
} else {
exec('which php', $output);
$php = $output[0];
}
}
if (!isset($php)) {
throw new ClassFinderException(sprintf(
'Could not locate PHP interrupter. See "%s" for details.',
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/filesCouldNotLocatePHP.md'
));
}
return $php;
}
}
@@ -1,59 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\Files;
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
use HaydenPierce\ClassFinder\FinderInterface;
class FilesFinder implements FinderInterface
{
/** @var FilesEntryFactory */
private $factory;
/**
* @param FilesEntryFactory $factory
* @throws ClassFinderException
*/
public function __construct(FilesEntryFactory $factory)
{
$this->factory = $factory;
if (!function_exists('exec')) {
throw new ClassFinderException(sprintf(
'FilesFinder requires that exec() is available. Check your php.ini to see if it is disabled. See "%s" for details.',
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/filesExecNotAvailable.md'
));
}
}
/**
* @param string $namespace
* @return bool
*/
public function isNamespaceKnown($namespace)
{
$filesEntries = $this->factory->getFilesEntries();
foreach($filesEntries as $filesEntry) {
if ($filesEntry->knowsNamespace($namespace)) {
return true;
}
}
return false;
}
/**
* @param string $namespace
* @param int $options
* @return string[]
*/
public function findClasses($namespace, $options)
{
$filesEntries = $this->factory->getFilesEntries();
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace){
return array_merge($carry, $entry->getClasses($namespace));
}, array());
}
}
@@ -1,29 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder;
interface FinderInterface
{
/**
* Find classes in a given namespace.
*
* @param string $namespace
* @param int $options
* @return string[]
*/
public function findClasses($namespace, $options);
/**
* Check if a given namespace is known.
*
* A namespace is "known" if a Finder can determine that the autoloader can create classes from that namespace.
*
* For instance:
* If given a classmap for "TestApp1\Foo\Bar\Baz", the namespace "TestApp1\Foo" is known, even if nothing loads
* from that namespace directly. It is known because classes that include that namespace are known.
*
* @param string $namespace
* @return bool
*/
public function isNamespaceKnown($namespace);
}
-106
View File
@@ -1,106 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\PSR4;
use HaydenPierce\ClassFinder\ClassFinder;
use HaydenPierce\ClassFinder\FinderInterface;
class PSR4Finder implements FinderInterface
{
/** @var PSR4NamespaceFactory */
private $factory;
public function __construct(PSR4NamespaceFactory $factory)
{
$this->factory = $factory;
}
/**
* @param string $namespace
* @param int $options
* @return string[]
*/
public function findClasses($namespace, $options)
{
if ($options === ClassFinder::RECURSIVE_MODE) {
$applicableNamespaces = $this->findAllApplicableNamespaces($namespace);
}
if (empty($applicableNamespaces)) {
$bestNamespace = $this->findBestPSR4Namespace($namespace);
$applicableNamespaces = array($bestNamespace);
}
return array_reduce($applicableNamespaces, function($carry, $psr4NamespaceOrNull) use ($namespace, $options) {
if ($psr4NamespaceOrNull instanceof PSR4Namespace) {
$classes = $psr4NamespaceOrNull->findClasses($namespace, $options);
} else {
$classes = array();
}
return array_merge($carry, $classes);
}, array());
}
/**
* @param string $namespace
* @return bool
*/
public function isNamespaceKnown($namespace)
{
$composerNamespaces = $this->factory->getPSR4Namespaces();
foreach($composerNamespaces as $psr4Namespace) {
if ($psr4Namespace->knowsNamespace($namespace)) {
return true;
}
}
return false;
}
/**
* @param string $namespace
* @return PSR4Namespace[]
*/
private function findAllApplicableNamespaces($namespace)
{
$composerNamespaces = $this->factory->getPSR4Namespaces();
return array_filter($composerNamespaces, function(PSR4Namespace $potentialNamespace) use ($namespace){
return $potentialNamespace->isAcceptableNamespaceRecursiveMode($namespace);
});
}
/**
* @param string $namespace
* @return PSR4Namespace
*/
private function findBestPSR4Namespace($namespace)
{
$composerNamespaces = $this->factory->getPSR4Namespaces();
$acceptableNamespaces = array_filter($composerNamespaces, function(PSR4Namespace $potentialNamespace) use ($namespace){
return $potentialNamespace->isAcceptableNamespace($namespace);
});
$carry = new \stdClass();
$carry->highestMatchingSegments = 0;
$carry->bestNamespace = null;
/** @var PSR4Namespace $bestNamespace */
$bestNamespace = array_reduce($acceptableNamespaces, function ($carry, PSR4Namespace $potentialNamespace) use ($namespace) {
$matchingSegments = $potentialNamespace->countMatchingNamespaceSegments($namespace);
if ($matchingSegments > $carry->highestMatchingSegments) {
$carry->highestMatchingSegments = $matchingSegments;
$carry->bestNamespace = $potentialNamespace;
}
return $carry;
}, $carry);
return $bestNamespace->bestNamespace;
}
}
@@ -1,302 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\PSR4;
use HaydenPierce\ClassFinder\ClassFinder;
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
class PSR4Namespace
{
/** @var string */
private $namespace;
/** @var string[] */
private $directories;
/** @var PSR4Namespace[] */
private $directSubnamespaces;
/**
* @param string $namespace
* @param string[] $directories
*/
public function __construct($namespace, $directories)
{
$this->namespace = $namespace;
$this->directories = $directories;
}
/**
* @param string $namespace
* @return bool
*/
public function knowsNamespace($namespace)
{
$numberOfSegments = count(explode('\\', $namespace));
$matchingSegments = $this->countMatchingNamespaceSegments($namespace);
if ($matchingSegments === 0) {
// Provided namespace doesn't map to anything registered.
return false;
} elseif ($numberOfSegments <= $matchingSegments) {
// This namespace is a superset of the provided namespace. Namespace is known.
return true;
} else {
// This namespace is a subset of the provided namespace. We must resolve the remaining segments to a directory.
$relativePath = substr($namespace, strlen($this->namespace));
foreach ($this->directories as $directory) {
$path = $this->normalizePath($directory, $relativePath);
if (is_dir($path)) {
return true;
}
}
return false;
}
}
/**
* Determines how many namespace segments match the internal namespace. This is useful because multiple namespaces
* may technically match a registered namespace root, but one of the matches may be a better match. Namespaces that
* match, but are not _the best_ match are incorrect matches. TestApp1\\ is **not** the best match when searching for
* namespace TestApp1\\Multi\\Foo if TestApp1\\Multi was explicitly registered.
*
* PSR4Namespace $a;
* $a->namespace = "TestApp1\\";
* $a->countMatchingNamespaceSegments("TestApp1\\Multi") -> 1, TestApp1 matches.
*
* PSR4Namespace $b;
* $b->namespace = "TestApp1\\Multi";
* $b->countMatchingNamespaceSegments("TestApp1\\Multi") -> 2, TestApp1\\Multi matches
*
* PSR4Namespace $c;
* $c->namespace = "HaydenPierce\\Foo\\Bar";
* $c->countMatchingNamespaceSegments("TestApp1\\Multi") -> 0, No matches.
*
* @param string $namespace
* @return int
*/
public function countMatchingNamespaceSegments($namespace)
{
$namespaceFragments = explode('\\', $namespace);
$undefinedNamespaceFragments = array();
while($namespaceFragments) {
$possibleNamespace = implode('\\', $namespaceFragments) . '\\';
if(strpos($this->namespace, $possibleNamespace) !== false){
return count(explode('\\', $possibleNamespace)) - 1;
}
array_unshift($undefinedNamespaceFragments, array_pop($namespaceFragments));
}
return 0;
}
/**
* @param string $namespace
* @return bool
*/
public function isAcceptableNamespace($namespace)
{
$namespaceSegments = count(explode('\\', $this->namespace)) - 1;
$matchingSegments = $this->countMatchingNamespaceSegments($namespace);
return $namespaceSegments === $matchingSegments;
}
/**
* @param string $namespace
* @return bool
*/
public function isAcceptableNamespaceRecursiveMode($namespace)
{
// Remove prefix backslash (TODO: review if we do this eariler).
$namespace = ltrim($namespace, '\\');
return strpos($this->namespace, $namespace) === 0;
}
/**
* Used to identify subnamespaces.
*
* @return string[]
*/
public function findDirectories()
{
$self = $this;
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
$path = $self->normalizePath($directory, '');
$realDirectory = realpath($path);
if ($realDirectory !== false) {
return array_merge($carry, array($realDirectory));
} else {
return $carry;
}
}, array());
$arraysOfClasses = array_map(function($directory) use ($self) {
$files = scandir($directory);
return array_map(function($file) use ($directory, $self) {
return $self->normalizePath($directory, $file);
}, $files);
}, $directories);
$potentialDirectories = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
return array_merge($carry, $arrayOfClasses);
}, array());
// Remove '.' and '..' directories
$potentialDirectories = array_filter($potentialDirectories, function($potentialDirectory) {
$segments = explode('/', $potentialDirectory);
$lastSegment = array_pop($segments);
return $lastSegment !== '.' && $lastSegment !== '..';
});
$confirmedDirectories = array_filter($potentialDirectories, function($potentialDirectory) {
return is_dir($potentialDirectory);
});
return $confirmedDirectories;
}
/**
* @param string $namespace
* @param int $options
* @return string[]
*/
public function findClasses($namespace, $options = ClassFinder::STANDARD_MODE)
{
$relativePath = substr($namespace, strlen($this->namespace));
$self = $this;
$directories = array_reduce($this->directories, function($carry, $directory) use ($relativePath, $namespace, $self){
$path = $self->normalizePath($directory, $relativePath);
$realDirectory = realpath($path);
if ($realDirectory !== false) {
return array_merge($carry, array($realDirectory));
} else {
return $carry;
}
}, array());
$arraysOfClasses = array_map(function($directory) {
return scandir($directory);
}, $directories);
$potentialClassFiles = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
return array_merge($carry, $arrayOfClasses);
}, array());
$potentialClasses = array_map(function($file) use ($namespace){
return $namespace . '\\' . str_replace('.php', '', $file);
}, $potentialClassFiles);
if ($options == ClassFinder::RECURSIVE_MODE) {
return $this->getClassesFromListRecursively($namespace);
} else {
return array_filter($potentialClasses, function($potentialClass) {
if (function_exists($potentialClass)) {
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
// Example: DeepCopy\deep_copy
return false;
} else {
return class_exists($potentialClass);
}
});
}
}
/**
* @return string[]
*/
private function getDirectClassesOnly()
{
$self = $this;
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
$path = $self->normalizePath($directory, '');
$realDirectory = realpath($path);
if ($realDirectory !== false) {
return array_merge($carry, array($realDirectory));
} else {
return $carry;
}
}, array());
$arraysOfClasses = array_map(function($directory) {
return scandir($directory);
}, $directories);
$potentialClassFiles = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
return array_merge($carry, $arrayOfClasses);
}, array());
$selfNamespace = $this->namespace; // PHP 5.3 BC
$potentialClasses = array_map(function($file) use ($self, $selfNamespace) {
return $selfNamespace . str_replace('.php', '', $file);
}, $potentialClassFiles);
return array_filter($potentialClasses, function($potentialClass) {
if (function_exists($potentialClass)) {
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
// Example: DeepCopy\deep_copy
return false;
} else {
return class_exists($potentialClass);
}
});
}
/**
* @param string $namespace
* @return string[]
*/
public function getClassesFromListRecursively($namespace)
{
$initialClasses = strpos( $this->namespace, $namespace) !== false ? $this->getDirectClassesOnly() : array();
return array_reduce($this->getDirectSubnamespaces(), function($carry, PSR4Namespace $subNamespace) use ($namespace) {
return array_merge($carry, $subNamespace->getClassesFromListRecursively($namespace));
}, $initialClasses);
}
/**
* Join an absolute path and a relative path in a platform agnostic way.
*
* This method is also extracted so that it can be turned into a vfs:// stream URL for unit testing.
*
* @param string $directory
* @param string $relativePath
* @return mixed
*/
public function normalizePath($directory, $relativePath)
{
$path = str_replace('\\', '/', $directory . '/' . $relativePath);
return $path;
}
/**
* @return PSR4Namespace[]
*/
public function getDirectSubnamespaces()
{
return $this->directSubnamespaces;
}
/**
* @param PSR4Namespace[] $directSubnamespaces
*/
public function setDirectSubnamespaces($directSubnamespaces)
{
$this->directSubnamespaces = $directSubnamespaces;
}
/**
* @return mixed
*/
public function getNamespace()
{
return trim($this->namespace, '\\');
}
}
@@ -1,153 +0,0 @@
<?php
namespace HaydenPierce\ClassFinder\PSR4;
use HaydenPierce\ClassFinder\AppConfig;
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
class PSR4NamespaceFactory
{
/** @var AppConfig */
private $appConfig;
public function __construct(AppConfig $appConfig)
{
$this->appConfig = $appConfig;
}
/**
* @return string[]
*/
public function getPSR4Namespaces()
{
$namespaces = $this->getUserDefinedPSR4Namespaces();
$vendorNamespaces = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_psr4.php');
$namespaces = array_merge($vendorNamespaces, $namespaces);
// There's some wackiness going on here for PHP 5.3 compatibility.
$names = array_keys($namespaces);
$directories = array_values($namespaces);
$self = $this;
$namespaces = array_map(function($index) use ($self, $names, $directories) {
return $self->createNamespace($names[$index], $directories[$index]);
},range(0, count($namespaces) - 1));
return $namespaces;
}
/**
* @return string[]
*/
private function getUserDefinedPSR4Namespaces()
{
$appRoot = $this->appConfig->getAppRoot();
$composerJsonPath = $appRoot . 'composer.json';
$composerConfig = json_decode(file_get_contents($composerJsonPath));
if (!isset($composerConfig->autoload)) {
return array();
}
//Apparently PHP doesn't like hyphens, so we use variable variables instead.
$psr4 = "psr-4";
return (array)$composerConfig->autoload->$psr4;
}
/**
* Creates a namespace from composer_psr4.php and composer.json autoload.psr4 items.
*
* @param string $namespace
* @param string[] $directories
* @return PSR4Namespace
* @throws ClassFinderException
*/
public function createNamespace($namespace, $directories)
{
if (is_string($directories)) {
// This is an acceptable format according to composer.json
$directories = array($directories);
} elseif (is_array($directories)) {
// composer_psr4.php seems to put everything in this format
} else {
throw new ClassFinderException('Unknown PSR4 definition.');
}
$self = $this;
$appConfig = $this->appConfig;
$directories = array_map(function($directory) use ($self, $appConfig) {
if ($self->isAbsolutePath($directory)) {
return $directory;
} else {
return $appConfig->getAppRoot() . $directory;
}
}, $directories);
$directories = array_filter(array_map(function($directory) {
return realpath($directory);
}, $directories));
$psr4Namespace = new PSR4Namespace($namespace, $directories);
$subNamespaces = $this->getSubnamespaces($psr4Namespace);
$psr4Namespace->setDirectSubnamespaces($subNamespaces);
return $psr4Namespace;
}
/**
* @param PSR4Namespace $psr4Namespace
* @return PSR4Namespace[]
*/
private function getSubnamespaces(PSR4Namespace $psr4Namespace)
{
// Scan it's own directories.
$directories = $psr4Namespace->findDirectories();
$self = $this;
$subnamespaces = array_map(function($directory) use ($self, $psr4Namespace){
$segments = explode('/', $directory);
$subnamespaceSegment = array_pop($segments);
$namespace = $psr4Namespace->getNamespace() . "\\" . $subnamespaceSegment . "\\";
return $self->createNamespace($namespace, $directory);
}, $directories);
return $subnamespaces;
}
/**
* Check if a path is absolute.
*
* Mostly this answer https://stackoverflow.com/a/38022806/3000068
* A few changes: Changed exceptions to be ClassFinderExceptions, removed some ctype dependencies,
* updated the root prefix regex to handle Window paths better.
*
* @param string $path
* @return bool
* @throws ClassFinderException
*/
public function isAbsolutePath($path) {
if (!is_string($path)) {
$mess = sprintf('String expected but was given %s', gettype($path));
throw new ClassFinderException($mess);
}
// Optional wrapper(s).
$regExp = '%^(?<wrappers>(?:[[:print:]]{2,}://)*)';
// Optional root prefix.
$regExp .= '(?<root>(?:[[:alpha:]]:[/\\\\]|/)?)';
// Actual path.
$regExp .= '(?<path>(?:[[:print:]]*))$%';
$parts = array();
if (!preg_match($regExp, $path, $parts)) {
$mess = sprintf('Path is NOT valid, was given %s', $path);
throw new ClassFinderException($mess);
}
if ('' !== $parts['root']) {
return true;
}
return false;
}
}
-5
View File
@@ -1,5 +0,0 @@
preset: laravel
risky: true
enabled:
- declare_strict_types
- unalign_double_arrow
-13
View File
@@ -1,13 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 1.0.0
### Added
- Add `RequestParser` to convert an incoming HTTP request to one or more `OperationParams`
-16
View File
@@ -1,16 +0,0 @@
The MIT License (MIT)
Copyright (c) 2019 Benedikt Franke
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-39
View File
@@ -1,39 +0,0 @@
# laragraph/utils
[![CI Status](https://github.com/laragraph/utils/workflows/Continuous%20Integration/badge.svg)](https://github.com/laragraph/utils/actions)
[![codecov](https://codecov.io/gh/laragraph/utils/branch/master/graph/badge.svg)](https://codecov.io/gh/laragraph/utils)
[![StyleCI](https://github.styleci.io/repos/228471198/shield?branch=master)](https://github.styleci.io/repos/228471198)
[![Latest Stable Version](https://poser.pugx.org/laragraph/utils/v/stable)](https://packagist.org/packages/laragraph/utils)
[![Total Downloads](https://poser.pugx.org/laragraph/utils/downloads)](https://packagist.org/packages/laragraph/utils)
Utilities for using GraphQL with Laravel
## Installation
Install through composer
```bash
composer require laragraph/utils
```
## Usage
This package holds basic utilities that are useful for building a GraphQL server with Laravel.
If you want to build an application, we recommend using a full framework that integrates the
primitives within this package:
- SDL-first: [Lighthouse](https://github.com/nuwave/lighthouse)
- Code-first: [graphql-laravel](https://github.com/rebing/graphql-laravel)
## Changelog
See [`CHANGELOG.md`](CHANGELOG.md).
## Contributing
See [`CONTRIBUTING.md`](.github/CONTRIBUTING.md).
## License
This package is licensed using the MIT License.
-55
View File
@@ -1,55 +0,0 @@
{
"name": "laragraph/utils",
"type": "library",
"description": "Utilities for using GraphQL with Laravel",
"homepage": "https://github.com/laragraph/utils",
"license": "MIT",
"authors": [
{
"name": "Benedikt Franke",
"email": "benedikt@franke.tech"
}
],
"require": {
"php": "^7.2 || ^8.0",
"illuminate/contracts": "5.6.* || 5.7.* || 5.8.* || ^6 || ^7 || ^8",
"illuminate/http": "5.6.* || 5.7.* || 5.8.* || ^6 || ^7 || ^8",
"thecodingmachine/safe": "^1.1",
"webonyx/graphql-php": "^0.13.2 || ^14"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.11",
"infection/infection": "~0.20",
"jangregor/phpstan-prophecy": "^0.8.1",
"orchestra/testbench": "3.6.* || 3.7.* || 3.8.* || 3.9.* || ^4 || ^5 || ^6",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12.57",
"phpstan/phpstan-deprecation-rules": "^0.12.5",
"phpstan/phpstan-strict-rules": "^0.12.5",
"phpunit/phpunit": "^7.5 || ^8.5",
"thecodingmachine/phpstan-safe-rule": "^1.0"
},
"config": {
"preferred-install": "dist",
"sort-packages": true
},
"autoload": {
"psr-4": {
"Laragraph\\Utils\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Laragraph\\Utils\\Tests\\": "tests/"
},
"files": [
"vendor/symfony/var-dumper/Resources/functions/dump.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"support": {
"issues": "https://github.com/laragraph/utils/issues",
"source": "https://github.com/laragraph/utils"
}
}
-118
View File
@@ -1,118 +0,0 @@
<?php
declare(strict_types=1);
namespace Laragraph\Utils;
use GraphQL\Server\Helper;
use GraphQL\Server\OperationParams;
use GraphQL\Server\RequestError;
use GraphQL\Utils\Utils;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class RequestParser
{
/**
* @var \GraphQL\Server\Helper
*/
protected $helper;
public function __construct()
{
$this->helper = new Helper();
}
/**
* Converts an incoming HTTP request to one or more OperationParams.
*
* @return \GraphQL\Server\OperationParams|array<int, \GraphQL\Server\OperationParams>
*
* @throws \GraphQL\Server\RequestError
*/
public function parseRequest(Request $request)
{
$method = $request->getMethod();
$bodyParams = [];
/** @var array<string, mixed> $queryParams */
$queryParams = $request->query();
if ($method === 'POST') {
/**
* Never null, since Symfony defaults to application/x-www-form-urlencoded.
*
* @var string $contentType
*/
$contentType = $request->header('Content-Type');
if (stripos($contentType, 'application/json') !== false) {
/** @var string $content */
$content = $request->getContent();
$bodyParams = \Safe\json_decode($content, true);
if (! is_array($bodyParams)) {
throw new RequestError(
'GraphQL Server expects JSON object or array, but got '.
Utils::printSafeJson($bodyParams)
);
}
} elseif (stripos($contentType, 'application/graphql') !== false) {
/** @var string $content */
$content = $request->getContent();
$bodyParams = ['query' => $content];
} elseif (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
/** @var array<string, mixed> $bodyParams */
$bodyParams = $request->post();
} elseif (stripos($contentType, 'multipart/form-data') !== false) {
$bodyParams = $this->inlineFiles($request);
} else {
throw new RequestError('Unexpected content type: '.Utils::printSafeJson($contentType));
}
}
return $this->helper->parseRequestParams($method, $bodyParams, $queryParams);
}
/**
* Inline file uploads given through a multipart request.
*
* @param \Illuminate\Http\Request $request
* @return array<mixed>
*/
protected function inlineFiles(Request $request): array
{
/** @var string|null $mapParam */
$mapParam = $request->post('map');
if ($mapParam === null) {
throw new RequestError(
'Could not find a valid map, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec'
);
}
/** @var string|null $operationsParam */
$operationsParam = $request->post('operations');
if ($operationsParam === null) {
throw new RequestError(
'Could not find valid operations, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec'
);
}
/** @var array<string, mixed>|array<int, array<string, mixed>> $operations */
$operations = \Safe\json_decode($operationsParam, true);
/** @var array<int|string, array<int, string>> $map */
$map = \Safe\json_decode($mapParam, true);
foreach ($map as $fileKey => $operationsPaths) {
/** @var array<string> $operationsPaths */
$file = $request->file((string) $fileKey);
/** @var string $operationsPath */
foreach ($operationsPaths as $operationsPath) {
Arr::set($operations, $operationsPath, $file);
}
}
return $operations;
}
}
-253
View File
@@ -1,253 +0,0 @@
<?php
declare(strict_types=1);
namespace Laragraph\Utils\Tests\Unit;
use GraphQL\Server\RequestError;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Laragraph\Utils\RequestParser;
use Orchestra\Testbench\TestCase;
use Safe\Exceptions\JsonException;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class RequestParserTest extends TestCase
{
public function testGetWithQuery(): void
{
$query = /** @lang GraphQL */ '{ foo }';
$request = $this->makeRequest('GET', ['query' => $query]);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame($query, $params->query);
}
public function testPostWithJson(): void
{
$query = /** @lang GraphQL */ '{ foo }';
$request = $this->makeRequest(
'POST',
[],
[],
['Content-Type' => 'application/json'],
\Safe\json_encode(['query' => $query])
);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame($query, $params->query);
}
public function testPostWithQueryApplicationGraphQL(): void
{
$query = /** @lang GraphQL */ '{ foo }';
$request = $this->makeRequest(
'POST',
[],
[],
['Content-Type' => 'application/graphql'],
$query
);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame($query, $params->query);
}
public function testPostWithRegularForm(): void
{
$query = /** @lang GraphQL */ '{ foo }';
$request = $this->makeRequest(
'POST',
['query' => $query],
[],
['Content-Type' => 'application/x-www-form-urlencoded']
);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame($query, $params->query);
}
public function testPostDefaultsToRegularForm(): void
{
$query = /** @lang GraphQL */ '{ foo }';
$request = $this->makeRequest(
'POST',
['query' => $query]
);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame($query, $params->query);
}
public function testNonSensicalContentType(): void
{
$request = $this->makeRequest(
'POST',
[],
[],
['Content-Type' => 'foobar']
);
$parser = new RequestParser();
$this->expectException(RequestError::class);
$parser->parseRequest($request);
}
public function testNoQuery(): void
{
$request = $this->makeRequest('GET');
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame(null, $params->query);
}
public function testInvalidJson(): void
{
$request = $this->makeRequest(
'POST',
[],
[],
['Content-Type' => 'application/json'],
'this is not valid json'
);
$parser = new RequestParser();
$this->expectException(JsonException::class);
$parser->parseRequest($request);
}
public function testNonArrayJson(): void
{
$request = $this->makeRequest(
'POST',
[],
[],
['Content-Type' => 'application/json'],
'"this should be a map with query, variables, etc."'
);
$parser = new RequestParser();
$this->expectException(RequestError::class);
$parser->parseRequest($request);
}
public function testMultipartFormRequest(): void
{
$file = UploadedFile::fake()->create('image.jpg', 500);
$request = $this->makeRequest(
'POST',
[
'operations' => /** @lang JSON */ '
{
"query": "mutation Upload($file: Upload!) { upload(file: $file) }",
"variables": {
"file": null
}
}
',
'map' => /** @lang JSON */ '
{
"0": ["variables.file"]
}
',
],
[
'0' => $file,
],
[
'Content-Type' => 'multipart/form-data',
]
);
$parser = new RequestParser();
/** @var \GraphQL\Server\OperationParams $params */
$params = $parser->parseRequest($request);
self::assertSame('mutation Upload($file: Upload!) { upload(file: $file) }', $params->query);
$variables = $params->variables;
self::assertNotNull($variables);
/** @var array<string, mixed> $variables */
self::assertSame($file, $variables['file']);
}
public function testMultipartFormWithoutMap(): void
{
$request = $this->makeRequest(
'POST',
[],
[],
[
'Content-Type' => 'multipart/form-data',
]
);
$parser = new RequestParser();
$this->expectException(RequestError::class);
$parser->parseRequest($request);
}
public function testMultipartFormWithoutOperations(): void
{
$request = $this->makeRequest(
'POST',
[
'map' => /** @lang JSON */ '
{
"0": ["variables.file"]
}
',
],
[],
[
'Content-Type' => 'multipart/form-data',
]
);
$parser = new RequestParser();
$this->expectException(RequestError::class);
$parser->parseRequest($request);
}
/**
* @param string $method
* @param array<mixed> $parameters
* @param array<mixed> $files
* @param array<mixed> $headers
* @param string|resource|null $content
* @return \Illuminate\Http\Request
*/
public function makeRequest(string $method, array $parameters = [], array $files = [], array $headers = [], $content = null): Request
{
$symfonyRequest = SymfonyRequest::create(
'http://foo.bar/graphql',
$method,
$parameters,
[],
$files,
$this->transformHeadersToServerVars($headers),
$content
);
return Request::createFromBase($symfonyRequest);
}
}
-3
View File
@@ -1,3 +0,0 @@
composer.lock
composer.phar
/vendor/
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-13
View File
@@ -1,13 +0,0 @@
Container interface
==============
This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url].
Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: https://www.php-fig.org/psr/psr-11/
[package-url]: https://packagist.org/packages/psr/container
[implementation-url]: https://packagist.org/providers/psr/container-implementation
-22
View File
@@ -1,22 +0,0 @@
{
"name": "psr/container",
"type": "library",
"description": "Common Container Interface (PHP FIG PSR-11)",
"keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
"homepage": "https://github.com/php-fig/container",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=7.2.0"
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
}
}
@@ -1,10 +0,0 @@
<?php
namespace Psr\Container;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
{
}
-36
View File
@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id);
}
-10
View File
@@ -1,10 +0,0 @@
<?php
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
-15
View File
@@ -1,15 +0,0 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab
-2
View File
@@ -1,2 +0,0 @@
/vendor/
composer.lock
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 PHP-FIG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-6
View File
@@ -1,6 +0,0 @@
PSR Event Dispatcher
====================
This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/).
Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details.
-26
View File
@@ -1,26 +0,0 @@
{
"name": "psr/event-dispatcher",
"description": "Standard interfaces for event handling.",
"type": "library",
"keywords": ["psr", "psr-14", "events"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=7.2.0"
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}
@@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Defines a dispatcher for events.
*/
interface EventDispatcherInterface
{
/**
* Provide all relevant listeners with an event to process.
*
* @param object $event
* The object to process.
*
* @return object
* The Event that was passed, now modified by listeners.
*/
public function dispatch(object $event);
}
@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Mapper from an event to the listeners that are applicable to that event.
*/
interface ListenerProviderInterface
{
/**
* @param object $event
* An event for which to return the relevant listeners.
* @return iterable[callable]
* An iterable (array, iterator, or generator) of callables. Each
* callable MUST be type-compatible with $event.
*/
public function getListenersForEvent(object $event) : iterable;
}
@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* An Event whose processing may be interrupted when the event has been handled.
*
* A Dispatcher implementation MUST check to determine if an Event
* is marked as stopped after each listener is called. If it is then it should
* return immediately without calling any further Listeners.
*/
interface StoppableEventInterface
{
/**
* Is propagation stopped?
*
* This will typically only be used by the Dispatcher to determine if the
* previous listener halted propagation.
*
* @return bool
* True if the Event is complete and no further listeners should be called.
* False to continue calling listeners.
*/
public function isPropagationStopped() : bool;
}
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2012 PHP Framework Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-128
View File
@@ -1,128 +0,0 @@
<?php
namespace Psr\Log;
/**
* This is a simple Logger implementation that other Loggers can inherit from.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
abstract class AbstractLogger implements LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}
-7
View File
@@ -1,7 +0,0 @@
<?php
namespace Psr\Log;
class InvalidArgumentException extends \InvalidArgumentException
{
}
-18
View File
@@ -1,18 +0,0 @@
<?php
namespace Psr\Log;
/**
* Describes log levels.
*/
class LogLevel
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
-18
View File
@@ -1,18 +0,0 @@
<?php
namespace Psr\Log;
/**
* Describes a logger-aware instance.
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object.
*
* @param LoggerInterface $logger
*
* @return void
*/
public function setLogger(LoggerInterface $logger);
}
-26
View File
@@ -1,26 +0,0 @@
<?php
namespace Psr\Log;
/**
* Basic Implementation of LoggerAwareInterface.
*/
trait LoggerAwareTrait
{
/**
* The logger instance.
*
* @var LoggerInterface|null
*/
protected $logger;
/**
* Sets a logger.
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
-125
View File
@@ -1,125 +0,0 @@
<?php
namespace Psr\Log;
/**
* Describes a logger instance.
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data. The only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param mixed[] $context
*
* @return void
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param mixed[] $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, $message, array $context = array());
}
-142
View File
@@ -1,142 +0,0 @@
<?php
namespace Psr\Log;
/**
* This is a simple Logger trait that classes unable to extend AbstractLogger
* (because they extend another class, etc) can include.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
trait LoggerTrait
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
abstract public function log($level, $message, array $context = array());
}
-30
View File
@@ -1,30 +0,0 @@
<?php
namespace Psr\Log;
/**
* This Logger can be used to avoid conditional log calls.
*
* Logging should always be optional, and if no logger is provided to your
* library creating a NullLogger instance to have something to throw logs at
* is a good way to avoid littering your code with `if ($this->logger) { }`
* blocks.
*/
class NullLogger extends AbstractLogger
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, $message, array $context = array())
{
// noop
}
}
-18
View File
@@ -1,18 +0,0 @@
<?php
namespace Psr\Log\Test;
/**
* This class is internal and does not follow the BC promise.
*
* Do NOT use this class in any way.
*
* @internal
*/
class DummyTest
{
public function __toString()
{
return 'DummyTest';
}
}
-138
View File
@@ -1,138 +0,0 @@
<?php
namespace Psr\Log\Test;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use PHPUnit\Framework\TestCase;
/**
* Provides a base test class for ensuring compliance with the LoggerInterface.
*
* Implementors can extend the class and implement abstract methods to run this
* as part of their test suite.
*/
abstract class LoggerInterfaceTest extends TestCase
{
/**
* @return LoggerInterface
*/
abstract public function getLogger();
/**
* This must return the log messages in order.
*
* The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
*
* Example ->error('Foo') would yield "error Foo".
*
* @return string[]
*/
abstract public function getLogs();
public function testImplements()
{
$this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
}
/**
* @dataProvider provideLevelsAndMessages
*/
public function testLogsAtAllLevels($level, $message)
{
$logger = $this->getLogger();
$logger->{$level}($message, array('user' => 'Bob'));
$logger->log($level, $message, array('user' => 'Bob'));
$expected = array(
$level.' message of level '.$level.' with context: Bob',
$level.' message of level '.$level.' with context: Bob',
);
$this->assertEquals($expected, $this->getLogs());
}
public function provideLevelsAndMessages()
{
return array(
LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
);
}
/**
* @expectedException \Psr\Log\InvalidArgumentException
*/
public function testThrowsOnInvalidLevel()
{
$logger = $this->getLogger();
$logger->log('invalid level', 'Foo');
}
public function testContextReplacement()
{
$logger = $this->getLogger();
$logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
$expected = array('info {Message {nothing} Bob Bar a}');
$this->assertEquals($expected, $this->getLogs());
}
public function testObjectCastToString()
{
if (method_exists($this, 'createPartialMock')) {
$dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
} else {
$dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
}
$dummy->expects($this->once())
->method('__toString')
->will($this->returnValue('DUMMY'));
$this->getLogger()->warning($dummy);
$expected = array('warning DUMMY');
$this->assertEquals($expected, $this->getLogs());
}
public function testContextCanContainAnything()
{
$closed = fopen('php://memory', 'r');
fclose($closed);
$context = array(
'bool' => true,
'null' => null,
'string' => 'Foo',
'int' => 0,
'float' => 0.5,
'nested' => array('with object' => new DummyTest),
'object' => new \DateTime,
'resource' => fopen('php://memory', 'r'),
'closed' => $closed,
);
$this->getLogger()->warning('Crazy context data', $context);
$expected = array('warning Crazy context data');
$this->assertEquals($expected, $this->getLogs());
}
public function testContextExceptionKeyCanBeExceptionOrOtherValues()
{
$logger = $this->getLogger();
$logger->warning('Random message', array('exception' => 'oops'));
$logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
$expected = array(
'warning Random message',
'critical Uncaught Exception!'
);
$this->assertEquals($expected, $this->getLogs());
}
}
-147
View File
@@ -1,147 +0,0 @@
<?php
namespace Psr\Log\Test;
use Psr\Log\AbstractLogger;
/**
* Used for testing purposes.
*
* It records all records and gives you access to them for verification.
*
* @method bool hasEmergency($record)
* @method bool hasAlert($record)
* @method bool hasCritical($record)
* @method bool hasError($record)
* @method bool hasWarning($record)
* @method bool hasNotice($record)
* @method bool hasInfo($record)
* @method bool hasDebug($record)
*
* @method bool hasEmergencyRecords()
* @method bool hasAlertRecords()
* @method bool hasCriticalRecords()
* @method bool hasErrorRecords()
* @method bool hasWarningRecords()
* @method bool hasNoticeRecords()
* @method bool hasInfoRecords()
* @method bool hasDebugRecords()
*
* @method bool hasEmergencyThatContains($message)
* @method bool hasAlertThatContains($message)
* @method bool hasCriticalThatContains($message)
* @method bool hasErrorThatContains($message)
* @method bool hasWarningThatContains($message)
* @method bool hasNoticeThatContains($message)
* @method bool hasInfoThatContains($message)
* @method bool hasDebugThatContains($message)
*
* @method bool hasEmergencyThatMatches($message)
* @method bool hasAlertThatMatches($message)
* @method bool hasCriticalThatMatches($message)
* @method bool hasErrorThatMatches($message)
* @method bool hasWarningThatMatches($message)
* @method bool hasNoticeThatMatches($message)
* @method bool hasInfoThatMatches($message)
* @method bool hasDebugThatMatches($message)
*
* @method bool hasEmergencyThatPasses($message)
* @method bool hasAlertThatPasses($message)
* @method bool hasCriticalThatPasses($message)
* @method bool hasErrorThatPasses($message)
* @method bool hasWarningThatPasses($message)
* @method bool hasNoticeThatPasses($message)
* @method bool hasInfoThatPasses($message)
* @method bool hasDebugThatPasses($message)
*/
class TestLogger extends AbstractLogger
{
/**
* @var array
*/
public $records = [];
public $recordsByLevel = [];
/**
* @inheritdoc
*/
public function log($level, $message, array $context = [])
{
$record = [
'level' => $level,
'message' => $message,
'context' => $context,
];
$this->recordsByLevel[$record['level']][] = $record;
$this->records[] = $record;
}
public function hasRecords($level)
{
return isset($this->recordsByLevel[$level]);
}
public function hasRecord($record, $level)
{
if (is_string($record)) {
$record = ['message' => $record];
}
return $this->hasRecordThatPasses(function ($rec) use ($record) {
if ($rec['message'] !== $record['message']) {
return false;
}
if (isset($record['context']) && $rec['context'] !== $record['context']) {
return false;
}
return true;
}, $level);
}
public function hasRecordThatContains($message, $level)
{
return $this->hasRecordThatPasses(function ($rec) use ($message) {
return strpos($rec['message'], $message) !== false;
}, $level);
}
public function hasRecordThatMatches($regex, $level)
{
return $this->hasRecordThatPasses(function ($rec) use ($regex) {
return preg_match($regex, $rec['message']) > 0;
}, $level);
}
public function hasRecordThatPasses(callable $predicate, $level)
{
if (!isset($this->recordsByLevel[$level])) {
return false;
}
foreach ($this->recordsByLevel[$level] as $i => $rec) {
if (call_user_func($predicate, $rec, $i)) {
return true;
}
}
return false;
}
public function __call($method, $args)
{
if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
$genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
$level = strtolower($matches[2]);
if (method_exists($this, $genericMethod)) {
$args[] = $level;
return call_user_func_array([$this, $genericMethod], $args);
}
}
throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
}
public function reset()
{
$this->records = [];
$this->recordsByLevel = [];
}
}
-58
View File
@@ -1,58 +0,0 @@
PSR Log
=======
This repository holds all interfaces/classes/traits related to
[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
Note that this is not a logger of its own. It is merely an interface that
describes a logger. See the specification for more details.
Installation
------------
```bash
composer require psr/log
```
Usage
-----
If you need a logger, you can use the interface like this:
```php
<?php
use Psr\Log\LoggerInterface;
class Foo
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
public function doSomething()
{
if ($this->logger) {
$this->logger->info('Doing work');
}
try {
$this->doSomethingElse();
} catch (Exception $exception) {
$this->logger->error('Oh no!', array('exception' => $exception));
}
// do something useful
}
}
```
You can then pick one of the implementations of the interface to get a logger.
If you want to implement the interface, you can require this package and
implement `Psr\Log\LoggerInterface` in your code. Please read the
[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
for details.
-26
View File
@@ -1,26 +0,0 @@
{
"name": "psr/log",
"description": "Common interface for logging libraries",
"keywords": ["psr", "psr-3", "log"],
"homepage": "https://github.com/php-fig/log",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}
-12
View File
@@ -1,12 +0,0 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
-21
View File
@@ -1,21 +0,0 @@
# The MIT License (MIT)
Copyright (c) 2016 PHP Framework Interoperability Group
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
-8
View File
@@ -1,8 +0,0 @@
PHP FIG Simple Cache PSR
========================
This repository holds all interfaces related to PSR-16.
Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details.
You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package.
-25
View File
@@ -1,25 +0,0 @@
{
"name": "psr/simple-cache",
"description": "Common interfaces for simple caching",
"keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}
-10
View File
@@ -1,10 +0,0 @@
<?php
namespace Psr\SimpleCache;
/**
* Interface used for all types of exceptions thrown by the implementing library.
*/
interface CacheException
{
}
-114
View File
@@ -1,114 +0,0 @@
<?php
namespace Psr\SimpleCache;
interface CacheInterface
{
/**
* Fetches a value from the cache.
*
* @param string $key The unique key of this item in the cache.
* @param mixed $default Default value to return if the key does not exist.
*
* @return mixed The value of the item from the cache, or $default in case of cache miss.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
public function get($key, $default = null);
/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param string $key The key of the item to store.
* @param mixed $value The value of the item to store, must be serializable.
* @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @return bool True on success and false on failure.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
public function set($key, $value, $ttl = null);
/**
* Delete an item from the cache by its unique key.
*
* @param string $key The unique cache key of the item to delete.
*
* @return bool True if the item was successfully removed. False if there was an error.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
public function delete($key);
/**
* Wipes clean the entire cache's keys.
*
* @return bool True on success and false on failure.
*/
public function clear();
/**
* Obtains multiple cache items by their unique keys.
*
* @param iterable $keys A list of keys that can obtained in a single operation.
* @param mixed $default Default value to return for keys that do not exist.
*
* @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
public function getMultiple($keys, $default = null);
/**
* Persists a set of key => value pairs in the cache, with an optional TTL.
*
* @param iterable $values A list of key => value pairs for a multiple-set operation.
* @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @return bool True on success and false on failure.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $values is neither an array nor a Traversable,
* or if any of the $values are not a legal value.
*/
public function setMultiple($values, $ttl = null);
/**
* Deletes multiple cache items in a single operation.
*
* @param iterable $keys A list of string-based keys to be deleted.
*
* @return bool True if the items were successfully removed. False if there was an error.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
public function deleteMultiple($keys);
/**
* Determines whether an item is present in the cache.
*
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it making the state of your app out of date.
*
* @param string $key The cache item key.
*
* @return bool
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
public function has($key);
}
@@ -1,13 +0,0 @@
<?php
namespace Psr\SimpleCache;
/**
* Exception interface for invalid cache arguments.
*
* When an invalid argument is passed it must throw an exception which implements
* this interface
*/
interface InvalidArgumentException extends CacheException
{
}
+219
View File
@@ -0,0 +1,219 @@
# Changelog
## 7.0.1
* [FIXED] Infinite recursion in `presence_auth`.
## 7.0.0
* [DEPRECATED] `get_channel_info`, `get_channels`, `socket_auth`, `presence_auth` in favour of camelCased versions
* [DEPRECATED] `get_users_info` in favour of `getPresenceUsers`
* [DEPRECATED] `ensure_valid_signature` in favour of `verifySignature`
* [CHANGED] Restrict `$app_id` parameter of the `Pusher()` object to `string` (`int` was possible).
* [ADDED] Return types.
* [ADDED] Namespacing, PSR-12 formatting.
## 6.1.0
* [ADDED] triggerAsync and triggerBatchAsync using the Guzzle async interface.
## 6.0.1
* [CHANGED] Use type hints where possible (mixed type not available in PHP7).
* [CHANGED] Document that functions can throw GuzzleException.
## 6.0.0
* [CHANGED] internal HTTP client to Guzzle
* [ADDED] optional client parameter to constructor
* [CHANGED] useTLS is true by default
* [REMOVED] `curl_options` from options
* [REMOVED] customer logger
* [REMOVED] host, port and timeout constructor parameters
* [REMOVED] support for PHP 7.1
* [CHANGED] lower severity level of logging to DEBUG level
## 5.0.3
* [CHANGED] Ensure version in Pusher.php is bumped on release.
## 5.0.2
* [CHANGED] Add release automation actions.
## 5.0.1
* [FIXED] Notice raised due to reference to potentially missing object property in `trigger` method
## 5.0.0
* [CHANGED] The methods that make HTTP requests now throw an `ApiErrorException` instead of returning `false` for non-2xx responses
* [CHANGED] `trigger` now accepts a `$params` associative array instead of a `$socket_id` as the third parameter
* [ADDED] Support for requesting channel attributes as part of a `trigger` and `triggerBatch` request via an `info` parameter
* [REMOVED] `debug` parameter from methods that make HTTP requests and from the constructor options
* [REMOVED] Support for legacy push notifications (this has been superseded by https://github.com/pusher/push-notifications-php)
## 4.1.5
* [ADDED] Support for PHP 8.
## 4.1.4
* [FIXED] Errors in the failure path of `get_...` methods revealed by stricter type checking in PHP7.4
## 4.1.3
* No functional change, previous release was only partially successful
## 4.1.2
* [ADDED] option `encryption_master_key_base64`
* [DEPRECATED] option `encryption_master_key`
## 4.1.1
* [ADDED] Support for PHP 7.4.
## 4.1.0
* [ADDED] `path` configuration option.
## 4.0.0
* [REMOVED] Support for PHP 5.x, PHP 7.0 and HHVM.
## 3.4.1
* [ADDED] Support for PHP 7.3.
## 3.4.0
* [ADDED] `get_users_info` method.
## 3.3.1
* [FIXED] PHP Notice for Undefined `socket_id` in triggerBatch
## 3.3.0
* [ADDED] Support for End-to-end encrypted channels for triggerbatch
* [FIXED] trigger behavior with mixtures of encrypted and non-encrypted channels
## 3.2.0
* [ADDED] This release adds support for end to end encrypted channels, a new feature for Channels. Read more [in our docs](https://pusher.com/docs/client_api_guide/client_encrypted_channels).
* [DEPRECATED] Renamed `encrypted` option to `useTLS` - `encrypted` will still work!
## 3.1.0
* [ADDED] This release adds Webhook validation as well as a data structure to store Webhook payloads.
## 3.0.4
* [FIXED] Non zero indexed arrays of channels no longer get serialized as an object.
## 3.0.3
* [ADDED] PSR-3 logger compatibility.
* [CHANGED] Improved PHP docs.
## 3.0.2
* [FIXED] Insufficient check for un-initialized curl resource.
* [FIXED] Acceptance tests.
## 3.0.1
* [CHANGED] Info messages are now prefixed with INFO and errors are now prefixed with ERROR.
## 3.0.0
* [NEW] Added namespaces (thanks [@vinkla](https://github.com/vinkla)).
## 2.6.4
* [FIXED] Log the curl error in more circumstances
## 2.6.1
* [FIXED] Check for correct status code when POSTing to native push notifications API.
## 2.6.0
* [ADDED] support for publishing push notifications on up to 10 interests.
## 2.5.0
* [REMOVED] Native push notifications payload validation in the client.
## 2.5.0-rc2
* [FIXED] DDN and Native Push endpoints were not assembled correctly.
## 2.5.0-rc1
* [NEW] Native push notifications
## 2.4.2
* [CHANGED] One curl instance per Pusher instance
## 2.4.1
* [FIXED] Presence data could not be submitted after the style changes
## 2.4.0
* [ADDED] Support for batch events
* [ADDED] Curl options
* [FIXED] Applied fixes from StyleCI
## 2.3.0
* [ADDED] A new `cluster` option for the Pusher constructor.
## 2.2.2
* [FIXED] Fixed a PHP 5.2 incompatibility caused by referencing a private method in array_walk.
## 2.2.1
* [FIXED] Channel name and socket_id values are now validated.
* [BROKE] Inadvertently broke PHP 5.2 compatibility by referencing a private method in array_walk.
## 2.2.0
* [CHANGED] `new Pusher($app_key, $app_secret, $app_id, $options)` - The `$options` parameter
has been added as the forth parameter to the constructor and other additional
parameters are now deprecated.
## 2.1.3
* [NEW] `$pusher->trigger` can now take an `array` of channel names as a first parameter to allow the same event to be published on multiple channels.
* [NEW] `$pusher->get` generic function can be used to make `GET` calls to the REST API
* [NEW] `$pusher->set_logger` to allow internal logging to be exposed and logged in your own logs.
## 2.1.2
* [CHANGED] Debug response from `$pusher->trigger` call is now an associative array in the form `array( 'body' => '{String} body text of response', 'status' => '{Number} http status of the response' )`
## 2.1.1
* [CHANGED] Added optional $options parameter to get_channel_info. get_channel_info($channel, $options = array() )
## 2.1.0
* [CHANGED] Renamed get_channel_stats to get_channel_info
* [CHANGED] get_channels now takes and $options parameter. get_channels( $options = array() )
* [REMOVED] get_presence_channels
## 2.0.1
* [FIXED] Overwritten socket_id parameter in trigger: https://github.com/pusher/pusher-php-server/pull/3
## 2.0.0
* [NEW] Versioning introduced at 2.0.0
* [NEW] Added composer.json for submission to http://packagist.org/
* [CHANGED] `get_channels()` now returns an object which has a `channels` property. This must be accessed to get the Array of channels in an application.
* [CHANGED] `get_presence_channels()` now returns an object which has a `channels` property. This must be accessed to get the Array of channels in an application.
+36
View File
@@ -0,0 +1,36 @@
{
"name": "pusher/pusher-php-server",
"description" : "Library for interacting with the Pusher REST API",
"keywords": ["php-pusher-server", "pusher", "rest", "realtime", "real-time", "real time", "messaging", "push", "trigger", "publish", "events"],
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"ext-curl": "*",
"ext-json": "*",
"guzzlehttp/guzzle": "^7.2",
"psr/log": "^1.0",
"paragonie/sodium_compat": "^1.6"
},
"require-dev": {
"phpunit/phpunit": "^8.5|^9.3",
"overtrue/phplint": "^2.3"
},
"autoload": {
"psr-4": {
"Pusher\\": "src/"
}
},
"autoload-dev": {
"psr-4": { "": "tests/" }
},
"config": {
"preferred-install": "dist"
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
@@ -0,0 +1,7 @@
## Description
Add a short description of the change. If this is related to an issue, please add a reference to the issue.
## CHANGELOG
* [CHANGED] Describe your change here. Look at CHANGELOG.md to see the format.
@@ -0,0 +1,21 @@
<?php
namespace Pusher;
/**
* HTTP error responses.
* getCode() will return the response HTTP status code,
* and getMessage() will return the response body.
*/
class ApiErrorException extends PusherException
{
/**
* Returns the string representation of the exception.
*
* @return string
*/
public function __toString(): string
{
return "(Status {$this->getCode()}) {$this->getMessage()}";
}
}
+915
View File
@@ -0,0 +1,915 @@
<?php
namespace Pusher;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise\PromiseInterface;
class Pusher implements LoggerAwareInterface, PusherInterface
{
use LoggerAwareTrait;
/**
* @var string Version
*/
public static $VERSION = '7.0.1';
/**
* @var null|PusherCrypto
*/
private $crypto;
/**
* @var array Settings
*/
private $settings = [
'scheme' => 'http',
'port' => 80,
'path' => '',
'timeout' => 30,
];
/**
* @var null|resource
*/
private $client = null; // Guzzle client
/**
* Initializes a new Pusher instance with key, secret, app ID and channel.
*
* @param string $auth_key
* @param string $secret
* @param string $app_id
* @param array $options [optional]
* Options to configure the Pusher instance.
* scheme - e.g. http or https
* host - the host e.g. api-mt1.pusher.com. No trailing forward slash.
* port - the http port
* timeout - the http timeout
* useTLS - quick option to use scheme of https and port 443 (default is true).
* cluster - cluster name to connect to.
* encryption_master_key_base64 - a 32 byte key, encoded as base64. This key, along with the channel name, are used to derive per-channel encryption keys. Per-channel keys are used to encrypt event data on encrypted channels.
* @param ClientInterface|null $client [optional] - a Guzzle client to use for all HTTP requests
*
* @throws PusherException Throws exception if any required dependencies are missing
*/
public function __construct(string $auth_key, string $secret, string $app_id, array $options = [], ClientInterface $client = null)
{
$this->check_compatibility();
if (!is_null($client)) {
$this->client = $client;
} else {
$this->client = new \GuzzleHttp\Client();
}
$useTLS = true;
if (isset($options['useTLS'])) {
$useTLS = $options['useTLS'] === true;
}
if (
$useTLS &&
!isset($options['scheme']) &&
!isset($options['port'])
) {
$options['scheme'] = 'https';
$options['port'] = 443;
}
$this->settings['auth_key'] = $auth_key;
$this->settings['secret'] = $secret;
$this->settings['app_id'] = $app_id;
$this->settings['base_path'] = '/apps/' . $this->settings['app_id'];
foreach ($options as $key => $value) {
// only set if valid setting/option
if (isset($this->settings[$key])) {
$this->settings[$key] = $value;
}
}
// handle the case when 'host' and 'cluster' are specified in the options.
if (!array_key_exists('host', $this->settings)) {
if (array_key_exists('host', $options)) {
$this->settings['host'] = $options['host'];
} elseif (array_key_exists('cluster', $options)) {
$this->settings['host'] = 'api-' . $options['cluster'] . '.pusher.com';
} else {
$this->settings['host'] = 'api-mt1.pusher.com';
}
}
// ensure host doesn't have a scheme prefix
$this->settings['host'] = preg_replace('/http[s]?\:\/\//', '', $this->settings['host'], 1);
if (!array_key_exists('encryption_master_key_base64', $options)) {
$options['encryption_master_key_base64'] = '';
}
if ($options['encryption_master_key_base64'] !== '') {
$parsedKey = PusherCrypto::parse_master_key(
$options['encryption_master_key_base64']
);
$this->crypto = new PusherCrypto($parsedKey);
}
}
/**
* Fetch the settings.
*
* @return array
*/
public function getSettings(): array
{
return $this->settings;
}
/**
* Log a string.
*
* @param string $msg The message to log
* @param array|\Exception $context [optional] Any extraneous information that does not fit well in a string.
* @param string $level [optional] Importance of log message, highly recommended to use Psr\Log\LogLevel::{level}
*/
private function log(string $msg, array $context = [], string $level = LogLevel::DEBUG): void
{
if (is_null($this->logger)) {
return;
}
if ($this->logger instanceof LoggerInterface) {
$this->logger->log($level, $msg, $context);
return;
}
// Support old style logger (deprecated)
$msg = sprintf('Pusher: %s: %s', strtoupper($level), $msg);
$replacement = [];
foreach ($context as $k => $v) {
$replacement['{' . $k . '}'] = $v;
}
$this->logger->log($level, strtr($msg, $replacement));
}
/**
* Check if the current PHP setup is sufficient to run this class.
*
* @throws PusherException If any required dependencies are missing
*/
private function check_compatibility(): void
{
if (!extension_loaded('json')) {
throw new PusherException('The Pusher library requires the PHP JSON module. Please ensure it is installed');
}
if (!in_array('sha256', hash_algos(), true)) {
throw new PusherException('SHA256 appears to be unsupported - make sure you have support for it, or upgrade your version of PHP.');
}
}
/**
* Validate number of channels and channel name format.
*
* @param string[] $channels An array of channel names to validate
*
* @throws PusherException If $channels is too big or any channel is invalid
*/
private function validate_channels(array $channels): void
{
if (count($channels) > 100) {
throw new PusherException('An event can be triggered on a maximum of 100 channels in a single call.');
}
foreach ($channels as $channel) {
$this->validate_channel($channel);
}
}
/**
* Ensure a channel name is valid based on our spec.
*
* @param string $channel The channel name to validate
*
* @throws PusherException If $channel is invalid
*/
private function validate_channel(string $channel): void
{
if (!preg_match('/\A[-a-zA-Z0-9_=@,.;]+\z/', $channel)) {
throw new PusherException('Invalid channel name ' . $channel);
}
}
/**
* Ensure a socket_id is valid based on our spec.
*
* @param string $socket_id The socket ID to validate
*
* @throws PusherException If $socket_id is invalid
*/
private function validate_socket_id(string $socket_id): void
{
if ($socket_id !== null && !preg_match('/\A\d+\.\d+\z/', $socket_id)) {
throw new PusherException('Invalid socket ID ' . $socket_id);
}
}
/**
* Utility function used to generate signing headers
*
* @param string $path
* @param string $request_method
* @param array $query_params [optional]
*
* @return array
*/
private function sign(string $path, string $request_method = 'GET', array $query_params = []): array
{
return self::build_auth_query_params(
$this->settings['auth_key'],
$this->settings['secret'],
$request_method,
$path,
$query_params
);
}
/**
* Build the Channels url prefix.
*
* @return string
*/
private function channels_url_prefix(): string
{
return $this->settings['scheme'] . '://' . $this->settings['host'] . ':' . $this->settings['port'] . $this->settings['path'];
}
/**
* Build the required HMAC'd auth string.
*
* @param string $auth_key
* @param string $auth_secret
* @param string $request_method
* @param string $request_path
* @param array $query_params [optional]
* @param string $auth_version [optional]
* @param string|null $auth_timestamp [optional]
* @return array
*/
public static function build_auth_query_params(
string $auth_key,
string $auth_secret,
string $request_method,
string $request_path,
array $query_params = [],
string $auth_version = '1.0',
string $auth_timestamp = null
): array {
$params = [];
$params['auth_key'] = $auth_key;
$params['auth_timestamp'] = (is_null($auth_timestamp) ? time() : $auth_timestamp);
$params['auth_version'] = $auth_version;
$params = array_merge($params, $query_params);
ksort($params);
$string_to_sign = "$request_method\n" . $request_path . "\n" . self::array_implode('=', '&', $params);
$auth_signature = hash_hmac('sha256', $string_to_sign, $auth_secret, false);
$params['auth_signature'] = $auth_signature;
return $params;
}
/**
* Implode an array with the key and value pair giving
* a glue, a separator between pairs and the array
* to implode.
*
* @param string $glue The glue between key and value
* @param string $separator Separator between pairs
* @param array|string $array The array to implode
*
* @return string The imploded array
*/
public static function array_implode(string $glue, string $separator, $array): string
{
if (!is_array($array)) {
return $array;
}
$string = [];
foreach ($array as $key => $val) {
if (is_array($val)) {
$val = implode(',', $val);
}
$string[] = "{$key}{$glue}{$val}";
}
return implode($separator, $string);
}
/**
* Helper function to prepare trigger request. Takes the same
* parameters as the public trigger functions.
*
* @param array|string $channels A channel name or an array of channel names to publish the event on.
* @param string $event
* @param mixed $data Event data
* @param array $params [optional]
* @param bool $already_encoded [optional]
*
* @return Request
* @throws PusherException Throws PusherException if $channels is an array of size 101 or above or $socket_id is invalid
*/
public function make_request($channels, string $event, $data, array $params = [], bool $already_encoded = false): Request
{
if (is_string($channels) === true) {
$channels = [$channels];
}
$this->validate_channels($channels);
if (isset($params['socket_id'])) {
$this->validate_socket_id($params['socket_id']);
}
$has_encrypted_channel = false;
foreach ($channels as $chan) {
if (PusherCrypto::is_encrypted_channel($chan)) {
$has_encrypted_channel = true;
break;
}
}
if ($has_encrypted_channel) {
if (count($channels) > 1) {
// For rationale, see limitations of end-to-end encryption in the README
throw new PusherException('You cannot trigger to multiple channels when using encrypted channels');
} else {
try {
$data_encoded = $this->crypto->encrypt_payload(
$channels[0],
$already_encoded ? $data : json_encode($data, JSON_THROW_ON_ERROR)
);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
}
} else {
try {
$data_encoded = $already_encoded ? $data : json_encode($data, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
}
$query_params = [];
$path = $this->settings['base_path'] . '/events';
// json_encode might return false on failure
if (!$data_encoded) {
$this->log('Failed to perform json_encode on the the provided data: {error}', [
'error' => print_r($data, true),
], LogLevel::ERROR);
}
$post_params = [];
$post_params['name'] = $event;
$post_params['data'] = $data_encoded;
$post_params['channels'] = array_values($channels);
$all_params = array_merge($post_params, $params);
try {
$post_value = json_encode($all_params, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
$query_params['body_md5'] = md5($post_value);
$signature = $this->sign($path, 'POST', $query_params);
$this->log('trigger POST: {post_value}', compact('post_value'));
$headers = [
'Content-Type' => 'application/json',
'X-Pusher-Library' => 'pusher-http-php ' . self::$VERSION
];
$params = array_merge($signature, $query_params);
$query_string = self::array_implode('=', '&', $params);
$full_path = $path . "?" . $query_string;
return new Request('POST', $full_path, $headers, $post_value);
}
/**
* Trigger an event by providing event name and payload.
* Optionally provide a socket ID to exclude a client (most likely the sender).
*
* @param array|string $channels A channel name or an array of channel names to publish the event on.
* @param string $event
* @param mixed $data Event data
* @param array $params [optional]
* @param bool $already_encoded [optional]
*
* @return object
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
* @throws PusherException Throws PusherException if $channels is an array of size 101 or above or $socket_id is invalid
*/
public function trigger($channels, string $event, $data, array $params = [], bool $already_encoded = false): object
{
$request = $this->make_request($channels, $event, $data, $params, $already_encoded);
$response = $this->client->send($request, [
'http_errors' => false,
'base_uri' => $this->channels_url_prefix()
]);
$status = $response->getStatusCode();
if ($status !== 200) {
$body = (string) $response->getBody();
throw new ApiErrorException($body, $status);
}
try {
$result = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
}
/**
* Asynchronously trigger an event by providing event name and payload.
* Optionally provide a socket ID to exclude a client (most likely the sender).
*
* @param array|string $channels A channel name or an array of channel names to publish the event on.
* @param string $event
* @param mixed $data Event data
* @param array $params [optional]
* @param bool $already_encoded [optional]
*
* @return PromiseInterface
* @throws PusherException
*/
public function triggerAsync($channels, string $event, $data, array $params = [], bool $already_encoded = false): PromiseInterface
{
$request = $this->make_request($channels, $event, $data, $params, $already_encoded);
$promise = $this->client->sendAsync($request, [
'http_errors' => false,
'base_uri' => $this->channels_url_prefix()
])->then(function ($response) {
$status = $response->getStatusCode();
if ($status !== 200) {
$body = (string) $response->getBody();
throw new ApiErrorException($body, $status);
}
$result = json_decode($response->getBody(), null, 512, JSON_THROW_ON_ERROR);
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
});
return $promise;
}
/**
* Helper function to prepare batch trigger request. Takes the same * parameters as the public batch trigger functions.
*
* @param array $batch [optional] An array of events to send
* @param bool $already_encoded [optional]
*
* @return Request
* @throws PusherException
*/
public function make_batch_request(array $batch = [], bool $already_encoded = false): Request
{
foreach ($batch as $key => $event) {
$this->validate_channel($event['channel']);
if (isset($event['socket_id'])) {
$this->validate_socket_id($event['socket_id']);
}
$data = $event['data'];
if (!is_string($data)) {
try {
$data = $already_encoded ? $data : json_encode($data, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
}
if (PusherCrypto::is_encrypted_channel($event['channel'])) {
$batch[$key]['data'] = $this->crypto->encrypt_payload($event['channel'], $data);
} else {
$batch[$key]['data'] = $data;
}
}
$post_params = [];
$post_params['batch'] = $batch;
try {
$post_value = json_encode($post_params, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
$query_params = [];
$query_params['body_md5'] = md5($post_value);
$path = $this->settings['base_path'] . '/batch_events';
$signature = $this->sign($path, 'POST', $query_params);
$this->log('trigger POST: {post_value}', compact('post_value'));
$headers = [
'Content-Type' => 'application/json',
'X-Pusher-Library' => 'pusher-http-php ' . self::$VERSION
];
$params = array_merge($signature, $query_params);
$query_string = self::array_implode('=', '&', $params);
$full_path = $path . "?" . $query_string;
return new Request('POST', $full_path, $headers, $post_value);
}
/**
* Trigger multiple events at the same time.
*
* @param array $batch [optional] An array of events to send
* @param bool $already_encoded [optional]
*
* @return object
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
* @throws PusherException
*/
public function triggerBatch(array $batch = [], bool $already_encoded = false): object
{
$request = $this->make_batch_request($batch, $already_encoded);
$response = $this->client->send($request, [
'http_errors' => false,
'base_uri' => $this->channels_url_prefix()
]);
$status = $response->getStatusCode();
if ($status !== 200) {
$body = (string) $response->getBody();
throw new ApiErrorException($body, $status);
}
try {
$result = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
}
/**
* Asynchronously trigger multiple events at the same time.
*
* @param array $batch [optional] An array of events to send
* @param bool $already_encoded [optional]
*
* @return PromiseInterface
* @throws PusherException
*/
public function triggerBatchAsync(array $batch = [], bool $already_encoded = false): PromiseInterface
{
$request = $this->make_batch_request($batch, $already_encoded);
$promise = $this->client->sendAsync($request, [
'http_errors' => false,
'base_uri' => $this->channels_url_prefix()
])->then(function ($response) {
$status = $response->getStatusCode();
if ($status !== 200) {
$body = (string) $response->getBody();
throw new ApiErrorException($body, $status);
}
$result = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
});
return $promise;
}
/**
* Fetch channel information for a specific channel.
*
* @param string $channel The name of the channel
* @param array $params Additional parameters for the query e.g. $params = array( 'info' => 'connection_count' )
*
* @throws PusherException If $channel is invalid
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
*
*/
public function getChannelInfo(string $channel, array $params = []): object
{
$this->validate_channel($channel);
return $this->get('/channels/' . $channel, $params);
}
/**
* @deprecated in favour of getChannelInfo
*/
public function get_channel_info(string $channel, array $params = []): object
{
return $this->getChannelInfo($channel, $params);
}
/**
* Fetch a list containing all channels.
*
* @param array $params Additional parameters for the query e.g. $params = array( 'info' => 'connection_count' )
*
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
*
*/
public function getChannels(array $params = []): object
{
$result = $this->get('/channels', $params);
$result->channels = get_object_vars($result->channels);
return $result;
}
/**
* @deprecated in favour of getChannels
*/
public function get_channels(array $params = []): object
{
return $this->getChannels($params);
}
/**
* Fetch user ids currently subscribed to a presence channel.
*
* @param string $channel The name of the channel
*
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
*
*/
public function getPresenceUsers(string $channel): object
{
return $this->get('/channels/' . $channel . '/users');
}
/**
* @deprecated in favour of getPresenceUsers
*/
public function get_users_info(string $channel): object
{
return $this->getPresenceUsers($channel);
}
/**
* GET arbitrary REST API resource using a synchronous http client.
* All request signing is handled automatically.
*
* @param string $path Path excluding /apps/APP_ID
* @param array $params API params (see http://pusher.com/docs/rest_api)
* @param bool $associative When true, return the response body as an associative array, else return as an object
*
* @throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
* @throws GuzzleException
* @throws PusherException
*
* @return mixed See Pusher API docs
*/
public function get(string $path, array $params = [], $associative = false)
{
$path = $this->settings['base_path'] . $path;
$signature = $this->sign($path, 'GET', $params);
$headers = [
'Content-Type' => 'application/json',
'X-Pusher-Library' => 'pusher-http-php ' . self::$VERSION
];
$response = $this->client->get($path, [
'query' => $signature,
'http_errors' => false,
'headers' => $headers,
'base_uri' => $this->channels_url_prefix()
]);
$status = $response->getStatusCode();
if ($status !== 200) {
$body = (string) $response->getBody();
throw new ApiErrorException($body, $status);
}
try {
$body = json_decode($response->getBody(), $associative, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data decoding error.');
}
return $body;
}
/**
* Creates a socket signature.
*
* @param string $channel
* @param string $socket_id
* @param string|null $custom_data
*
* @return string Json encoded authentication string.
* @throws PusherException Throws exception if $channel is invalid or above or $socket_id is invalid
*/
public function socketAuth(string $channel, string $socket_id, string $custom_data = null): string
{
$this->validate_channel($channel);
$this->validate_socket_id($socket_id);
if ($custom_data) {
$signature = hash_hmac('sha256', $socket_id . ':' . $channel . ':' . $custom_data, $this->settings['secret'], false);
} else {
$signature = hash_hmac('sha256', $socket_id . ':' . $channel, $this->settings['secret'], false);
}
$signature = ['auth' => $this->settings['auth_key'] . ':' . $signature];
// add the custom data if it has been supplied
if ($custom_data) {
$signature['channel_data'] = $custom_data;
}
if (PusherCrypto::is_encrypted_channel($channel)) {
if (!is_null($this->crypto)) {
$signature['shared_secret'] = base64_encode($this->crypto->generate_shared_secret($channel));
} else {
throw new PusherException('You must specify an encryption master key to authorize an encrypted channel');
}
}
try {
$response = json_encode($signature, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
return $response;
}
/**
* @deprecated in favour of socketAuth
*/
public function socket_auth(string $channel, string $socket_id, string $custom_data = null): string
{
return $this->socketAuth($channel, $socket_id, $custom_data);
}
/**
* Creates a presence signature (an extension of socket signing).
*
* @param string $channel
* @param string $socket_id
* @param string $user_id
* @param mixed $user_info
*
* @return string
* @throws PusherException Throws exception if $channel is invalid or above or $socket_id is invalid
*/
public function presenceAuth(string $channel, string $socket_id, string $user_id, $user_info = null): string
{
$user_data = ['user_id' => $user_id];
if ($user_info) {
$user_data['user_info'] = $user_info;
}
try {
return $this->socket_auth($channel, $socket_id, json_encode($user_data, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
}
/**
* @deprecated in favour of presenceAuth
*/
public function presence_auth(string $channel, string $socket_id, string $user_id, $user_info = null): string
{
return $this->presenceAuth($channel, $socket_id, $user_id, $user_info);
}
/**
* Verify that a webhook actually came from Pusher, decrypts any encrypted events, and marshals them into a PHP object.
*
* @param array $headers a array of headers from the request (for example, from getallheaders())
* @param string $body the body of the request (for example, from file_get_contents('php://input'))
*
* @throws PusherException
*
* @return Webhook marshalled object with the properties time_ms (an int) and events (an array of event objects)
*/
public function webhook(array $headers, string $body): object
{
$this->ensure_valid_signature($headers, $body);
$decoded_events = [];
try {
$decoded_json = json_decode($body, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$this->log('Unable to decrypt webhook event payload.', null, LogLevel::WARNING);
throw new PusherException('Data encoding error.');
}
foreach ($decoded_json->events as $key => $event) {
if (PusherCrypto::is_encrypted_channel($event->channel)) {
if (!is_null($this->crypto)) {
$decryptedEvent = $this->crypto->decrypt_event($event);
if ($decryptedEvent === false) {
$this->log('Unable to decrypt webhook event payload. Wrong key? Ignoring.', null, LogLevel::WARNING);
continue;
}
$decoded_events[] = $decryptedEvent;
} else {
$this->log('Got an encrypted webhook event payload, but no master key specified. Ignoring.', null, LogLevel::WARNING);
continue;
}
} else {
$decoded_events[] = $event;
}
}
return new Webhook($decoded_json->time_ms, $decoded_events);
}
/**
* Verify that a given Pusher Signature is valid.
*
* @param array $headers an array of headers from the request (for example, from getallheaders())
* @param string $body the body of the request (for example, from file_get_contents('php://input'))
*
* @throws PusherException if signature is incorrect.
*/
public function verifySignature(array $headers, string $body): void
{
$x_pusher_key = $headers['X-Pusher-Key'];
$x_pusher_signature = $headers['X-Pusher-Signature'];
if ($x_pusher_key === $this->settings['auth_key']) {
$expected = hash_hmac('sha256', $body, $this->settings['secret']);
if ($expected === $x_pusher_signature) {
return;
}
}
throw new PusherException(sprintf('Received WebHook with invalid signature: got %s.', $x_pusher_signature));
}
/**
* @deprecated in favour of verifySignature
*/
public function ensure_valid_signature(array $headers, string $body): void
{
$this->verifySignature($headers, $body);
}
}
+200
View File
@@ -0,0 +1,200 @@
<?php
namespace Pusher;
class PusherCrypto
{
private $encryption_master_key;
// The prefix any e2e channel must have
public const ENCRYPTED_PREFIX = 'private-encrypted-';
/**
* Checks if a given channel is an encrypted channel.
*
* @param string $channel the name of the channel
*
* @return bool true if channel is an encrypted channel
*/
public static function is_encrypted_channel(string $channel): bool
{
return strpos($channel, self::ENCRYPTED_PREFIX) === 0;
}
/**
* @param $encryption_master_key_base64
* @return string
* @throws PusherException
*/
public static function parse_master_key($encryption_master_key_base64): string
{
if (!function_exists('sodium_crypto_secretbox')) {
throw new PusherException('To use end to end encryption, you must either be using PHP 7.2 or greater or have installed the libsodium-php extension for php < 7.2.');
}
if ($encryption_master_key_base64 !== '') {
$decoded_key = base64_decode($encryption_master_key_base64, true);
if ($decoded_key === false) {
throw new PusherException('encryption_master_key_base64 must be a valid base64 string');
}
if (strlen($decoded_key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new PusherException('encryption_master_key_base64 must encode a key which is 32 bytes long');
}
return $decoded_key;
}
return '';
}
/**
* Initialises a PusherCrypto instance.
*
* @param string $encryption_master_key the SECRET_KEY_LENGTH key that will be used for key derivation.
*/
public function __construct(string $encryption_master_key)
{
$this->encryption_master_key = $encryption_master_key;
}
/**
* Decrypts a given event.
*
* @param object $event an object that has an encrypted data property and a channel property.
*
* @return object the event with a decrypted payload, or false if decryption was unsuccessful.
* @throws PusherException
*/
public function decrypt_event(object $event): object
{
$parsed_payload = $this->parse_encrypted_message($event->data);
$shared_secret = $this->generate_shared_secret($event->channel);
$decrypted_payload = $this->decrypt_payload($parsed_payload->ciphertext, $parsed_payload->nonce, $shared_secret);
if (!$decrypted_payload) {
throw new PusherException('Decryption of the payload failed. Wrong key?');
}
$event->data = $decrypted_payload;
return $event;
}
/**
* Derives a shared secret from the secret key and the channel to broadcast to.
*
* @param string $channel the name of the channel
*
* @return string a SHA256 hash (encoded as base64) of the channel name appended to the encryption key
* @throws PusherException
*/
public function generate_shared_secret(string $channel): string
{
if (!self::is_encrypted_channel($channel)) {
throw new PusherException('You must specify a channel of the form private-encrypted-* for E2E encryption. Got ' . $channel);
}
return hash('sha256', $channel . $this->encryption_master_key, true);
}
/**
* Encrypts a given plaintext for broadcast on a particular channel.
*
* @param string $channel the name of the channel the payloads event will be broadcast on
* @param string $plaintext the data to encrypt
*
* @return string a string ready to be sent as the data of an event.
* @throws PusherException
* @throws \SodiumException
*/
public function encrypt_payload(string $channel, string $plaintext): string
{
if (!self::is_encrypted_channel($channel)) {
throw new PusherException('Cannot encrypt plaintext for a channel that is not of the form private-encrypted-*. Got ' . $channel);
}
$nonce = $this->generate_nonce();
$shared_secret = $this->generate_shared_secret($channel);
$cipher_text = sodium_crypto_secretbox($plaintext, $nonce, $shared_secret);
try {
return $this->format_encrypted_message($nonce, $cipher_text);
} catch (\JsonException $e) {
throw new PusherException('Data encoding error.');
}
}
/**
* Decrypts a given payload using the nonce and shared secret.
*
* @param string $payload the ciphertext
* @param string $nonce the nonce used in the encryption
* @param string $shared_secret the shared_secret used in the encryption
*
* @return string plaintext
* @throws \SodiumException
*/
public function decrypt_payload(string $payload, string $nonce, string $shared_secret)
{
$plaintext = sodium_crypto_secretbox_open($payload, $nonce, $shared_secret);
if (empty($plaintext)) {
return false;
}
return $plaintext;
}
/**
* Formats an encrypted message ready for broadcast.
*
* @param string $nonce the nonce used in the encryption process (bytes)
* @param string $ciphertext the ciphertext (bytes)
*
* @return string JSON with base64 encoded nonce and ciphertext`
* @throws \JsonException
*/
private function format_encrypted_message(string $nonce, string $ciphertext): string
{
$encrypted_message = new \stdClass();
$encrypted_message->nonce = base64_encode($nonce);
$encrypted_message->ciphertext = base64_encode($ciphertext);
return json_encode($encrypted_message, JSON_THROW_ON_ERROR);
}
/**
* Parses an encrypted message into its nonce and ciphertext components.
*
*
* @param string $payload the encrypted message payload
*
* @return object php object with decoded nonce and ciphertext
* @throws PusherException
*/
private function parse_encrypted_message(string $payload): object
{
try {
$decoded_payload = json_decode($payload, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new PusherException('Data decoding error.');
}
$decoded_payload->nonce = base64_decode($decoded_payload->nonce);
$decoded_payload->ciphertext = base64_decode($decoded_payload->ciphertext);
if ($decoded_payload->ciphertext === '' || strlen($decoded_payload->nonce) !== SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
throw new PusherException('Received a payload that cannot be parsed.');
}
return $decoded_payload;
}
/**
* Generates a nonce that is SODIUM_CRYPTO_SECRETBOX_NONCEBYTES long.
* @return string
* @throws \Exception
*/
private function generate_nonce(): string
{
return random_bytes(
SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);
}
}
@@ -0,0 +1,9 @@
<?php
namespace Pusher;
use Exception;
class PusherException extends Exception
{
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Pusher;
class PusherInstance
{
private static $instance = null;
private static $app_id = '';
private static $secret = '';
private static $api_key = '';
/**
* Get the pusher singleton instance.
*
* @return Pusher
* @throws PusherException
*/
public static function get_pusher()
{
if (self::$instance !== null) {
return self::$instance;
}
self::$instance = new Pusher(
self::$api_key,
self::$secret,
self::$app_id
);
return self::$instance;
}
}

Some files were not shown because too many files have changed in this diff Show More